[FIX] {purchase_,}mrp: display routes for subcontracted boms

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 odoo/odoo#159516

X-original-commit: c7b84a4a602f72c7a67144b7a81b38afe36e0eee
Signed-off-by: Quentin Wolfs (quwo) <quwo@odoo.com>
Signed-off-by: Steve Van Essche <svs@odoo.com>
This commit is contained in:
clesgow
2024-03-28 14:16:50 +00:00
parent c6b1d287e7
commit 9971fb02cf
3 changed files with 74 additions and 0 deletions
@@ -573,6 +573,9 @@ class ReportBomStructure(models.AbstractModel):
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:
@@ -580,6 +583,10 @@ class ReportBomStructure(models.AbstractModel):
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
@@ -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
@@ -403,3 +404,65 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon):
{'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')
@@ -25,6 +25,10 @@ class ReportBomStructure(models.AbstractModel):
}
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)