[FIX] stock: unreserve drop all the processed work
Usecase to reproduce(or any other way to lower initial demand on move): - Build a MO with 5 products to do - Produce 3 - Update the quantity on the mo to 3 -> All the consumed quantity on move lines is dropped. It happens because lowering the initial demand on a move will call unlink on its move lines. (increase it will update the move's state and reassign if it comes from a supplier) Instead of dropping and reassign the move in order to change the reservation, it is possible to directly write the new reserved quantity on the move lines and that will update the reservation on qaunt directly. Also we don't want move lines without reserved quantity and quantity done thus we drop them. closes odoo/odoo#29941
This commit is contained in:
@@ -439,7 +439,10 @@ class MrpProduction(models.Model):
|
||||
move = self.move_raw_ids.filtered(lambda x: x.bom_line_id.id == bom_line.id and x.state not in ('done', 'cancel'))
|
||||
if move:
|
||||
if quantity > 0:
|
||||
move[0].write({'product_uom_qty': quantity})
|
||||
move[0]._decrease_reserved_quanity(quantity)
|
||||
move[0].with_context(do_not_unreserve=True).write({'product_uom_qty': quantity})
|
||||
move[0]._recompute_state()
|
||||
move[0]._action_assign()
|
||||
move.unit_factor = quantity / move.raw_material_production_id.product_qty
|
||||
elif quantity < 0: # Do not remove 0 lines
|
||||
if move[0].quantity_done > 0:
|
||||
|
||||
@@ -182,6 +182,24 @@ class StockMove(models.Model):
|
||||
self.sudo().unlink()
|
||||
return processed_moves
|
||||
|
||||
def _decrease_reserved_quanity(self, quantity):
|
||||
""" Decrease the reservation on move lines but keeps the
|
||||
all other data.
|
||||
"""
|
||||
move_line_to_unlink = self.env['stock.move.line']
|
||||
for move in self:
|
||||
reserved_quantity = quantity
|
||||
for move_line in self.move_line_ids:
|
||||
if move_line.product_uom_qty > reserved_quantity:
|
||||
move_line.product_uom_qty = reserved_quantity
|
||||
else:
|
||||
move_line.product_uom_qty = 0
|
||||
reserved_quantity -= move_line.product_uom_qty
|
||||
if not move_line.product_uom_qty and not move_line.qty_done:
|
||||
move_line_to_unlink |= move_line
|
||||
move_line_to_unlink.unlink()
|
||||
return True
|
||||
|
||||
def _prepare_phantom_move_values(self, bom_line, quantity):
|
||||
return {
|
||||
'picking_id': self.picking_id.id if self.picking_id else False,
|
||||
|
||||
@@ -617,3 +617,49 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
|
||||
def test_product_produce_5(self):
|
||||
""" Build 5 final products with different consumed lots,
|
||||
then edit the finished quantity and update the Manufacturing
|
||||
order quantity. Then check if the produced quantity do not
|
||||
change and it is possible to close the MO.
|
||||
"""
|
||||
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||
mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot')
|
||||
self.assertEqual(len(mo), 1, 'MO should have been created')
|
||||
|
||||
lot_1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': p1.id,
|
||||
})
|
||||
lot_2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': p1.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_1)
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_2)
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
|
||||
mo.action_assign()
|
||||
|
||||
produce_wizard = self.env['mrp.product.produce'].with_context({
|
||||
'active_id': mo.id,
|
||||
'active_ids': [mo.id],
|
||||
}).create({
|
||||
'product_qty': 5.0,
|
||||
})
|
||||
|
||||
for produce_line in produce_wizard.produce_line_ids:
|
||||
produce_line.qty_done = produce_line.qty_to_consume
|
||||
produce_wizard.do_produce()
|
||||
|
||||
mo.move_finished_ids.move_line_ids.qty_done -= 1
|
||||
update_quantity_wizard = self.env['change.production.qty'].create({
|
||||
'mo_id': mo.id,
|
||||
'product_qty': 4,
|
||||
})
|
||||
update_quantity_wizard.change_prod_qty()
|
||||
|
||||
self.assertEqual(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).quantity_done, 20, 'Update the produce quantity should not impact already produced quantity.')
|
||||
mo.button_mark_done()
|
||||
|
||||
@@ -50,7 +50,6 @@ class ChangeProductionQty(models.TransientModel):
|
||||
qty_produced = production.product_id.uom_id._compute_quantity(sum(done_moves.mapped('product_qty')), production.product_uom_id)
|
||||
factor = production.product_uom_id._compute_quantity(production.product_qty - qty_produced, production.bom_id.product_uom_id) / production.bom_id.product_qty
|
||||
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
|
||||
done_quantities = {move: move.quantity_done for move in production.move_raw_ids}
|
||||
for line, line_data in lines:
|
||||
production._update_raw_move(line, line_data)
|
||||
operation_bom_qty = {}
|
||||
@@ -60,9 +59,6 @@ class ChangeProductionQty(models.TransientModel):
|
||||
self._update_product_to_produce(production, production.product_qty - qty_produced)
|
||||
moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
|
||||
moves._action_assign()
|
||||
for move in production.move_raw_ids:
|
||||
if move.quantity_done != done_quantities[move]:
|
||||
move._set_quantity_done(done_quantities[move])
|
||||
for wo in production.workorder_ids:
|
||||
operation = wo.operation_id
|
||||
if operation_bom_qty.get(operation.id):
|
||||
|
||||
Reference in New Issue
Block a user