diff --git a/shopfloor_reception/services/reception.py b/shopfloor_reception/services/reception.py index cceccb12588..c5605b08b66 100644 --- a/shopfloor_reception/services/reception.py +++ b/shopfloor_reception/services/reception.py @@ -20,6 +20,10 @@ UTC = timezone.utc +class RollbackTransaction(Exception): + """Internal exception used to trigger a savepoint rollback without bubbling up.""" + + class Reception(Component): """ Methods for the Reception Process @@ -741,16 +745,35 @@ def _set_quantity__by_new_package( selected_line.result_package_id = package return self._response_for_set_destination(picking, selected_line) - def _set_quantity__by_location(self, picking, selected_line, location): - if not self.is_dest_location_valid(selected_line.move_id, location): - message = self.msg_store.dest_location_not_allowed() - return self._response_for_set_quantity( - picking, selected_line, message=message + def _set_quantity__by_location(self, picking, selected_line, location, quantity=1): + err_message = None + try: + with self.env.cr.savepoint(): + self.process_without_pack( + picking.id, + selected_line.id, + quantity, + is_over_reception_confirmed=False, + ) + response = self.set_destination( + picking.id, + selected_line.id, + location.name, + confirmation=location.name, + ) + # If location wasn't applied, trigger rollback + if selected_line.location_dest_id != location: + err_message = response.get("message") + raise RollbackTransaction() + except RollbackTransaction: + # TODO: this is necessary because we hit `set_quantity__assign_quantity` + # before entering this function + selected_line.qty_done = 0 + + response = self._response_for_set_quantity( + picking, selected_line, message=err_message ) - # process without pack, set destination location, and go back to - # `select_move` - selected_line.location_dest_id = location - return self._response_for_select_move(picking) + return response def _set_quantity__by_lot(self, picking, selected_line, lot): if selected_line.lot_id.name == lot.name or selected_line.lot_name == lot.name: @@ -821,7 +844,7 @@ def _response_for_set_lot( self, picking, line, message=None, lot_name=None, lot_expiration_date=None, **kw ): # ↓ In case "lot_name" is pre-filled on the line in odoo, pre-fill - # shpofloor screen + # shopfloor screen if lot_name and not lot_expiration_date and not message: lot = ( self._actions_for("search") @@ -1399,19 +1422,18 @@ def _set_quantity__get_handlers_by_type(self): "product": self._set_quantity__by_product, "packaging": self._set_quantity__by_packaging, "package": self._set_quantity__by_package, - "location": self._set_quantity__by_location, "lot": self._set_quantity__by_lot, } def _set_quantity__by_barcode( - self, picking, selected_line, barcode, confirmation=None + self, picking, selected_line, barcode, confirmation=None, quantity=1 ): handlers_by_type = self._set_quantity__get_handlers_by_type() search = self._actions_for("search").for_products(selected_line.product_id) try: search_result = search.find( barcode, - handlers_by_type.keys(), + [*handlers_by_type.keys(), "location"], ) except SearchInvalidProduct as e: return self._response_for_set_quantity( @@ -1423,6 +1445,12 @@ def _set_quantity__by_barcode( if handler: return handler(picking, selected_line, search_result.record) + # We scanned a location -> skip the "set destination" screen + if search_result.type == "location": + return self._set_quantity__by_location( + picking, selected_line, search_result.record, quantity + ) + # Nothing found, ask user if we should create a new pack for the scanned # barcode return self._set_quantity__by_new_package( @@ -1489,7 +1517,11 @@ def set_quantity( # Then, we add the qty of whatever was scanned # on top of the qty of the picker. return self._set_quantity__by_barcode( - picking, selected_line, barcode, confirmation + picking, + selected_line, + barcode, + confirmation, + quantity if quantity else 1, ) return self._response_for_set_quantity(picking, selected_line) diff --git a/shopfloor_reception/tests/test_set_quantity.py b/shopfloor_reception/tests/test_set_quantity.py index f3022092875..ae9d62d805f 100644 --- a/shopfloor_reception/tests/test_set_quantity.py +++ b/shopfloor_reception/tests/test_set_quantity.py @@ -323,8 +323,11 @@ def test_scan_location_child_of_dest_location(self): "picking_id": picking.id, "selected_line_id": selected_move_line.id, "barcode": self.dispatch_location.barcode, + "quantity": 4, }, ) + self.assertTrue(selected_move_line.shopfloor_unloaded) + self.assertEqual(selected_move_line.qty_done, 4) self.assertEqual(selected_move_line.location_dest_id, self.dispatch_location) self.assert_response( response, next_state="select_move", data=self._data_for_select_move(picking) @@ -342,18 +345,21 @@ def test_scan_location_not_child_of_dest_location(self): "picking_id": picking.id, "selected_line_id": selected_move_line.id, "barcode": self.packing_location.barcode, + "quantity": 4, }, ) - data = self.data.picking(picking) + + self.assertFalse(selected_move_line.shopfloor_unloaded) + self.assertEqual(selected_move_line.qty_done, 0) self.assert_response( response, next_state="set_quantity", data={ - "picking": data, + "picking": self.data.picking(picking), "selected_move_line": self.data.move_lines(selected_move_line), "confirmation_required": None, }, - message={"message_type": "error", "body": "You cannot place it here"}, + message=self.msg_store.dest_location_not_allowed(), ) def test_scan_location_view_usage(self): @@ -381,7 +387,7 @@ def test_scan_location_view_usage(self): "selected_move_line": self.data.move_lines(selected_move_line), "confirmation_required": None, }, - message={"message_type": "error", "body": "You cannot place it here"}, + message=self.msg_store.dest_location_not_allowed(), ) def test_scan_new_package(self): diff --git a/shopfloor_reception_putinpack_restriction/services/reception.py b/shopfloor_reception_putinpack_restriction/services/reception.py index d3eef874ce2..d83313111d0 100644 --- a/shopfloor_reception_putinpack_restriction/services/reception.py +++ b/shopfloor_reception_putinpack_restriction/services/reception.py @@ -18,7 +18,9 @@ def _check_picking_putinpack_restriction(self, picking, with_pack: bool): if picking.put_in_pack_restriction == "with_package" and not with_pack: return self.msg_store.package_required_for_operation(picking) - def process_with_existing_pack(self, picking_id, selected_line_id, quantity): + def process_with_existing_pack( + self, picking_id, selected_line_id, quantity, is_over_reception_confirmed=False + ): picking = self.env["stock.picking"].browse(picking_id) selected_line = self.env["stock.move.line"].browse(selected_line_id) @@ -28,11 +30,15 @@ def process_with_existing_pack(self, picking_id, selected_line_id, quantity): return self._response_for_set_quantity( picking, selected_line, message=message ) - res = super().process_with_existing_pack(picking_id, selected_line_id, quantity) + res = super().process_with_existing_pack( + picking_id, selected_line_id, quantity, is_over_reception_confirmed + ) return res - def process_with_new_pack(self, picking_id, selected_line_id, quantity): + def process_with_new_pack( + self, picking_id, selected_line_id, quantity, is_over_reception_confirmed=False + ): picking = self.env["stock.picking"].browse(picking_id) selected_line = self.env["stock.move.line"].browse(selected_line_id) @@ -42,11 +48,15 @@ def process_with_new_pack(self, picking_id, selected_line_id, quantity): return self._response_for_set_quantity( picking, selected_line, message=message ) - res = super().process_with_new_pack(picking_id, selected_line_id, quantity) + res = super().process_with_new_pack( + picking_id, selected_line_id, quantity, is_over_reception_confirmed + ) return res - def process_without_pack(self, picking_id, selected_line_id, quantity): + def process_without_pack( + self, picking_id, selected_line_id, quantity, is_over_reception_confirmed=False + ): picking = self.env["stock.picking"].browse(picking_id) selected_line = self.env["stock.move.line"].browse(selected_line_id) @@ -56,7 +66,9 @@ def process_without_pack(self, picking_id, selected_line_id, quantity): return self._response_for_set_quantity( picking, selected_line, message=message ) - res = super().process_without_pack(picking_id, selected_line_id, quantity) + res = super().process_without_pack( + picking_id, selected_line_id, quantity, is_over_reception_confirmed + ) return res