Skip to content

Commit

Permalink
[FIX] mrp: merge SFP for 2 different MO
Browse files Browse the repository at this point in the history
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
odoo@bcfa038

opw-2585525

closes odoo#73248

Signed-off-by: Nicolas Lempereur (nle) <[email protected]>
  • Loading branch information
agr-odoo committed Jul 6, 2021
1 parent 0858384 commit 7ab0ac3
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 3 deletions.
4 changes: 2 additions & 2 deletions addons/mrp/models/mrp_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,12 @@ def _compute_allowed_product_ids(self):
@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):
Expand Down
32 changes: 31 additions & 1 deletion addons/mrp/models/stock_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ def _run_manufacture(self, procurements):
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_id.get_warehouse()
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']
Expand All @@ -86,7 +106,7 @@ def _get_matching_bom(self, product_id, company_id, values):
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_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'),
Expand All @@ -105,6 +125,16 @@ def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, na
'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_id.get_warehouse().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'])
Expand Down
112 changes: 112 additions & 0 deletions addons/mrp/tests/test_warehouse_multistep_manufacturing.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,115 @@ def test_manufacturing_3_steps_flexible(self):
pbm_move = move_raw_ids.move_orig_ids
self.assertEqual(len(pbm_move), 2)
self.assertTrue(new_product in pbm_move.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

product_form = Form(self.env['product.product'])
product_form.name = 'Arrow'
product_form.type = 'product'
product_form.route_ids.clear()
product_form.route_ids.add(self.warehouse.manufacture_pull_id.route_id)
product_form.route_ids.add(self.warehouse.mto_pull_id.route_id)
self.complex_product = product_form.save()

## Create raw product for manufactured product
product_form = Form(self.env['product.product'])
product_form.name = 'Raw Iron'
product_form.type = 'product'
product_form.uom_id = self.uom_unit
product_form.uom_po_id = self.uom_unit
self.raw_product_2 = product_form.save()

with Form(self.finished_product) as finished_product:
finished_product.route_ids.clear()
finished_product.route_ids.add(self.warehouse.manufacture_pull_id.route_id)
finished_product.route_ids.add(self.warehouse.mto_pull_id.route_id)

## 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_lines), 1)
picking.move_lines[0].product_id = self.raw_product

## Move SFP - PostProcessing -> Stock
picking = sfp_pickings[1]
self.assertEqual(len(picking.move_lines), 1)
picking.move_lines[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_lines), 2)
picking.move_lines[0].product_id = self.finished_product
picking.move_lines[1].product_id = self.raw_product_2

## Move FP PostProcessing -> Stock
picking = pickings[1]
self.assertEqual(len(picking.move_lines), 1)
picking.product_id = self.complex_product

0 comments on commit 7ab0ac3

Please sign in to comment.