[FIX] mrp: unbuild MO with several lines for same component

When unbuilding a MO, if the latter has several lines (in its
components) for the same tracked product, it won't work.

To reproduce the issue:
1. Create two storable products P_compo and P_finished
    - P_compo is tracked by USN
2. Update P_compo's quantity to 2
3. Create a MO:
    - Product: Finished
    - Quantity: 1
    - Components:
        - 1 x P_compo
        - 1 x P_compo
4. Process the MO
5. Unbuild the MO

Error: a validation error is displayed while it should not "The serial
number has already been assigned: Product: P_Compo, Serial Number: C01"

The for-loop in `action_unbuild` do not handle the case where there are
several SMs for the same product. In the above case, there are two
component lines for P_compo, so there are two SM in `MO.move_raw_ids`.
When unbuilding the MO, the module creates and processes two SMs (SM01,
SM02) that "produce" two P_compo (stored in `produce_moves`).
When processing the first one in the for-loop, it takes the original
moves (so the `MO.move_raw_ids` with the same product) and uses them one
by one. Since SM01 needs one P_compo, it uses the first SM of
`MO.move_raw_ids` (and reuse the lot of that SM). Later on, when
processing SM02 in the for-loop, it does exactly the same thing: it
takes the original moves and uses the first one. Here is the issue: this
first SM has already been used and so does its lot, so this SM should
actually not be used again.

OPW-2715155

closes odoo/odoo#85354

X-original-commit: 8d2597965202a76ddeea8d8fed60b7ac014e2df8
Signed-off-by: Steve Van Essche <svs@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Adrien Widart
2022-02-25 10:25:43 +00:00
parent 9170895684
commit d9b9f94d18
2 changed files with 61 additions and 1 deletions
+5 -1
View File
@@ -6,6 +6,8 @@ from odoo.exceptions import AccessError, UserError
from odoo.tools import float_compare, float_round
from odoo.osv import expression
from collections import defaultdict
class MrpUnbuild(models.Model):
_name = "mrp.unbuild"
@@ -159,6 +161,7 @@ class MrpUnbuild(models.Model):
finished_move.quantity_done = finished_move.product_uom_qty
# TODO: Will fail if user do more than one unbuild with lot on the same MO. Need to check what other unbuild has aready took
qty_already_used = defaultdict(float)
for move in produce_moves | consume_moves:
if move.has_tracking != 'none':
original_move = move in produce_moves and self.mo_id.move_raw_ids or self.mo_id.move_finished_ids
@@ -169,7 +172,7 @@ class MrpUnbuild(models.Model):
moves_lines = moves_lines.filtered(lambda ml: self.lot_id in ml.produce_line_ids.lot_id) # FIXME sle: double check with arm
for move_line in moves_lines:
# Iterate over all move_lines until we unbuilded the correct quantity.
taken_quantity = min(needed_quantity, move_line.qty_done)
taken_quantity = min(needed_quantity, move_line.qty_done - qty_already_used[move_line])
if taken_quantity:
self.env['stock.move.line'].create({
'move_id': move.id,
@@ -181,6 +184,7 @@ class MrpUnbuild(models.Model):
'location_dest_id': move.location_dest_id.id,
})
needed_quantity -= taken_quantity
qty_already_used[move_line] += taken_quantity
else:
move.quantity_done = float_round(move.product_uom_qty, precision_rounding=move.product_uom.rounding)
+56
View File
@@ -631,3 +631,59 @@ class TestUnbuild(TestMrpCommon):
uo = uo_form.save()
uo.action_unbuild()
self.assertEqual(uo.state, 'done')
def test_unbuild_similar_tracked_components(self):
"""
Suppose a MO with, in the components, two lines for the same tracked-by-usn product
When unbuilding such an MO, all SN used in the MO should be back in stock
"""
compo, finished = self.env['product.product'].create([{
'name': 'compo',
'type': 'product',
'tracking': 'serial',
}, {
'name': 'finished',
'type': 'product',
}])
lot01, lot02 = self.env['stock.lot'].create([{
'name': n,
'product_id': compo.id,
'company_id': self.env.company.id,
} for n in ['lot01', 'lot02']])
self.env['stock.quant']._update_available_quantity(compo, self.stock_location, 1, lot_id=lot01)
self.env['stock.quant']._update_available_quantity(compo, self.stock_location, 1, lot_id=lot02)
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = finished
with mo_form.move_raw_ids.new() as line:
line.product_id = compo
line.product_uom_qty = 1
with mo_form.move_raw_ids.new() as line:
line.product_id = compo
line.product_uom_qty = 1
mo = mo_form.save()
mo.action_confirm()
mo_form = Form(mo)
mo_form.qty_producing = 1
mo = mo_form.save()
mo.action_assign()
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 1
details_operation_form.save()
details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 1
details_operation_form.save()
mo.button_mark_done()
uo_form = Form(self.env['mrp.unbuild'])
uo_form.mo_id = mo
uo_form.product_qty = 1
uo = uo_form.save()
uo.action_unbuild()
self.assertEqual(uo.produce_line_ids.filtered(lambda sm: sm.product_id == compo).lot_ids, lot01 + lot02)