From 4c838357631eb6065b21437f15d88d1b680e519b Mon Sep 17 00:00:00 2001 From: Kev-Roche Date: Thu, 4 Apr 2024 16:17:50 +0200 Subject: [PATCH 1/3] [16.0][IMP] propagation of restrict_lot changes --- stock_restrict_lot/models/stock_move.py | 36 +++++++++ stock_restrict_lot/tests/test_restrict_lot.py | 80 +++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/stock_restrict_lot/models/stock_move.py b/stock_restrict_lot/models/stock_move.py index 1e1bd9ac6f10..f8616684a00b 100644 --- a/stock_restrict_lot/models/stock_move.py +++ b/stock_restrict_lot/models/stock_move.py @@ -1,4 +1,5 @@ from odoo import _, api, exceptions, fields, models +from odoo.exceptions import ValidationError class StockMove(models.Model): @@ -88,3 +89,38 @@ def _split(self, qty, restrict_partner_id=False): if vals_list and self.restrict_lot_id: vals_list[0]["restrict_lot_id"] = self.restrict_lot_id.id return vals_list + + def get_all_dest_moves(self): + res = self.move_dest_ids + moves_to_search = self.move_dest_ids + while moves_to_search: + new_dest_ids = moves_to_search.move_dest_ids + moves_to_search = new_dest_ids - res + res |= new_dest_ids + return res + + def get_all_orig_moves(self): + res = self.move_orig_ids + moves_to_search = self.move_orig_ids + while moves_to_search: + new_orig_ids = moves_to_search.move_orig_ids + moves_to_search = new_orig_ids - res + res |= new_orig_ids + return res + + def write(self, vals): + if "restrict_lot_id" not in vals: + return super().write(vals) + else: + restrict_lot_id = vals.pop("restrict_lot_id") + chained_moves = self | self.get_all_dest_moves() | self.get_all_orig_moves() + if any([sm.state == "done" for sm in chained_moves]): + raise ValidationError( + _( + "You can't modify the Lot/Serial number" + " because at least one move in the chain has " + "already been done." + ) + ) + super(StockMove, chained_moves).write({"restrict_lot_id": restrict_lot_id}) + return super().write(vals) diff --git a/stock_restrict_lot/tests/test_restrict_lot.py b/stock_restrict_lot/tests/test_restrict_lot.py index e8c3bac3f6a6..4861d578e6f8 100644 --- a/stock_restrict_lot/tests/test_restrict_lot.py +++ b/stock_restrict_lot/tests/test_restrict_lot.py @@ -1,3 +1,4 @@ +from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase @@ -19,6 +20,50 @@ def setUpClass(cls): } ) + def _create_move_with_lot(self): + move = self.env["stock.move"].create( + { + "product_id": self.product.id, + "location_id": self.output_loc.id, + "location_dest_id": self.customer_loc.id, + "product_uom_qty": 1, + "product_uom": self.product.uom_id.id, + "name": "test", + "procure_method": "make_to_order", + "warehouse_id": self.warehouse.id, + "route_ids": [(6, 0, self.warehouse.delivery_route_id.ids)], + "restrict_lot_id": self.lot.id, + } + ) + move._action_confirm() + move._action_assign() + new_lot = self.env["stock.lot"].create( + { + "name": "lot2", + "product_id": self.product.id, + "company_id": self.warehouse.company_id.id, + } + ) + return move, new_lot + + def _create_move_dest(self): + return self.env["stock.move"].create( + { + "product_id": self.product.id, + "location_id": self.customer_loc.id, + "product_uom_qty": 1, + "product_uom": self.product.uom_id.id, + "picking_type_id": self.warehouse.out_type_id.id, + "location_dest_id": self.output_loc.id, + "name": "test", + "procure_method": "make_to_order", + "warehouse_id": self.warehouse.id, + "route_ids": [(6, 0, self.warehouse.delivery_route_id.ids)], + "state": "waiting", + "restrict_lot_id": self.lot.id, + } + ) + def test_00_move_restrict_lot_propagation(self): move = self.env["stock.move"].create( { @@ -224,3 +269,38 @@ def assert_move_line_per_lot_and_location( assert_move_line_per_lot_and_location( pick.move_line_ids_without_package, lot2, location_2, 25 ) + + def test_restrict_lot_propagation_dest_moves(self): + move, new_lot = self._create_move_with_lot() + move_dest = self._create_move_dest() + move.move_dest_ids = [(4, move_dest.id)] + self.assertEqual(move_dest.restrict_lot_id, self.lot) + move.restrict_lot_id = new_lot.id + self.assertEqual(move_dest.restrict_lot_id, new_lot) + + def test_restrict_lot_propagation_origin_moves(self): + move, new_lot = self._create_move_with_lot() + orig_move = move.move_orig_ids + self.assertEqual(orig_move.restrict_lot_id, self.lot) + move.restrict_lot_id = new_lot.id + self.assertEqual(orig_move.restrict_lot_id, new_lot) + + def test_restrict_lot_propagation_origin_and_dest_moves(self): + move, new_lot = self._create_move_with_lot() + move_dest = self._create_move_dest() + move.move_dest_ids = [(4, move_dest.id)] + orig_move = move.move_orig_ids + self.assertEqual(move_dest.restrict_lot_id, self.lot) + self.assertEqual(orig_move.restrict_lot_id, self.lot) + move.restrict_lot_id = new_lot.id + self.assertEqual(orig_move.restrict_lot_id, new_lot) + self.assertEqual(move_dest.restrict_lot_id, new_lot) + + def test_restrict_lot_no_propagation_error(self): + move, new_lot = self._create_move_with_lot() + orig_move = move.move_orig_ids + orig_move.state = "done" + self.assertEqual(orig_move.state, "done") + with self.assertRaises(ValidationError) as m: + move.restrict_lot_id = new_lot.id + self.assertIn("You can't modify the Lot/Serial number", m.exception.args[0]) From ae5c38dcf05fb4f2f17699af779241eec8494816 Mon Sep 17 00:00:00 2001 From: Guillaume MASSON Date: Wed, 17 Apr 2024 19:23:34 +0200 Subject: [PATCH 2/3] [IMP] stock_restrict_lot : update restrict lot at stock move creation if in a chain and don't block an update if a move has already been done in the chain with this particular lot --- stock_restrict_lot/models/stock_move.py | 28 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/stock_restrict_lot/models/stock_move.py b/stock_restrict_lot/models/stock_move.py index f8616684a00b..2fb25b7f4cd0 100644 --- a/stock_restrict_lot/models/stock_move.py +++ b/stock_restrict_lot/models/stock_move.py @@ -5,7 +5,7 @@ class StockMove(models.Model): _inherit = "stock.move" - # seems better to not copy this field except when a move is splitted, because a move + # seems better to not copy this field except when a move is split, because a move # can be copied in multiple different occasions and could even be copied with a # different product... restrict_lot_id = fields.Many2one("stock.lot", string="Restrict Lot", copy=False) @@ -113,14 +113,32 @@ def write(self, vals): return super().write(vals) else: restrict_lot_id = vals.pop("restrict_lot_id") + restrict_lot = self.env["stock.lot"].browse(restrict_lot_id) chained_moves = self | self.get_all_dest_moves() | self.get_all_orig_moves() - if any([sm.state == "done" for sm in chained_moves]): + if any( + [ + sm.state == "done" and sm.lot_ids != restrict_lot + for sm in chained_moves + ] + ): raise ValidationError( _( - "You can't modify the Lot/Serial number" - " because at least one move in the chain has " - "already been done." + "You can't modify the Lot/Serial number " + "because at least one move in the chain has " + "already been done with another Lot/Serial number." ) ) super(StockMove, chained_moves).write({"restrict_lot_id": restrict_lot_id}) return super().write(vals) + + @api.model_create_multi + def create(self, vals_list): + res = super().create(vals_list) + for move in res: + if not move.restrict_lot_id: + chained_moves = ( + move | move.get_all_dest_moves() | move.get_all_orig_moves() + ) + if chained_moves.restrict_lot_id: + move.restrict_lot_id = chained_moves.restrict_lot_id[0] + return res From ccc5c057aa936ff43c1cf7f097c1bd385f60e8fe Mon Sep 17 00:00:00 2001 From: emiliesoutiras Date: Thu, 13 Jun 2024 17:23:39 +0200 Subject: [PATCH 3/3] try to chain a dest mode without lot_id --- stock_restrict_lot/models/stock_move.py | 20 +++++++++++++++- stock_restrict_lot/tests/test_restrict_lot.py | 24 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/stock_restrict_lot/models/stock_move.py b/stock_restrict_lot/models/stock_move.py index 2fb25b7f4cd0..ba052986d7c3 100644 --- a/stock_restrict_lot/models/stock_move.py +++ b/stock_restrict_lot/models/stock_move.py @@ -110,7 +110,25 @@ def get_all_orig_moves(self): def write(self, vals): if "restrict_lot_id" not in vals: - return super().write(vals) + res = super().write(vals) + if "move_dest_ids" in vals or "move_orig_ids" in vals: + for move in self: + chained_moves = ( + move | move.get_all_dest_moves() | move.get_all_orig_moves() + ) + if not move.restrict_lot_id and move.state not in ["done", "cancel"]: + # update restrict_lot_id on current move from chained_moves + if chained_moves.restrict_lot_id: + move.restrict_lot_id = chained_moves.restrict_lot_id[0] + else: + # update chained_moves + to_update_move = chained_moves.filtered( + lambda sm: sm.state not in ['done', 'cancel'] and + sm.restrict_lot_id != move.restrict_lot_id + ) + to_update_move.restrict_lot_id = move.restrict_lot_id + else: + return res else: restrict_lot_id = vals.pop("restrict_lot_id") restrict_lot = self.env["stock.lot"].browse(restrict_lot_id) diff --git a/stock_restrict_lot/tests/test_restrict_lot.py b/stock_restrict_lot/tests/test_restrict_lot.py index 4861d578e6f8..cb8e468ca717 100644 --- a/stock_restrict_lot/tests/test_restrict_lot.py +++ b/stock_restrict_lot/tests/test_restrict_lot.py @@ -46,7 +46,7 @@ def _create_move_with_lot(self): ) return move, new_lot - def _create_move_dest(self): + def _create_move_dest(self, with_lot = True): return self.env["stock.move"].create( { "product_id": self.product.id, @@ -60,7 +60,7 @@ def _create_move_dest(self): "warehouse_id": self.warehouse.id, "route_ids": [(6, 0, self.warehouse.delivery_route_id.ids)], "state": "waiting", - "restrict_lot_id": self.lot.id, + "restrict_lot_id": self.lot.id if with_lot else False, } ) @@ -278,6 +278,18 @@ def test_restrict_lot_propagation_dest_moves(self): move.restrict_lot_id = new_lot.id self.assertEqual(move_dest.restrict_lot_id, new_lot) + def test_restrict_lot_propagation_dest_moves_without_lot(self): + move, new_lot = self._create_move_with_lot() + move_dest = self._create_move_dest(False) + move.move_dest_ids = [(4, move_dest.id)] + chained_moves = ( + move | move.get_all_dest_moves() | move.get_all_orig_moves() + ) + self.assertEqual(chained_moves.restrict_lot_id, self.lot) + self.assertEqual(move_dest.restrict_lot_id, self.lot) + move.restrict_lot_id = new_lot.id + self.assertEqual(move_dest.restrict_lot_id, new_lot) + def test_restrict_lot_propagation_origin_moves(self): move, new_lot = self._create_move_with_lot() orig_move = move.move_orig_ids @@ -285,6 +297,14 @@ def test_restrict_lot_propagation_origin_moves(self): move.restrict_lot_id = new_lot.id self.assertEqual(orig_move.restrict_lot_id, new_lot) + def test_restrict_lot_propagation_orig_moves_without_lot(self): + move, new_lot = self._create_move_with_lot() + move_dest = self._create_move_dest(False) + move_dest.move_orig_ids = [(4, move.id)] + self.assertEqual(move_dest.restrict_lot_id, self.lot) + move.restrict_lot_id = new_lot.id + self.assertEqual(move_dest.restrict_lot_id, new_lot) + def test_restrict_lot_propagation_origin_and_dest_moves(self): move, new_lot = self._create_move_with_lot() move_dest = self._create_move_dest()