[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#122939 X-original-commit: b32554322d4c26665bdcb29fd58bb95f12faa20e Signed-off-by: William Henrotin (whe) <whe@odoo.com> Signed-off-by: Alvaro Fuentes Suarez (afu) <afu@odoo.com>
This commit is contained in:
@@ -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
|
||||
@@ -210,20 +211,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
|
||||
|
||||
@@ -230,3 +230,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
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1142,3 +1142,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)
|
||||
|
||||
Reference in New Issue
Block a user