[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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user