[FIX] mrp: correctly copy non-backordered MO
Steps to reproduce: Step 1: make a MO and create less than the quantity to produce Step 2: Mark As Done (with no backorder) Step 3: duplicate the MO and try to change the quantity to produce Expected result: qty to produce changes as expected Actual result: server error Issue is due to the copied MO's `move_finished_ids` including a copy of the cancelled finished move (i.e. the qty not backordered) so there were 2 `move_finished_ids` for the product to produce. This resulted in an access error since the onchange to update the `move_finished_ids` only expects 1 move for the product to produce and results in a singleton error. Note we copy cancelled move_raw_ids because otherwise we wouldn't be able to duplicate cancelled MOs without losing all of its components. Issue 2 of Task: 2618962 closes odoo/odoo#75073 X-original-commit: 456c337534427db1296030b004ae061b47d8db79 Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com> Signed-off-by: Tiffany Chang <tic@odoo.com>
This commit is contained in:
@@ -190,11 +190,11 @@ class MrpProduction(models.Model):
|
||||
|
||||
move_raw_ids = fields.One2many(
|
||||
'stock.move', 'raw_material_production_id', 'Components',
|
||||
copy=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
domain=[('scrapped', '=', False)])
|
||||
move_finished_ids = fields.One2many(
|
||||
'stock.move', 'production_id', 'Finished Products',
|
||||
copy=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
domain=[('scrapped', '=', False)])
|
||||
move_byproduct_ids = fields.One2many('stock.move', compute='_compute_move_byproduct_ids', inverse='_set_move_byproduct_ids')
|
||||
finished_move_line_ids = fields.One2many(
|
||||
@@ -773,6 +773,16 @@ class MrpProduction(models.Model):
|
||||
workorders_to_delete.unlink()
|
||||
return super(MrpProduction, self).unlink()
|
||||
|
||||
def copy_data(self, default=None):
|
||||
default = dict(default or {})
|
||||
# covers at least 2 cases: backorders generation (follow default logic for moves copying)
|
||||
# and copying a done MO via the form (i.e. copy only the non-cancelled moves since no backorder = cancelled finished moves)
|
||||
if not default or 'move_finished_ids' not in default:
|
||||
default['move_finished_ids'] = [(0, 0, move.copy_data()[0]) for move in self.move_finished_ids.filtered(lambda m: m.state != 'cancel' and m.product_qty != 0.0)]
|
||||
if not default or 'move_raw_ids' not in default:
|
||||
default['move_raw_ids'] = [(0, 0, move.copy_data()[0]) for move in self.move_raw_ids.filtered(lambda m: m.product_qty != 0.0)]
|
||||
return super(MrpProduction, self).copy_data(default=default)
|
||||
|
||||
def action_toggle_is_locked(self):
|
||||
self.ensure_one()
|
||||
self.is_locked = not self.is_locked
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
}).action_apply_inventory()
|
||||
|
||||
test_date_planned = Dt.now() - timedelta(days=1)
|
||||
test_quantity = 2.0
|
||||
test_quantity = 3.0
|
||||
man_order_form = Form(self.env['mrp.production'].with_user(self.user_mrp_user))
|
||||
man_order_form.product_id = self.product_4
|
||||
man_order_form.bom_id = self.bom_1
|
||||
@@ -89,7 +89,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
# produce product
|
||||
mo_form = Form(man_order)
|
||||
mo_form.qty_producing = 1.0
|
||||
mo_form.qty_producing = 2.0
|
||||
man_order = mo_form.save()
|
||||
|
||||
action = man_order.button_mark_done()
|
||||
@@ -101,6 +101,14 @@ class TestMrpOrder(TestMrpCommon):
|
||||
backorder.save().action_close_mo()
|
||||
self.assertEqual(man_order.state, 'done', "Production order should be done.")
|
||||
|
||||
# check that copy handles moves correctly
|
||||
mo_copy = man_order.copy()
|
||||
self.assertEqual(mo_copy.state, 'draft', "Copied production order should be draft.")
|
||||
self.assertEqual(len(mo_copy.move_raw_ids), 4,
|
||||
"Incorrect number of component moves [i.e. all non-0 (even cancelled) moves should be copied].")
|
||||
self.assertEqual(len(mo_copy.move_finished_ids), 1, "Incorrect number of moves for products to produce [i.e. cancelled moves should not be copied")
|
||||
self.assertEqual(mo_copy.move_finished_ids.product_uom_qty, 2, "Incorrect qty of products to produce")
|
||||
|
||||
def test_production_availability(self):
|
||||
""" Checks the availability of a production order through mutliple calls to `action_assign`.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user