[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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user