[FIX] mrp: use correct UoM for each component in MO
In a Manufacturing Order, the components' quantities are rounded using
the rounding precision of the produced product's UoM. This leads to
incorrect values.
To reproduce the error:
1. In Settings, enable "Units of Measure"
2. In UoM, edit Units:
- Rounding Precision: 1
3. Create two products P_finished and P_compo
- P_compo's Product Type: Consumable
- P_compo's UoM: L
- P_finished's UoM: Units
4. Create a Bill of Materials
- Product: P_finished
- 1 Component:
- Product: P_compo
- Quantity: 0.2
- UoM: L
5. Create a Manufacturing Order:
- Product: P_finished
6. Confirm, Mark as Done
Error: Qty to consumes became 0 and consumed qty is 0. Both values
should be 0.2L, but they have been rounded using the rounding precision
of Units
OPW-2529462
closes odoo/odoo#71293
X-original-commit: aff3a2e06801dfb7a58df5180fb4ab5487b27f02
Signed-off-by: Steve Van Essche <svs-odoo@users.noreply.github.com>
Signed-off-by: Adrien Widart <adwid@users.noreply.github.com>
This commit is contained in:
@@ -920,9 +920,9 @@ class MrpProduction(models.Model):
|
||||
self.qty_producing = self.product_id.uom_id._compute_quantity(1, self.product_uom_id, rounding_method='HALF-UP')
|
||||
|
||||
for move in (self.move_raw_ids | self.move_finished_ids.filtered(lambda m: m.product_id != self.product_id)):
|
||||
if move._should_bypass_set_qty_producing():
|
||||
if move._should_bypass_set_qty_producing() or not move.product_uom:
|
||||
continue
|
||||
new_qty = self.product_uom_id._compute_quantity((self.qty_producing - self.qty_produced) * move.unit_factor, self.product_uom_id, rounding_method='HALF-UP')
|
||||
new_qty = float_round((self.qty_producing - self.qty_produced) * move.unit_factor, precision_rounding=move.product_uom.rounding)
|
||||
move.move_line_ids.filtered(lambda ml: ml.state not in ('done', 'cancel')).qty_done = 0
|
||||
move.move_line_ids = move._set_quantity_done_prepare_vals(new_qty)
|
||||
|
||||
|
||||
@@ -165,14 +165,14 @@ class StockMove(models.Model):
|
||||
moves_with_reference |= move
|
||||
super(StockMove, self - moves_with_reference)._compute_reference()
|
||||
|
||||
@api.depends('raw_material_production_id.qty_producing', 'product_uom_qty')
|
||||
@api.depends('raw_material_production_id.qty_producing', 'product_uom_qty', 'product_uom')
|
||||
def _compute_should_consume_qty(self):
|
||||
for move in self:
|
||||
mo = move.raw_material_production_id
|
||||
if not mo:
|
||||
if not mo or not move.product_uom:
|
||||
move.should_consume_qty = 0
|
||||
continue
|
||||
move.should_consume_qty = mo.product_uom_id._compute_quantity((mo.qty_producing - mo.qty_produced) * move.unit_factor, mo.product_uom_id, rounding_method='HALF-UP')
|
||||
move.should_consume_qty = float_round((mo.qty_producing - mo.qty_produced) * move.unit_factor, precision_rounding=move.product_uom.rounding)
|
||||
|
||||
@api.onchange('product_uom_qty')
|
||||
def _onchange_product_uom_qty(self):
|
||||
|
||||
@@ -1521,6 +1521,61 @@ class TestMrpOrder(TestMrpCommon):
|
||||
self.assertEqual(mo.move_finished_ids.quantity_done, 1)
|
||||
self.assertEqual(component.qty_available, 13)
|
||||
|
||||
def test_immediate_validate_uom_2(self):
|
||||
"""The rounding precision of a component should be based on the UoM used in the MO for this component,
|
||||
not on the produced product's UoM nor the default UoM of the component"""
|
||||
uom_units = self.env['ir.model.data'].xmlid_to_object('uom.product_uom_unit')
|
||||
uom_L = self.env['ir.model.data'].xmlid_to_object('uom.product_uom_litre')
|
||||
uom_cL = self.env['uom.uom'].create({
|
||||
'name': 'cL',
|
||||
'category_id': uom_L.category_id.id,
|
||||
'uom_type': 'smaller',
|
||||
'factor': 100,
|
||||
'rounding': 1,
|
||||
})
|
||||
uom_units.rounding = 1
|
||||
uom_L.rounding = 0.01
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'SuperProduct',
|
||||
'uom_id': uom_units.id,
|
||||
})
|
||||
consumable_component = self.env['product.product'].create({
|
||||
'name': 'Consumable Component',
|
||||
'type': 'consu',
|
||||
'uom_id': uom_cL.id,
|
||||
'uom_po_id': uom_cL.id,
|
||||
})
|
||||
storable_component = self.env['product.product'].create({
|
||||
'name': 'Storable Component',
|
||||
'type': 'product',
|
||||
'uom_id': uom_cL.id,
|
||||
'uom_po_id': uom_cL.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(storable_component, self.env.ref('stock.stock_location_stock'), 100)
|
||||
|
||||
for component in [consumable_component, storable_component]:
|
||||
bom = self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'bom_line_ids': [(0, 0, {
|
||||
'product_id': component.id,
|
||||
'product_qty': 0.2,
|
||||
'product_uom_id': uom_L.id,
|
||||
})],
|
||||
})
|
||||
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = bom
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
action = mo.button_mark_done()
|
||||
self.assertEqual(action.get('res_model'), 'mrp.immediate.production')
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
action = wizard.process()
|
||||
|
||||
self.assertEqual(mo.move_raw_ids.product_uom_qty, 0.2)
|
||||
self.assertEqual(mo.move_raw_ids.quantity_done, 0.2)
|
||||
|
||||
def test_copy(self):
|
||||
""" Check that copying a done production, create all the stock moves"""
|
||||
mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=1, qty_base_1=1, qty_base_2=1)
|
||||
|
||||
Reference in New Issue
Block a user