From df7953f3031a2d3b0f2abf30a41df4955fb6b553 Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Fri, 4 Jan 2019 13:35:28 +0000 Subject: [PATCH] [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 --- addons/mrp/models/mrp_production.py | 5 ++- addons/mrp/models/stock_move.py | 18 +++++++++ addons/mrp/tests/test_order.py | 46 ++++++++++++++++++++++ addons/mrp/wizard/change_production_qty.py | 4 -- 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 636d0abd8dc..608a0eace65 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -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: diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 9db5a2b9d49..0821c2f8449 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -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, diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 656156a9cde..b49c39c226b 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -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() diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 38a125c3524..cddc9101043 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -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):