From a1deba11869530624fbbeebc76361f0f3ea18740 Mon Sep 17 00:00:00 2001 From: Walid Date: Fri, 30 Jun 2023 11:09:52 +0000 Subject: [PATCH] [FIX] mrp: keep reserved tracked products on Backorders Steps to reproduce: - Create a subcontracted product with components tracked by lots - make a PO for 35 the product and send components from multiple lots - login as subcontractor on the portal - record uncomplete production and change the default consumed lots Bug: split production ignores the done quantities on the initial move on the Backorders components are considered to be consumed in the default order Fix: Consume components on the initial move as indicated by the user reserve the remaining components for the Backorders as usual opw-3186773 closes odoo/odoo#127467 X-original-commit: c39256ba6df257ee79fb66eab61617f5fec166b9 Signed-off-by: William Henrotin (whe) Signed-off-by: Walid Hanniche (waha) --- addons/mrp/models/mrp_production.py | 19 +++++++++-- addons/mrp/tests/test_backorder.py | 49 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index db64af44d15..b17ac2e2eab 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -1789,15 +1789,30 @@ class MrpProduction(models.Model): move = moves and moves.pop(0) move_qty_to_reserve = move.product_qty + + for index, (quantity, move_line, ml_vals) in enumerate(ml_by_move): + taken_qty = min(quantity, move_qty_to_reserve) + taken_qty_uom = min(product_uom._compute_quantity(taken_qty, move_line.product_uom_id), move_line.qty_done) + if float_is_zero(taken_qty_uom, precision_rounding=move_line.product_uom_id.rounding): + continue + move_line.with_context(bypass_reservation_update=True).reserved_uom_qty = taken_qty_uom + move_qty_to_reserve -= taken_qty_uom + ml_by_move[index] = (quantity - taken_qty_uom, move_line, ml_vals) + + if float_compare(move_qty_to_reserve, 0, precision_rounding=move.product_uom.rounding) <= 0: + assigned_moves.add(move.id) + move = moves and moves.pop(0) + move_qty_to_reserve = move and move.product_qty or 0 + for quantity, move_line, ml_vals in ml_by_move: while float_compare(quantity, 0, precision_rounding=product_uom.rounding) > 0 and move: # Do not create `stock.move.line` if there is no initial demand on `stock.move` taken_qty = min(move_qty_to_reserve, quantity) taken_qty_uom = product_uom._compute_quantity(taken_qty, move_line.product_uom_id) if move == initial_move: - move_line.with_context(bypass_reservation_update=True).reserved_uom_qty = taken_qty_uom + move_line.with_context(bypass_reservation_update=True).reserved_uom_qty += taken_qty_uom if set_consumed_qty: - move_line.qty_done = taken_qty_uom + move_line.qty_done += taken_qty_uom elif not float_is_zero(taken_qty_uom, precision_rounding=move_line.product_uom_id.rounding): new_ml_vals = dict( ml_vals, diff --git a/addons/mrp/tests/test_backorder.py b/addons/mrp/tests/test_backorder.py index 06e5fc91a7c..101d62207b8 100644 --- a/addons/mrp/tests/test_backorder.py +++ b/addons/mrp/tests/test_backorder.py @@ -225,6 +225,55 @@ class TestMrpProductionBackorder(TestMrpCommon): self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), nb_product_todo, f'You should have the {nb_product_todo} final product in stock') self.assertEqual(len(production.procurement_group_id.mrp_production_ids), nb_product_todo) + def test_tracking_backorder_series_lot_2(self): + """ + Create a MO with component tracked by lots. Produce a part of the demand + by using some specific lots (not the ones suggested by the onchange). + The components' reservation of the backorder should consider which lots + have been consumed in the initial MO + """ + production, _, _, p1, p2 = self.generate_mo(tracking_base_2='lot') + lot1, lot2 = self.env['stock.lot'].create([{ + 'name': f'lot_consumed_{i}', + 'product_id': p2.id, + 'company_id': self.env.company.id, + } for i in range(2)]) + self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 20) + self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot1) + self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2, lot_id=lot2) + + production.action_assign() + + production_form = Form(production) + production_form.qty_producing = 3 + + details_operation_form = Form(production.move_raw_ids.filtered(lambda m: m.product_id == p1), view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 4 * 3 + details_operation_form.save() + + # Consume 1 Product from lot1 and 2 from lot 2 + p2_smls = production.move_raw_ids.filtered(lambda m: m.product_id == p2).move_line_ids + self.assertEqual(len(p2_smls), 2, 'One for each lot') + details_operation_form = Form(production.move_raw_ids.filtered(lambda m: m.product_id == p2), view=self.env.ref('stock.view_stock_move_operations')) + with details_operation_form.move_line_ids.edit(0) as ml: + ml.qty_done = 1 + ml.lot_id = lot1 + with details_operation_form.move_line_ids.edit(1) as ml: + ml.qty_done = 2 + ml.lot_id = lot2 + details_operation_form.save() + + production = production_form.save() + action = production.button_mark_done() + backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context'])) + backorder.save().action_backorder() + + p2_bo_mls = production.procurement_group_id.mrp_production_ids[-1].move_raw_ids.filtered(lambda m: m.product_id == p2).move_line_ids + self.assertEqual(len(p2_bo_mls), 1) + self.assertEqual(p2_bo_mls.lot_id, lot1) + self.assertEqual(p2_bo_mls.reserved_qty, 2) + def test_tracking_backorder_series_serial_1(self): """ Create a MO of 4 tracked products (serial) with pbm_sam. all component is tracked by serial