diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 52254b25b82..2f8243b74bc 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -276,12 +276,12 @@ class MrpProduction(models.Model): @api.depends('procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids') def _compute_mrp_production_child_count(self): for production in self: - production.mrp_production_child_count = len(production.procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids) + production.mrp_production_child_count = len(production.procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids - production) @api.depends('move_dest_ids.group_id.mrp_production_ids') def _compute_mrp_production_source_count(self): for production in self: - production.mrp_production_source_count = len(production.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.mrp_production_ids) + production.mrp_production_source_count = len(production.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.mrp_production_ids - production) @api.depends('procurement_group_id.mrp_production_ids') def _compute_mrp_production_backorder(self): diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index 2998b8c15ec..f6aef3a9456 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -80,6 +80,26 @@ class StockRule(models.Model): subtype_id=self.env.ref('mail.mt_note').id) return True + @api.model + def _run_pull(self, procurements): + # Override to correctly assign the move generated from the pull + # in its production order (pbm_sam only) + for procurement, rule in procurements: + warehouse_id = rule.warehouse_id + if not warehouse_id: + warehouse_id = rule.location_dest_id.warehouse_id + if rule.picking_type_id == warehouse_id.sam_type_id: + manu_type_id = warehouse_id.manu_type_id + if manu_type_id: + name = manu_type_id.sequence_id.next_by_id() + else: + name = self.env['ir.sequence'].next_by_code('mrp.production') or _('New') + # Create now the procurement group that will be assigned to the new MO + # This ensure that the outgoing move PostProduction -> Stock is linked to its MO + # rather than the original record (MO or SO) + procurement.values['group_id'] = self.env["procurement.group"].create({'name': name}) + return super()._run_pull(procurements) + def _get_custom_move_fields(self): fields = super(StockRule, self)._get_custom_move_fields() fields += ['bom_line_id'] @@ -93,7 +113,7 @@ class StockRule(models.Model): def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_dest_id, name, origin, company_id, values, bom): date_planned = self._get_date_planned(product_id, company_id, values) date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=company_id.manufacturing_lead) + relativedelta(days=product_id.produce_delay) - return { + mo_values = { 'origin': origin, 'product_id': product_id.id, 'product_description_variants': values.get('product_description_variants'), @@ -113,6 +133,16 @@ class StockRule(models.Model): 'move_dest_ids': values.get('move_dest_ids') and [(4, x.id) for x in values['move_dest_ids']] or False, 'user_id': False, } + # Use the procurement group created in _run_pull mrp override + # Preserve the origin from the original stock move, if available + if location_dest_id.warehouse_id.manufacture_steps == 'pbm_sam' and values.get('move_dest_ids') and values.get('group_id') and values['move_dest_ids'][0].origin != values['group_id'].name: + origin = values['move_dest_ids'][0].origin + mo_values.update({ + 'name': values['group_id'].name, + 'procurement_group_id': values['group_id'].id, + 'origin': origin, + }) + return mo_values def _get_date_planned(self, product_id, company_id, values): format_date_planned = fields.Datetime.from_string(values['date_planned']) diff --git a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py index a7ea55dc1b8..af1d644bcfb 100644 --- a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py +++ b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py @@ -473,3 +473,111 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon): mo.action_confirm() self.assertEqual(self.bom.bom_line_ids.product_id + self.product_2, mo.picking_ids.move_ids.product_id) + + def test_manufacturing_complex_product_3_steps(self): + """ Test MO/picking after manufacturing a complex product which uses + manufactured components. Ensure that everything is created and picked + correctly. + """ + + self.warehouse.mto_pull_id.route_id.active = True + # Creating complex product which trigger another manifacture + + routes = self.warehouse.manufacture_pull_id.route_id + self.warehouse.mto_pull_id.route_id + self.complex_product = self.env['product.product'].create({ + 'name': 'Arrow', + 'type': 'product', + 'route_ids': [(6, 0, routes.ids)], + }) + + # Create raw product for manufactured product + self.raw_product_2 = self.env['product.product'].create({ + 'name': 'Raw Iron', + 'type': 'product', + 'uom_id': self.uom_unit.id, + 'uom_po_id': self.uom_unit.id, + }) + + self.finished_product.route_ids = [(6, 0, routes.ids)] + + # Create bom for manufactured product + bom_product_form = Form(self.env['mrp.bom']) + bom_product_form.product_id = self.complex_product + bom_product_form.product_tmpl_id = self.complex_product.product_tmpl_id + with bom_product_form.bom_line_ids.new() as line: + line.product_id = self.finished_product + line.product_qty = 1.0 + with bom_product_form.bom_line_ids.new() as line: + line.product_id = self.raw_product_2 + line.product_qty = 1.0 + + self.complex_bom = bom_product_form.save() + + with Form(self.warehouse) as warehouse: + warehouse.manufacture_steps = 'pbm_sam' + + production_form = Form(self.env['mrp.production']) + production_form.product_id = self.complex_product + production_form.picking_type_id = self.warehouse.manu_type_id + production = production_form.save() + production.action_confirm() + + move_raw_ids = production.move_raw_ids + self.assertEqual(len(move_raw_ids), 2) + sfp_move_raw_id, raw_move_raw_id = move_raw_ids + self.assertEqual(sfp_move_raw_id.product_id, self.finished_product) + self.assertEqual(raw_move_raw_id.product_id, self.raw_product_2) + + for move_raw_id in move_raw_ids: + self.assertEqual(move_raw_id.picking_type_id, self.warehouse.manu_type_id) + + pbm_move = move_raw_id.move_orig_ids + self.assertEqual(len(pbm_move), 1) + self.assertEqual(pbm_move.location_id, self.warehouse.lot_stock_id) + self.assertEqual(pbm_move.location_dest_id, self.warehouse.pbm_loc_id) + self.assertEqual(pbm_move.picking_type_id, self.warehouse.pbm_type_id) + + # Check move locations + move_finished_ids = production.move_finished_ids + self.assertEqual(len(move_finished_ids), 1) + self.assertEqual(move_finished_ids.product_id, self.complex_product) + self.assertEqual(move_finished_ids.picking_type_id, self.warehouse.manu_type_id) + sam_move = move_finished_ids.move_dest_ids + self.assertEqual(len(sam_move), 1) + self.assertEqual(sam_move.location_id, self.warehouse.sam_loc_id) + self.assertEqual(sam_move.location_dest_id, self.warehouse.lot_stock_id) + self.assertEqual(sam_move.picking_type_id, self.warehouse.sam_type_id) + self.assertFalse(sam_move.move_dest_ids) + + subproduction = self.env['mrp.production'].browse(production.id+1) + sfp_pickings = subproduction.picking_ids.sorted('id') + + # SFP Production: 2 pickings, 1 group + self.assertEqual(len(sfp_pickings), 2) + self.assertEqual(sfp_pickings.mapped('group_id'), subproduction.procurement_group_id) + + # Move Raw Stick - Stock -> Preprocessing + picking = sfp_pickings[0] + self.assertEqual(len(picking.move_ids), 1) + picking.move_ids[0].product_id = self.raw_product + + # Move SFP - PostProcessing -> Stock + picking = sfp_pickings[1] + self.assertEqual(len(picking.move_ids), 1) + picking.move_ids[0].product_id = self.finished_product + + # Main production 2 pickings, 1 group + pickings = production.picking_ids.sorted('id') + self.assertEqual(len(pickings), 2) + self.assertEqual(pickings.mapped('group_id'), production.procurement_group_id) + + # Move 2 components Stock -> Preprocessing + picking = pickings[0] + self.assertEqual(len(picking.move_ids), 2) + picking.move_ids[0].product_id = self.finished_product + picking.move_ids[1].product_id = self.raw_product_2 + + # Move FP PostProcessing -> Stock + picking = pickings[1] + self.assertEqual(len(picking.move_ids), 1) + picking.product_id = self.complex_product