Skip to content
Snippets Groups Projects
Commit 8e152a56 authored by Denis Ledoux's avatar Denis Ledoux
Browse files

[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#65701

Signed-off-by: default avatarArnold Moyaux <amoyaux@users.noreply.github.com>
parent d63734eb
No related branches found
No related tags found
No related merge requests found
......@@ -135,11 +135,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,
......
......@@ -246,6 +246,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.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment