From 67e19a5dc768a5987829ea7076ca26d70cc0d0a5 Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Thu, 14 Oct 2021 08:53:00 +0000 Subject: [PATCH] [FIX] mrp_subcontracting: validate a partial picking When validating a receipt, if the product is subcontracted and if the picking is not fully done, it will be impossible to create a backorder To reproduce the issue: 1. Create two products P_compo, P_finished - Both storable - Both tracked by lot - P_compo must have the route "Resupply Subcontractor on Order" 2. Update P_compo's quantity: 4 3. Create a BoM: - Product: P_finished - BoM type: Subcontracting - Subcontractors: a partner P - Components: 1 x P_compo 4. In Inventory, create a planned transfer T: - Operation Type: Receipt - Receive From: P - Operations: 4 x P_finished 5. Mark as Todo 6. Inventory > Delivery Orders, find the delivery of P_compo for P and process it 7. Back to T, Record Components: - Quantity: 3/4 - Set a Lot for P_finished 8. Validate T, Create Backorder Error: a User Error is raised "You need to supply a Lot/Serial Number for product: - P_finished" Since P_finished is subcontracted, a related MO has been generated. On step 7, when recording the used components, since all P_finished have not been produced, a second MO is created for the last P_finished. However, when validating T, both MOs are selected and validated. This is an error since the user has not yet recorded the used components for the last MO. The latter should not be validated. OPW-2582538 closes odoo/odoo#78496 X-original-commit: 21068076b4a6b134f5f07c4c03de7c4eb37b4688 Signed-off-by: William Henrotin --- .../models/mrp_production.py | 6 +- .../tests/test_subcontracting.py | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/addons/mrp_subcontracting/models/mrp_production.py b/addons/mrp_subcontracting/models/mrp_production.py index 858114e38d4..599861d34f4 100644 --- a/addons/mrp_subcontracting/models/mrp_production.py +++ b/addons/mrp_subcontracting/models/mrp_production.py @@ -62,7 +62,7 @@ class MrpProduction(models.Model): backorder._set_qty_producing() self.product_qty = self.qty_producing - action = self._get_subcontract_move()._action_record_components() + action = self._get_subcontract_move().filtered(lambda m: m.state not in ('done', 'cancel'))._action_record_components() action['res_id'] = backorder.id return action return {'type': 'ir.actions.act_window_close'} @@ -75,7 +75,7 @@ class MrpProduction(models.Model): def _update_finished_move(self): """ After producing, set the move line on the subcontract picking. """ self.ensure_one() - subcontract_move_id = self._get_subcontract_move() + subcontract_move_id = self._get_subcontract_move().filtered(lambda m: m.state not in ('done', 'cancel')) if subcontract_move_id: quantity = self.qty_producing if self.lot_producing_id: @@ -141,6 +141,8 @@ class MrpProduction(models.Model): return False if not all(line.lot_id for line in mo.move_raw_ids.filtered(lambda sm: sm.has_tracking != 'none').move_line_ids): return False + if mo.product_id.tracking != 'none' and not mo.lot_producing_id: + return False return True return self.filtered(filter_in) diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py index 6b7d86c3a23..29e916f8deb 100644 --- a/addons/mrp_subcontracting/tests/test_subcontracting.py +++ b/addons/mrp_subcontracting/tests/test_subcontracting.py @@ -836,3 +836,75 @@ class TestSubcontractingTracking(TransactionCase): self.assertEqual(avail_qty_comp1, -todo_nb) self.assertEqual(avail_qty_comp2, -todo_nb) self.assertEqual(avail_qty_finished, todo_nb) + + def test_flow_tracked_backorder02(self): + """ Both component and finished product are tracked by lot. """ + todo_nb = 4 + resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')]) + finished_product, component = self.env['product.product'].create([{ + 'name': 'SuperProduct', + 'type': 'product', + 'tracking': 'lot', + }, { + 'name': 'Component', + 'type': 'product', + 'tracking': 'lot', + 'route_ids': [(4, resupply_sub_on_order_route.id)], + }]) + + bom_form = Form(self.env['mrp.bom']) + bom_form.type = 'subcontract' + bom_form.subcontractor_ids.add(self.subcontractor_partner1) + bom_form.product_tmpl_id = finished_product.product_tmpl_id + with bom_form.bom_line_ids.new() as bom_line: + bom_line.product_id = component + bom_line.product_qty = 1 + bom = bom_form.save() + + finished_lot, component_lot = self.env['stock.production.lot'].create([{ + 'name': 'lot_%s' % product.name, + 'product_id': product.id, + 'company_id': self.env.company.id, + } for product in [finished_product, component]]) + + self.env['stock.quant']._update_available_quantity(component, self.env.ref('stock.stock_location_stock'), todo_nb, lot_id=component_lot) + + # Create a receipt picking from the subcontractor + picking_form = Form(self.env['stock.picking']) + picking_form.picking_type_id = self.env.ref('stock.picking_type_in') + picking_form.partner_id = self.subcontractor_partner1 + with picking_form.move_ids_without_package.new() as move: + move.product_id = finished_product + move.product_uom_qty = todo_nb + picking_receipt = picking_form.save() + picking_receipt.action_confirm() + + mo = self.env['mrp.production'].search([('bom_id', '=', bom.id)]) + + # Process the delivery of the components + compo_picking = mo.picking_ids + compo_picking.action_assign() + wizard_data = compo_picking.button_validate() + wizard = Form(self.env[wizard_data['res_model']].with_context(wizard_data['context'])).save() + wizard.process() + + for qty in [3, 1]: + # Record the receiption of finished products + picking_receipt = self.env['stock.picking'].search([('partner_id', '=', self.subcontractor_partner1.id), ('state', '!=', 'done')]) + action = picking_receipt.action_record_components() + mo = self.env['mrp.production'].browse(action['res_id']) + mo_form = Form(mo.with_context(**action['context']), view=action['view_id']) + mo_form.qty_producing = qty + mo_form.lot_producing_id = finished_lot + with mo_form.move_line_raw_ids.edit(0) as ml: + ml.lot_id = component_lot + mo = mo_form.save() + mo.subcontracting_record_component() + + # Validate the picking and create a backorder + wizard_data = picking_receipt.button_validate() + if qty == 3: + wizard = Form(self.env[wizard_data['res_model']].with_context(wizard_data['context'])).save() + wizard.process() + + self.assertEqual(picking_receipt.state, 'done')