From c569a0d41b33fee706d27ce09ab1ba309fe2b389 Mon Sep 17 00:00:00 2001 From: "Pedram (PEBR)" Date: Wed, 6 Dec 2023 16:29:26 +0100 Subject: [PATCH] [FIX] product_expiry: handle missing lot in FEFO removal strategy Before this commit, using the First Expiry First Out (FEFO) removal strategy led to a traceback error during stock move confirmation if a stock quant of a product was missing its lot. This issue arose because the removal strategy's sort key was based on `removal_date`, which is a field related to `lot_id`. To address this problem, the sorting logic has been modified. Now, it uses `removal_date` or `fields.datetime.max` as the sort key. opw-3625784 closes odoo/odoo#145274 Signed-off-by: Quentin Wolfs (quwo) --- addons/product_expiry/models/stock_quant.py | 2 +- addons/product_expiry/tests/test_stock_lot.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/addons/product_expiry/models/stock_quant.py b/addons/product_expiry/models/stock_quant.py index 69f1c40c3f7..e8c4ae001ff 100644 --- a/addons/product_expiry/models/stock_quant.py +++ b/addons/product_expiry/models/stock_quant.py @@ -19,5 +19,5 @@ class StockQuant(models.Model): def _get_removal_strategy_sort_key(self, removal_strategy): if removal_strategy == 'fefo': - return lambda q: (q.removal_date, q.in_date, q.id), False + return lambda q: (q.removal_date or fields.datetime.max, q.in_date, q.id), False return super()._get_removal_strategy_sort_key(removal_strategy) diff --git a/addons/product_expiry/tests/test_stock_lot.py b/addons/product_expiry/tests/test_stock_lot.py index 6a7c3ed42fe..e0fe55062e3 100644 --- a/addons/product_expiry/tests/test_stock_lot.py +++ b/addons/product_expiry/tests/test_stock_lot.py @@ -615,3 +615,39 @@ class TestStockLot(TestStockCommon): picking_out.action_confirm() picking_out.action_assign() self.assertEqual(picking_out.move_ids.state, 'assigned', 'Wrong state of move line.') + + def test_no_lot(self): + """ + Try to reserve a move that for an expirable product that has both quants with and without lot attached. + """ + # Set the removal strategy to 'First Expiry First Out' + fefo_strategy = self.env['product.removal'].search( + [('method', '=', 'fefo')]) + self.apple_product.categ_id.removal_strategy_id = fefo_strategy.id + + apple_lot = self.LotObj.create({ + 'name': 'LOT001', + 'product_id': self.apple_product.id, + 'company_id': self.env.company.id, + }) + + self.StockQuantObj.with_context(inventory_mode=True).create([{ + 'product_id': self.apple_product.id, + 'location_id': self.stock_location, + 'quantity': 100, + }, { + 'product_id': self.apple_product.id, + 'location_id': self.stock_location, + 'quantity': 100, + 'lot_id': apple_lot.id, + }]) + + with Form(self.PickingObj) as picking_form: + picking_form.picking_type_id = self.env.ref('stock.picking_type_out') + with picking_form.move_ids_without_package.new() as move: + move.product_id = self.apple_product + move.product_uom_qty = 10 + picking_out = picking_form.save() + + picking_out.action_assign() + self.assertEqual(picking_out.move_line_ids.lot_id, apple_lot)