Skip to content

Commit

Permalink
Merge PR #902 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by rousseldenis
  • Loading branch information
OCA-git-bot committed Jan 14, 2025
2 parents 0f76e45 + d84f2d0 commit eed1335
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 55 deletions.
2 changes: 1 addition & 1 deletion shopfloor/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{
"name": "Shopfloor",
"summary": "manage warehouse operations with barcode scanners",
"version": "16.0.2.4.1",
"version": "16.0.2.4.2",
"development_status": "Beta",
"category": "Inventory",
"website": "https://github.com/OCA/wms",
Expand Down
3 changes: 2 additions & 1 deletion shopfloor/data/shopfloor_scenario_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"scan_location_or_pack_first": true,
"allow_alternative_destination_package": true,
"allow_move_line_search_sort_order": false,
"allow_move_line_search_additional_domain": true
"allow_move_line_search_additional_domain": true,
"require_destination_package": true
}
</field>
</record>
Expand Down
29 changes: 29 additions & 0 deletions shopfloor/migrations/16.0.2.4.2/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2024 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import json
import logging

from odoo import SUPERUSER_ID, api

_logger = logging.getLogger(__name__)


def migrate(cr, version):
_logger.info("Updating scenario Zone Picking")
if not version:
return
env = api.Environment(cr, SUPERUSER_ID, {})
zone_picking_scenario = env.ref("shopfloor.scenario_zone_picking")
_update_scenario_options(zone_picking_scenario)


def _update_scenario_options(scenario):
options = scenario.options
if "require_destination_package" not in options:
options["require_destination_package"] = True
options_edit = json.dumps(options or {}, indent=4, sort_keys=True)
scenario.write({"options_edit": options_edit})
_logger.info(
"Option require_destination_package added to scenario Zone Picking"
)
42 changes: 41 additions & 1 deletion shopfloor/models/shopfloor_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ class ShopfloorMenu(models.Model):
allow_alternative_destination_package_is_possible = fields.Boolean(
compute="_compute_allow_alternative_destination_package_is_possible"
)

move_line_search_additional_domain_is_possible = fields.Boolean(
compute="_compute_move_line_search_additional_domain_is_possible"
)
Expand All @@ -250,6 +249,16 @@ class ShopfloorMenu(models.Model):
move_line_search_sort_order_custom_code = fields.Text(
string="Custom sort key code", help="Python code to sort move lines. "
)
require_destination_package = fields.Boolean(
string="Destination package required",
default=True,
help="If set, the user will have to scan only the source location "
"and the destination location to process a line. The unload step will be skipped.",
)

require_destination_package_is_possible = fields.Boolean(
compute="_compute_require_destination_package_is_possible"
)

@api.onchange("unload_package_at_destination")
def _onchange_unload_package_at_destination(self):
Expand All @@ -267,6 +276,16 @@ def _onchange_pick_pack_same_time(self):
record.unload_package_at_destination = False
record.multiple_move_single_pack = False

@api.onchange("require_destination_package")
def _onchange_require_destination_package(self):
# require_destination_package is incompatible with pick_pack_same_time and
# unload_package_at_destination and multiple_move_single_pack
for record in self:
if not record.require_destination_package:
record.pick_pack_same_time = False
record.unload_package_at_destination = False
record.multiple_move_single_pack = False

@api.onchange("multiple_move_single_pack")
def _onchange_multiple_move_single_pack(self):
# multiple_move_single_pack is incompatible with pick_pack_same_time,
Expand All @@ -278,6 +297,7 @@ def _onchange_multiple_move_single_pack(self):
"unload_package_at_destination",
"pick_pack_same_time",
"multiple_move_single_pack",
"require_destination_package",
)
def _check_options(self):
if self.pick_pack_same_time and self.unload_package_at_destination:
Expand All @@ -294,6 +314,19 @@ def _check_options(self):
"'Multiple moves same destination package'."
)
)
elif not self.require_destination_package and (
self.pick_pack_same_time
or self.unload_package_at_destination
or self.multiple_move_single_pack
):
raise exceptions.UserError(
_(
"'No destination package required' is incompatible with "
"'Pick and pack at the same time',"
"'Unload package at destination' and 'Multiple moves "
"same destination package'."
)
)

