From e402cd496fa20e03ad0344101de72da6ba71c64c Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Mon, 8 Feb 2021 10:44:24 +0000 Subject: [PATCH] [FIX] mrp: `qty_available` computation of a kit and its component When `qty_available` was being called for a kit and its components at the same time, e.g.: - Kit product - Product 1 - Product 2 The kit product available qty was always computed to `0.0`, because when computing its component available quantity, they were computed to `0.0` because they were marked as `protected` by the ORM when calling the compute method for these records at the first call. When the `qty_available` compute method for the kit then called recursively the `qty_available` compute method for the component, as the component record were marked as protected, the ORM filled their value with the Falsy value `0.0`. This issue occured particularly in the upgrade integrity unit test which makes sure the available quantities of the products remain unchanged after upgrade. This test computes the available quantities for a bunch of products at the same time, and its therefore likely a kit product gets its available quantity computed as the same time than its components. upg-3185 upg-4215 upg-4228 upg-5558 upg-5745 upg-5856 upg-6012 upg-6064 upg-6076 upg-6306 upg-6514 upg-6588 upg-6729 closes odoo/odoo#65758 X-original-commit: 8e152a567a6003dae4abb3b537b963eec4142d6d Signed-off-by: Arnold Moyaux Signed-off-by: Denis Ledoux (dle) --- addons/mrp/models/product.py | 17 ++++++++++++----- addons/mrp/tests/test_bom.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py index aca579e9272..d7a5c12f496 100644 --- a/addons/mrp/models/product.py +++ b/addons/mrp/models/product.py @@ -150,11 +150,18 @@ class ProductProduct(models.Model): qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, raise_if_failure=False) if not qty_per_kit: continue - ratios_virtual_available.append(component.virtual_available / qty_per_kit) - ratios_qty_available.append(component.qty_available / qty_per_kit) - ratios_incoming_qty.append(component.incoming_qty / qty_per_kit) - ratios_outgoing_qty.append(component.outgoing_qty / qty_per_kit) - ratios_free_qty.append(component.free_qty / qty_per_kit) + component_res = res.get(component.id, { + "virtual_available": component.virtual_available, + "qty_available": component.qty_available, + "incoming_qty": component.incoming_qty, + "outgoing_qty": component.outgoing_qty, + "free_qty": component.free_qty, + }) + ratios_virtual_available.append(component_res["virtual_available"] / qty_per_kit) + ratios_qty_available.append(component_res["qty_available"] / qty_per_kit) + ratios_incoming_qty.append(component_res["incoming_qty"] / qty_per_kit) + ratios_outgoing_qty.append(component_res["outgoing_qty"] / qty_per_kit) + ratios_free_qty.append(component_res["free_qty"] / qty_per_kit) if bom_sub_lines and ratios_virtual_available: # Guard against all cnsumable bom: at least one ratio should be present. res[product.id] = { 'virtual_available': min(ratios_virtual_available) // 1, diff --git a/addons/mrp/tests/test_bom.py b/addons/mrp/tests/test_bom.py index fc241c0592b..fc16d68a4c6 100644 --- a/addons/mrp/tests/test_bom.py +++ b/addons/mrp/tests/test_bom.py @@ -262,6 +262,38 @@ class TestBoM(TestMrpCommon): # Check consumed materials in production order. self.assertEqual(mrp_order.move_raw_ids.product_id, consumed_products) + def test_13_bom_kit_qty(self): + self.env['mrp.bom'].create({ + 'product_id': self.product_7_3.id, + 'product_tmpl_id': self.product_7_template.id, + 'product_uom_id': self.uom_unit.id, + 'product_qty': 4.0, + 'type': 'phantom', + 'bom_line_ids': [ + (0, 0, { + 'product_id': self.product_2.id, + 'product_qty': 2, + }), + (0, 0, { + 'product_id': self.product_3.id, + 'product_qty': 2, + }) + ] + }) + location = self.env.ref('stock.stock_location_stock') + self.env['stock.quant']._update_available_quantity(self.product_2, location, 4.0) + self.env['stock.quant']._update_available_quantity(self.product_3, location, 8.0) + # Force the kit product available qty to be computed at the same time than its component quantities + # Because `qty_available` of a bom kit "recurse" on `qty_available` of its component, + # and this is a tricky thing for the ORM: + # `qty_available` gets called for `product_7_3`, `product_2` and `product_3` + # which then recurse on calling `qty_available` for `product_2` and `product_3` to compute the quantity of + # the kit `product_7_3`. `product_2` and `product_3` gets protected at the first call of the compute method, + # ending the recurse call to not call the compute method and just left the Falsy value `0.0` + # for the components available qty. + kit_product_qty, _, _ = (self.product_7_3 + self.product_2 + self.product_3).mapped("qty_available") + self.assertEqual(kit_product_qty, 2) + def test_20_bom_report(self): """ Simulate a crumble receipt with mrp and open the bom structure report and check that data insde are correct.