From 6bab9c7450bf9c0795d404b73471c6c9180a4a3e Mon Sep 17 00:00:00 2001 From: Nicolas Delbovier Date: Tue, 28 Apr 2026 17:09:37 +0200 Subject: [PATCH] [IMP] shopfloor_reception: prevent process 0 qty on move line --- shopfloor_reception/actions/message.py | 9 ++++ shopfloor_reception/services/reception.py | 46 ++++++++----------- .../tests/test_set_quantity_action.py | 25 ++++++++++ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/shopfloor_reception/actions/message.py b/shopfloor_reception/actions/message.py index ef38141237..0d17382616 100644 --- a/shopfloor_reception/actions/message.py +++ b/shopfloor_reception/actions/message.py @@ -39,3 +39,12 @@ def lot_creation_disabled(self, picking_type): picking_type=picking_type.display_name, ), } + + def invalid_quantity(self, qty): + return { + "message_type": "error", + "body": _( + "Invalid quantity: '%(qty)s'.", + qty=qty, + ), + } diff --git a/shopfloor_reception/services/reception.py b/shopfloor_reception/services/reception.py index c62c06354f..2d48d0a357 100644 --- a/shopfloor_reception/services/reception.py +++ b/shopfloor_reception/services/reception.py @@ -1416,8 +1416,6 @@ def set_quantity__cancel_action(self, picking_id, selected_line_id): return self._response_for_select_move(picking) def _set_quantity__process__set_qty_and_split(self, picking, line, quantity): - move = line.move_id - sum(move.move_line_ids.mapped("qty_done")) savepoint = self._actions_for("savepoint").new() line.qty_done = quantity compare = self._set_quantity__check_quantity_done(line) @@ -1437,33 +1435,33 @@ def _set_quantity__process__set_qty_and_split(self, picking, line, quantity): } line._split_qty_to_be_done(quantity, **default_values) - def process_with_existing_pack(self, picking_id, selected_line_id, quantity): - picking = self.env["stock.picking"].browse(picking_id) - selected_line = self.env["stock.move.line"].browse(selected_line_id) - message = self._check_picking_processible(picking) - if message: + def _process(self, picking, line, quantity): + if message := self._check_picking_processible(picking): + return self._response_for_set_quantity(picking, line, message=message) + + if float_is_zero(quantity, precision_rounding=line.product_id.uom_id.rounding): return self._response_for_set_quantity( - picking, selected_line, message=message + picking, + line, + message=self.msg_store.invalid_quantity(quantity), ) + response = self._set_quantity__process__set_qty_and_split( - picking, selected_line, quantity + picking, line, quantity ) - if response: + return response + + def process_with_existing_pack(self, picking_id, selected_line_id, quantity): + picking = self.env["stock.picking"].browse(picking_id) + selected_line = self.env["stock.move.line"].browse(selected_line_id) + if response := self._process(picking, selected_line, quantity): return response return self._response_for_select_dest_package(picking, selected_line) def process_with_new_pack(self, picking_id, selected_line_id, quantity): picking = self.env["stock.picking"].browse(picking_id) selected_line = self.env["stock.move.line"].browse(selected_line_id) - message = self._check_picking_processible(picking) - if message: - return self._response_for_set_quantity( - picking, selected_line, message=message - ) - response = self._set_quantity__process__set_qty_and_split( - picking, selected_line, quantity - ) - if response: + if response := self._process(picking, selected_line, quantity): return response picking._put_in_pack(selected_line) return self._response_for_set_destination(picking, selected_line) @@ -1471,15 +1469,7 @@ def process_with_new_pack(self, picking_id, selected_line_id, quantity): def process_without_pack(self, picking_id, selected_line_id, quantity): picking = self.env["stock.picking"].browse(picking_id) selected_line = self.env["stock.move.line"].browse(selected_line_id) - message = self._check_picking_processible(picking) - if message: - return self._response_for_set_quantity( - picking, selected_line, message=message - ) - response = self._set_quantity__process__set_qty_and_split( - picking, selected_line, quantity - ) - if response: + if response := self._process(picking, selected_line, quantity): return response return self._response_for_set_destination(picking, selected_line) diff --git a/shopfloor_reception/tests/test_set_quantity_action.py b/shopfloor_reception/tests/test_set_quantity_action.py index cceb476a37..8b93ea0ae2 100644 --- a/shopfloor_reception/tests/test_set_quantity_action.py +++ b/shopfloor_reception/tests/test_set_quantity_action.py @@ -218,3 +218,28 @@ def test_cancel_action_no_backorder(self): move_line.picking_id, "Cancelling the move line should not move it into a backorder", ) + + def test_set_quantity_actions_prevent_null_quantity(self): + for action in [ + "process_with_new_pack", + "process_without_pack", + "process_with_existing_pack", + ]: + response = self.service.dispatch( + action, + params={ + "picking_id": self.picking.id, + "selected_line_id": self.selected_move_line.id, + "quantity": 0.0, + }, + ) + self.assert_response( + response, + next_state="set_quantity", + data={ + "confirmation_required": None, + "picking": self.data.picking(self.picking), + "selected_move_line": self.data.move_lines(self.selected_move_line), + }, + message=self.msg_store.invalid_quantity(0.0), + )