diff --git a/shopfloor/__manifest__.py b/shopfloor/__manifest__.py
index 91c4f94f29..ab29111e43 100644
--- a/shopfloor/__manifest__.py
+++ b/shopfloor/__manifest__.py
@@ -6,7 +6,7 @@
{
"name": "Shopfloor",
"summary": "manage warehouse operations with barcode scanners",
- "version": "16.0.2.1.0",
+ "version": "16.0.2.1.1",
"development_status": "Beta",
"category": "Inventory",
"website": "https://github.com/OCA/wms",
diff --git a/shopfloor/data/shopfloor_scenario_data.xml b/shopfloor/data/shopfloor_scenario_data.xml
index 41768a3db3..1f16d068fa 100644
--- a/shopfloor/data/shopfloor_scenario_data.xml
+++ b/shopfloor/data/shopfloor_scenario_data.xml
@@ -21,7 +21,8 @@
"multiple_move_single_pack": true,
"no_prefill_qty": true,
"scan_location_or_pack_first": true,
- "allow_alternative_destination_package": true
+ "allow_alternative_destination_package": true,
+ "require_destination_package": true
}
diff --git a/shopfloor/migrations/16.0.2.1.1/post-migration.py b/shopfloor/migrations/16.0.2.1.1/post-migration.py
new file mode 100644
index 0000000000..f08f21ad0b
--- /dev/null
+++ b/shopfloor/migrations/16.0.2.1.1/post-migration.py
@@ -0,0 +1,26 @@
+# 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
+ 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")
diff --git a/shopfloor/models/shopfloor_menu.py b/shopfloor/models/shopfloor_menu.py
index bb0f0e0764..5564640df6 100644
--- a/shopfloor/models/shopfloor_menu.py
+++ b/shopfloor/models/shopfloor_menu.py
@@ -227,6 +227,17 @@ class ShopfloorMenu(models.Model):
compute="_compute_allow_alternative_destination_package_is_possible"
)
+ 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):
# Uncheck pick_pack_same_time when unload_package_at_destination is set to True
@@ -243,6 +254,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,
@@ -254,6 +275,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:
@@ -270,6 +292,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):
@@ -458,3 +493,10 @@ def _compute_allow_alternative_destination_package_is_possible(self):
menu.allow_alternative_destination_package_is_possible = (
menu.scenario_id.has_option("allow_alternative_destination_package")
)
+
+ @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"
+ )
diff --git a/shopfloor/services/zone_picking.py b/shopfloor/services/zone_picking.py
index 17b184b9c7..683221b9ca 100644
--- a/shopfloor/services/zone_picking.py
+++ b/shopfloor/services/zone_picking.py
@@ -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 (
@@ -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(),
diff --git a/shopfloor/static/description/index.html b/shopfloor/static/description/index.html
index 5202dc7132..1c38ff71bf 100644
--- a/shopfloor/static/description/index.html
+++ b/shopfloor/static/description/index.html
@@ -8,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
-:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
+:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
+Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }
-pre.code .ln { color: grey; } /* line numbers */
+pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -300,7 +301,7 @@
span.pre {
white-space: pre }
-span.problematic {
+span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@@ -485,7 +486,9 @@
This module is maintained by the OCA.
-
+
+
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
diff --git a/shopfloor/tests/__init__.py b/shopfloor/tests/__init__.py
index 7f5c27e658..5cbbd3c7dd 100644
--- a/shopfloor/tests/__init__.py
+++ b/shopfloor/tests/__init__.py
@@ -77,6 +77,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
diff --git a/shopfloor/tests/test_zone_picking_require_destination_package.py b/shopfloor/tests/test_zone_picking_require_destination_package.py
new file mode 100644
index 0000000000..5f1681fbf7
--- /dev/null
+++ b/shopfloor/tests/test_zone_picking_require_destination_package.py
@@ -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(),
+ )
diff --git a/shopfloor/views/shopfloor_menu.xml b/shopfloor/views/shopfloor_menu.xml
index 5617de5f9e..11b31c69a5 100644
--- a/shopfloor/views/shopfloor_menu.xml
+++ b/shopfloor/views/shopfloor_menu.xml
@@ -32,6 +32,16 @@
>
+
+
+
+