Skip to content

Commit

Permalink
[FIX] mrp: fix qty_available for kit with sub-kits
Browse files Browse the repository at this point in the history
On this situation:
* P1 consumable product, kit, with components P2 and P3 in its BoM using
  1 of each
* P2 comsumable product, kit, with component P3 using 1 in the BoM
* P3 storable product, 10 units in stock

Before:
The qyt_available for P1 is 10. That's incorrect: to assemble one P1 we
need precisely two of P3s, one for P2 BoM then an extra for P1 BoM.

After:
The qyt_available for P1 is 5.

closes odoo#122939

X-original-commit: b325543
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Alvaro Fuentes Suarez (afu) <afu@odoo.com>
  • Loading branch information
aj-fuentes committed May 31, 2023
1 parent 184f735 commit 854211c
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 9 deletions.
26 changes: 17 additions & 9 deletions addons/mrp/models/product.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import collections
from datetime import timedelta
from itertools import groupby
import operator as py_operator
Expand Down Expand Up @@ -210,20 +211,27 @@ def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False
# compute kit quantities
for product in bom_kits:
bom_sub_lines = bom_sub_lines_per_kit[product]
# group lines by component
bom_sub_lines_grouped = collections.defaultdict(list)
for info in bom_sub_lines:
bom_sub_lines_grouped[info[0].product_id].append(info)
ratios_virtual_available = []
ratios_qty_available = []
ratios_incoming_qty = []
ratios_outgoing_qty = []
ratios_free_qty = []
for bom_line, bom_line_data in bom_sub_lines:
component = bom_line.product_id.with_context(mrp_compute_quantities=qties).with_prefetch(prefetch_component_ids)
if component.type != 'product' or float_is_zero(bom_line_data['qty'], precision_rounding=bom_line.product_uom_id.rounding):
# As BoMs allow components with 0 qty, a.k.a. optionnal components, we simply skip those
# to avoid a division by zero. The same logic is applied to non-storable products as those
# products have 0 qty available.
continue
uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty']
qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, round=False, raise_if_failure=False)

for component, bom_sub_lines in bom_sub_lines_grouped.items():
component = component.with_context(mrp_compute_quantities=qties).with_prefetch(prefetch_component_ids)
qty_per_kit = 0
for bom_line, bom_line_data in bom_sub_lines:
if component.type != 'product' or float_is_zero(bom_line_data['qty'], precision_rounding=bom_line.product_uom_id.rounding):
# As BoMs allow components with 0 qty, a.k.a. optionnal components, we simply skip those
# to avoid a division by zero. The same logic is applied to non-storable products as those
# products have 0 qty available.
continue
uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty']
qty_per_kit += bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, round=False, raise_if_failure=False)
if not qty_per_kit:
continue
rounding = component.uom_id.rounding
Expand Down
29 changes: 29 additions & 0 deletions addons/mrp/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,32 @@ def setUpClass(cls):
'tracking': 'none',
'categ_id': cls.env.ref('product.product_category_all').id,
})

@classmethod
def make_prods(cls, n):
return [
cls.env["product.product"].create(
{"name": f"p{k + 1}", "type": "product"}
)
for k in range(n)
]

@classmethod
def make_bom(cls, p, *cs):
return cls.env["mrp.bom"].create(
{
"product_tmpl_id": p.product_tmpl_id.id,
"product_id": p.id,
"product_qty": 1,
"type": "phantom",
"product_uom_id": cls.uom_unit.id,
"bom_line_ids": [
(0, 0, {
"product_id": c.id,
"product_qty": 1,
"product_uom_id": cls.uom_unit.id
})
for c in cs
],
}
)
12 changes: 12 additions & 0 deletions addons/mrp/tests/test_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,3 +1142,15 @@ def test_replenishment(self):
self.assertEqual(orderpoint.route_id.id, manufacturing_route_id)
self.assertEqual(orderpoint.qty_multiple, 2000.0)
self.assertEqual(orderpoint.qty_to_order, 4000.0)

def test_bom_kit_with_sub_kit(self):
p1, p2, p3, p4 = self.make_prods(4)
self.make_bom(p1, p2, p3)
self.make_bom(p2, p3, p4)

loc = self.env.ref("stock.stock_location_stock")
self.env["stock.quant"]._update_available_quantity(p3, loc, 10)
self.env["stock.quant"]._update_available_quantity(p4, loc, 10)
self.assertEqual(p1.qty_available, 5.0)
self.assertEqual(p2.qty_available, 10.0)
self.assertEqual(p3.qty_available, 10.0)

0 comments on commit 854211c

Please sign in to comment.