@api.depends("scenario_id", "picking_type_ids")
def _compute_move_create_is_possible(self):
Expand Down Expand Up @@ -497,6 +530,13 @@ def _compute_move_line_search_sort_order_is_possible(self):
"allow_move_line_search_sort_order"
)

@api.depends("scenario_id")
def _compute_require_destination_package_is_possible(self):
for menu in self:
menu.require_destination_package_is_possible = menu.scenario_id.has_option(
"require_destination_package"
)

@api.constrains(
"move_line_search_sort_order", "move_line_search_sort_order_custom_code"
)
Expand Down
76 changes: 38 additions & 38 deletions shopfloor/services/zone_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ def lines_order(self):
def _pick_pack_same_time(self):
return self.work.menu.pick_pack_same_time

def _packing_required(self):
return self.work.menu.require_destination_package

def _handle_complete_mix_pack(self, package):
packaging = self._actions_for("packaging")
return (
Expand Down Expand Up @@ -924,7 +927,7 @@ def _set_destination_location(
return (location_changed, response)

# If no destination package
if not move_line.result_package_id:
if self._packing_required() and not move_line.result_package_id:
response = self._response_for_set_line_destination(
move_line,
message=self.msg_store.dest_package_required(),
Expand Down Expand Up @@ -1169,43 +1172,40 @@ def set_destination(
)

extra_message = ""
if moving_full_quantity:
# When the barcode is a location,
# only allow it if moving the full qty.
location = search.location_from_scan(barcode)
if location:
package = None
if handle_complete_mix_pack:
package = move_line.package_id
if self._pick_pack_same_time():
(
good_for_packing,
message,
) = self._handle_pick_pack_same_time_for_location(move_line)
# TODO: we should append the msg instead.
# To achieve this, we should refactor `response.message` to a list
# or, to no break backward compat, we could add `extra_messages`
# to allow backend to send a main message and N additional messages.
extra_message = message
if not good_for_packing:
return self._response_for_set_line_destination(
move_line, message=message, qty_done=quantity
)
pkg_moved, response = self._set_destination_location(
move_line,
package,
quantity,
confirmation,
location,
barcode,
)
if response:
if extra_message:
if response.get("message"):
response["message"]["body"] += "\n" + extra_message["body"]
else:
response["message"] = extra_message
return response
location = search.location_from_scan(barcode)
if location:
package = None
if handle_complete_mix_pack:
package = move_line.package_id
if self._pick_pack_same_time():
(
good_for_packing,
message,
) = self._handle_pick_pack_same_time_for_location(move_line)
# TODO: we should append the msg instead.
# To achieve this, we should refactor `response.message` to a list
# or, to no break backward compat, we could add `extra_messages`
# to allow backend to send a main message and N additional messages.
extra_message = message
if not good_for_packing:
return self._response_for_set_line_destination(
move_line, message=message, qty_done=quantity
)
pkg_moved, response = self._set_destination_location(
move_line,
package,
quantity,
confirmation,
location,
barcode,
)
if response:
if extra_message:
if response.get("message"):
response["message"]["body"] += "\n" + extra_message["body"]
else:
response["message"] = extra_message
return response

# When the barcode is a package
package = search.package_from_scan(barcode)
Expand Down
1 change: 1 addition & 0 deletions shopfloor/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
from . import test_zone_picking_unload_single
from . import test_zone_picking_unload_all
from . import test_zone_picking_unload_set_destination
from . import test_zone_picking_require_destination_package
from . import test_misc
from . import test_move_action_assign
from . import test_scan_anything
Expand Down
62 changes: 62 additions & 0 deletions shopfloor/tests/test_zone_picking_require_destination_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from .test_zone_picking_base import ZonePickingCommonCase

# pylint: disable=missing-return


class ZonePickingNoPAcking(ZonePickingCommonCase):
"""Tests zone picking without packing steps.
* /set_destination
"""

def setUp(self):
super().setUp()
self.service.work.current_picking_type = self.picking1.picking_type_id
self.picking1.move_line_ids.result_package_id = False

def test_set_destination(self):
# when no packing is set, you can set the destination directly
# without the need to pack the product
self.service.work.menu.sudo().require_destination_package = True
zone_location = self.zone_location
picking_type = self.picking1.picking_type_id
move_line = self.picking1.move_line_ids[0]
response = self.service.dispatch(
"set_destination",
params={
"move_line_id": move_line.id,
"barcode": move_line.location_dest_id.barcode,
"quantity": move_line.reserved_uom_qty,
"confirmation": None,
},
)
self.assert_response_set_line_destination(
response,
zone_location,
picking_type,
move_line,
qty_done=move_line.reserved_uom_qty,
message=self.service.msg_store.dest_package_required(),
)
self.service.work.menu.sudo().require_destination_package = False
response = self.service.dispatch(
"set_destination",
params={
"move_line_id": move_line.id,
"barcode": move_line.location_dest_id.barcode,
"quantity": move_line.reserved_uom_qty,
"confirmation": None,
},
)
move_lines = self.service._find_location_move_lines()
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
self.assert_response_select_line(
response,
zone_location,
picking_type,
move_lines,
message=self.service.msg_store.confirm_pack_moved(),
)
43 changes: 29 additions & 14 deletions shopfloor/tests/test_zone_picking_set_line_destination.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,15 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
move qty 10 (assigned):
-> move_line qty 10 from location X
Then the operator move 6 qty on 10, we get:
an error because we can move only full qty by location
and only a package barcode is allowed on scan.
Then the operator move 6 qty on 10:
-> move_line qty 6 from location X (done)
-> move_line qty 4 from location X (assigned)
"""
zone_location = self.zone_location
picking_type = self.picking3.picking_type_id
barcode = self.packing_location.barcode
moves_before = self.picking3.move_ids
self.assertEqual(moves_before.product_uom_qty, 10)
self.assertEqual(len(moves_before), 1)
self.assertEqual(len(moves_before.move_line_ids), 1)
move_line = moves_before.move_line_ids
Expand All @@ -210,14 +210,21 @@ def test_set_destination_location_no_other_move_line_partial_qty(self):
"confirmation": None,
},
)
self.assert_response_set_line_destination(
move_lines = self.service._find_location_move_lines()
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
self.assert_response_select_line(
response,
zone_location,
picking_type,
move_line,
qty_done=6,
message=self.service.msg_store.package_not_found_for_barcode(barcode),
move_lines,
message=self.service.msg_store.confirm_pack_moved(),
)
done_move = move_line.move_id
assigned_move = moves_before
self.assertEqual(done_move.state, "done")
self.assertEqual(done_move.product_uom_qty, 6)
self.assertEqual(assigned_move.state, "assigned")
self.assertEqual(assigned_move.product_uom_qty, 4)

def test_set_destination_location_several_move_line_full_qty(self):
"""Scanned barcode is the destination location.
Expand Down Expand Up @@ -297,8 +304,8 @@ def test_set_destination_location_several_move_line_partial_qty(self):
Then the operator move 4 qty on 6 (from the first move line), we get:
an error because we can move only full qty by location
and only a package barcode is allowed on scan.
-> move_line qty 6 from location X (assigned)
-> move_line qty 4 from location Y (done)
"""
zone_location = self.zone_location
picking_type = self.picking4.picking_type_id
Expand All @@ -318,14 +325,22 @@ def test_set_destination_location_several_move_line_partial_qty(self):
"confirmation": None,
},
)
self.assert_response_set_line_destination(
# Check response
move_lines = self.service._find_location_move_lines()
move_lines = move_lines.sorted(lambda l: l.move_id.priority, reverse=True)
self.assert_response_select_line(
response,
zone_location,
picking_type,
move_line,
qty_done=4,
message=self.service.msg_store.package_not_found_for_barcode(barcode),
move_lines,
message=self.service.msg_store.confirm_pack_moved(),
)
done_move = move_line.move_id
assigned_move = moves_before
self.assertEqual(done_move.state, "done")
self.assertEqual(done_move.product_uom_qty, 4)
self.assertEqual(assigned_move.state, "assigned")
self.assertEqual(assigned_move.product_uom_qty, 6)

def test_set_destination_location_zero_check(self):
"""Scanned barcode is the destination location.
Expand Down
Loading

0 comments on commit eed1335

Please sign in to comment.