[FIX] mrp: merge SFP for 2 different MO

Set the warehouse with manufacturing 3 steps
Create a BoM with a Components that use a Sub BoM
Set Both product MTO + Manufacture
Create a MO
You have only 2 picking for the first MO with the SFP
that contains both the finished product and the intermediate
component. However they are created and moved at two different times
since you could not create the finished product withtout the
component, so there is no meaning to put them both in the same picking.

It happens because the _assign_picking will merge moves with the same
procurement group. In our case, it's the wanted behavior to have the
2 differents SFP linked to the same MO.

The idea between the fix is:
- We want to have the pick components picking merged.
- We want to have the store components split since they come from
different MO
So we create the different procurement groups during the _run_pull with
the picking type of store finished product. This way the pickings won't
be merged during _assign_picking

We will also propagate those groups to the newly created MO in order to
keep the picking type sequence and correct name.

FW PORT of
https://github.com/odoo/odoo/commit/bcfa03821b92bf52675db0e3b54d3cb19dfe3087

opw-2585525

closes odoo/odoo#85195

X-original-commit: a647e680a196f7e6d9147d51dd80f8c31e0e741b
Signed-off-by: Arnold Moyaux <arm@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Andrea Grazioso (agr-odoo)
2022-02-23 12:09:02 +00:00
committed by Adrien Widart
parent c690c674a4
commit 073933ab01
3 changed files with 141 additions and 3 deletions
+2 -2
View File
@@ -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):
+31 -1
View File
@@ -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'])
@@ -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