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 @@

Other credits

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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 @@ > + + + +