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.