Skip to content

Commit

Permalink
[FIX] {purchase_,}mrp: display routes for subcontracted boms
Browse files Browse the repository at this point in the history
1. Fixes an issue where the parent product wasn't correctly set when
computing the routes of a component, leading on components from
subcontracted products displaying the wrong route (as it was trying to
resupply the selected warehouse instead of the subcontracted location).

2. If a route is found when searching for subcontracting routes but
doesn't lead to a way to resupply the stock (either buy buying or
manufacturing something), then ignore the found rules and revert to the
default of trying to resupply the stock location.
This avoids issue when using reordering rules to resupply the
subcontracted location instead, where the 'Buy' route would be hidden
even if it was selected.

closes #159566

X-original-commit: c7b84a4
Signed-off-by: Quentin Wolfs (quwo) <quwo@odoo.com>
Signed-off-by: Steve Van Essche <svs@odoo.com>
  • Loading branch information
clesgow committed Mar 28, 2024
1 parent 72776b5 commit 384ffef
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
7 changes: 7 additions & 0 deletions addons/mrp/report/mrp_report_bom_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,13 +573,20 @@ def _get_resupply_route_info(self, warehouse, product, quantity, product_info, b
found_rules = []
if self._need_special_rules(product_info, parent_bom, parent_product):
found_rules = self._find_special_rules(product, product_info, parent_bom, parent_product)
if found_rules and not self._is_resupply_rules(found_rules, bom):
# We only want to show the effective resupply (i.e. a form of manufacture or buy)
found_rules = []
if not found_rules:
found_rules = product._get_rules_from_location(warehouse.lot_stock_id)
if not found_rules:
return {}
rules_delay = sum(rule.delay for rule in found_rules)
return self.with_context(parent_bom=parent_bom)._format_route_info(found_rules, rules_delay, warehouse, product, bom, quantity)

@api.model
def _is_resupply_rules(self, rules, bom):
return bom and any(rule.action == 'manufacture' for rule in rules)

@api.model
def _need_special_rules(self, product_info, parent_bom=False, parent_product=False):
return False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import Command
from odoo.tests import Form
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon

Expand Down Expand Up @@ -403,3 +404,65 @@ def test_two_boms_same_component_supplier(self):
{'product_id': component.id, 'product_qty': 1.0},
{'product_id': component.id, 'product_qty': 1.0},
])

def test_subcontracted_bom_routes(self):
"""
Take two BoM having those components. One being subcontracted and the other not.
- Compo RR : Buy & Reordering rule to resupply subcontractor.
- Compo DROP : Buy & Dropship subcontractor on order.
Check that depending on the context, the right route is shown on the report.
"""
route_buy = self.env.ref('purchase_stock.route_warehouse0_buy')
route_dropship = self.env['stock.route'].search([('name', '=', 'Dropship Subcontractor on Order')], limit=1)
warehouse = self.env['stock.warehouse'].search([], limit=1)

compo_drop, compo_rr = self.env['product.product'].create([{
'name': name,
'type': 'product',
'seller_ids': [Command.create({'partner_id': self.subcontractor_partner1.parent_id.id})],
'route_ids': [Command.set(routes)],
} for name, routes in [
('Compo DROP', [route_buy.id, route_dropship.id]),
('Compo RR', [route_buy.id]),
]])

route_resupply = self.env['stock.route'].search([('name', '=like', '%Resupply Subcontractor'), ('warehouse_ids', '=', warehouse.id)])
route_resupply.product_selectable = True
self.env['stock.warehouse.orderpoint'].create({
'name': 'Resupply Subcontractor',
'location_id': self.subcontractor_partner1.property_stock_subcontractor.id,
'route_id': route_resupply.id,
'product_id': compo_rr.id,
'product_min_qty': 0,
'product_max_qty': 0,
})
compo_rr.route_ids = [Command.link(route_resupply.id)]

bom_subcontract, bom_local = self.env['mrp.bom'].create([{
'product_tmpl_id': self.comp1.product_tmpl_id.id,
'type': bom_type,
'subcontractor_ids': partner_id,
'bom_line_ids': [
Command.create({'product_id': compo_drop.id, 'product_qty': 1}),
Command.create({'product_id': compo_rr.id, 'product_qty': 1}),
]
} for bom_type, partner_id in [
('subcontract', [Command.link(self.subcontractor_partner1.id)]),
('normal', False),
]])
# Need to add the subcontractor as Vendor to have the bom read as subcontracted.
self.comp1.write({'seller_ids': [Command.create({'partner_id': self.subcontractor_partner1.id})]})

report = self.env['report.mrp.report_bom_structure'].with_context(warehouse=warehouse.id)._get_report_data(bom_subcontract.id)
component_lines = report.get('lines', []).get('components', [])
self.assertEqual(component_lines[0]['product_id'], compo_drop.id)
self.assertEqual(component_lines[0]['route_name'], 'Dropship Subcontractor on Order')
self.assertEqual(component_lines[1]['product_id'], compo_rr.id)
self.assertEqual(component_lines[1]['route_name'], 'Buy', 'Despite the RR linked to it, it should still display the Buy route')

report = self.env['report.mrp.report_bom_structure'].with_context(warehouse=warehouse.id)._get_report_data(bom_local.id)
component_lines = report.get('lines', []).get('components', [])
self.assertEqual(component_lines[0]['product_id'], compo_drop.id)
self.assertEqual(component_lines[0]['route_name'], 'Buy', 'Outside of the subcontracted context, it should try to resupply stock.')
self.assertEqual(component_lines[1]['product_id'], compo_rr.id)
self.assertEqual(component_lines[1]['route_name'], 'Buy')
4 changes: 4 additions & 0 deletions addons/purchase_mrp/report/mrp_report_bom_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def _format_route_info(self, rules, rules_delay, warehouse, product, bom, quanti
}
return res

@api.model
def _is_resupply_rules(self, rules, bom):
return super()._is_resupply_rules(rules, bom) or any(rule.action == 'buy' for rule in rules)

@api.model
def _is_buy_route(self, rules, product, bom):
return any(rule for rule in rules if rule.action == 'buy' and product.seller_ids)
Expand Down

0 comments on commit 384ffef

Please sign in to comment.