diff --git a/shopfloor_reception/services/reception.py b/shopfloor_reception/services/reception.py index a79addf582f..05d670d8b24 100644 --- a/shopfloor_reception/services/reception.py +++ b/shopfloor_reception/services/reception.py @@ -287,6 +287,8 @@ def _scan_line__find_or_create_line(self, picking, move, qty_done=1): """ unassigned_lines = self.env["stock.move.line"] for line in move.move_line_ids: + if line.shopfloor_unloaded: + continue if line.shopfloor_user_id.id == self.env.uid: return self._scan_line__recover(picking, line, qty_done) elif not line.shopfloor_user_id: @@ -333,7 +335,7 @@ def _select_line__filter_lines_by_packaging__return(self, lines, packaging): return_line = fields.first( lines.filtered( lambda l: not l.package_id.product_packaging_id - and not l.result_package_id + and not l.shopfloor_unloaded and l.shopfloor_user_id.id in (False, self.env.uid) ) ) @@ -350,7 +352,7 @@ def _select_line__filter_lines_by_packaging(self, lines, packaging): return fields.first( lines.filtered( lambda l: l.package_id.product_packaging_id == packaging - and not l.result_package_id + and not l.shopfloor_unloaded and l.shopfloor_user_id.id in [False, self.env.uid] ) ) @@ -544,9 +546,12 @@ def _scan_line__by_lot(self, picking, lot): """ lines = picking.move_line_ids.filtered( lambda l: ( - lot == l.lot_id - or (lot.name == l.lot_name and lot.product_id == l.product_id) - and not l.result_package_id + ( + lot == l.lot_id + or (lot.name == l.lot_name and lot.product_id == l.product_id) + ) + and not l.shopfloor_unloaded + and l.shopfloor_user_id.id in (False, self.env.uid) ) ) if not lines: @@ -568,7 +573,9 @@ def _scan_line__by_lot(self, picking, lot): def _scan_line__fallback(self, picking, barcode): # We might have lines with no lot, but with a lot_name. lines = picking.move_line_ids.filtered( - lambda l: l.lot_name == barcode and not l.result_package_id + lambda l: l.lot_name == barcode + and not l.shopfloor_unloaded + and l.shopfloor_user_id.id in (False, self.env.uid) ) if not lines: return self._response_for_select_move( @@ -1380,7 +1387,11 @@ def process_without_pack(self, picking_id, selected_line_id, quantity): return self._response_for_set_destination(picking, selected_line) def _post_line(self, selected_line): + """ + Called when the product is unloaded at destination. + """ selected_line.reserved_uom_qty = selected_line.qty_done + selected_line.shopfloor_unloaded = True if ( selected_line.picking_id.is_shopfloor_created and self.work.menu.allow_return diff --git a/shopfloor_reception/tests/test_select_move.py b/shopfloor_reception/tests/test_select_move.py index a184443e0a4..ad0ded79d9e 100644 --- a/shopfloor_reception/tests/test_select_move.py +++ b/shopfloor_reception/tests/test_select_move.py @@ -45,6 +45,93 @@ def test_scan_product(self): }, ) + def test_scan_product_partial(self): + # Scan a line + # Set a partial quantity done + # Try to scan the product again + # The selected line should be the other one + picking = self._create_picking() + lot = self._create_lot() + self.assertFalse(picking.printed) + selected_move_line = picking.move_line_ids.filtered( + lambda l: l.product_id == self.product_a + ) + + # Activate INPUT location + selected_move_line.location_dest_id.sudo().active = True + + selected_move_line.lot_id = lot + response = self.service.dispatch( + "scan_line", + params={"picking_id": picking.id, "barcode": lot.name}, + ) + data = self.data.picking(picking) + + self.assertTrue(selected_move_line.picking_id.printed) + self.assert_response( + response, + next_state="set_quantity", + data={ + "picking": data, + "selected_move_line": self.data.move_lines(selected_move_line), + "confirmation_required": None, + }, + ) + + selected_move_line.shopfloor_user_id = self.env.uid + response = self.service.dispatch( + "set_quantity", + params={ + "picking_id": picking.id, + "selected_line_id": selected_move_line.id, + "quantity": 5.0, + }, + ) + + response = self.service.dispatch( + "process_without_pack", + params={ + "picking_id": picking.id, + "selected_line_id": selected_move_line.id, + "quantity": 5.0, + }, + ) + data = self.data.picking(picking) + self.assert_response( + response, + next_state="set_destination", + data={ + "picking": data, + "selected_move_line": self.data.move_lines(selected_move_line), + }, + ) + + response = self.service.dispatch( + "set_destination", + params={ + "picking_id": picking.id, + "selected_line_id": selected_move_line.id, + "location_name": "INPUT", + }, + ) + self.assert_response( + response, + next_state="select_move", + data=self._data_for_select_move(picking), + ) + lines = picking.move_line_ids.filtered(lambda l: l.product_id == self.product_a) + self.assertEqual(2, len(lines)) + previous_line = selected_move_line + + response = self.service.dispatch( + "scan_line", + params={"picking_id": picking.id, "barcode": lot.name}, + ) + + self.assertNotEqual( + previous_line.id, response["data"]["set_lot"]["selected_move_line"][0]["id"] + ) + def test_scan_packaging(self): picking = self._create_picking() self._add_package(picking) @@ -93,6 +180,36 @@ def test_scan_lot(self): }, ) + def test_scan_lot_concurrent(self): + """ + If 2 operators work on the same lot, the second operator + should not steal the move line of the first. + """ + picking = self._create_picking() + lot = self._create_lot() + + service_u1 = self.service + res_u1 = service_u1.dispatch( + "scan_line", + params={ + "picking_id": picking.id, + "barcode": lot.name, + }, + ) + # User 2 starts working on the same move + service_u2 = self._get_service_for_user(self.shopfloor_manager) + res_u2 = service_u2.dispatch( + "scan_line", + params={ + "picking_id": picking.id, + "barcode": lot.name, + }, + ) + self.assertNotEqual( + res_u1["data"]["set_lot"]["selected_move_line"][0]["id"], + res_u2["data"]["set_lot"]["selected_move_line"][0]["id"], + ) + def test_scan_not_tracked_product(self): self.product_a.tracking = "none" picking = self._create_picking() diff --git a/shopfloor_reception/tests/test_set_destination.py b/shopfloor_reception/tests/test_set_destination.py index 9f962c9fdb4..b5b3909aba5 100644 --- a/shopfloor_reception/tests/test_set_destination.py +++ b/shopfloor_reception/tests/test_set_destination.py @@ -225,7 +225,7 @@ def test_auto_posting_full_two_lines(self): # One move remaining in the picking, for product b, still to be processed self.assertEqual(picking.move_ids.product_id, self.product_b) - def test_auto_posting_concurent_work(self): + def test_auto_posting_concurrent_work(self): """Check 2 users working on the same move. With the auto post line option On.