Skip to content
Snippets Groups Projects
Commit b3255432 authored by Alvaro Fuentes's avatar Alvaro Fuentes
Browse files

[FIX] mrp: fix qty_available for kit with sub-kits


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/odoo#121660

X-original-commit: d02851d5
Signed-off-by: default avatarWilliam Henrotin (whe) <whe@odoo.com>
parent 86a99af1
No related branches found
No related tags found
No related merge requests found
# -*- 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
......@@ -213,20 +214,27 @@ class ProductProduct(models.Model):
# 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
......
......@@ -208,3 +208,32 @@ class TestMrpCommon(common2.TestStockCommon):
'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
],
}
)
......@@ -1082,3 +1082,15 @@ class TestBoM(TestMrpCommon):
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% 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