Skip to content

Commit

Permalink
[FIX] {purchase_,}mrp: display routes for subcontracted boms
Browse files Browse the repository at this point in the history
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.

X-original-commit: c7b84a4
  • Loading branch information
clesgow committed Mar 28, 2024
1 parent 802e010 commit cf6fc85
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 5 deletions.
8 changes: 6 additions & 2 deletions addons/mrp/report/mrp_report_bom_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,20 +572,24 @@ def _get_bom_array_lines(self, data, level, unfolded_ids, unfolded, parent_unfol
def _get_resupply_route_info(self, warehouse, product, quantity, product_info, bom=False, parent_bom=False, parent_product=False):
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)
found_rules = self._find_special_rules(product, product_info, bom, parent_bom, parent_product)
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

@api.model
def _find_special_rules(self, product, product_info, parent_bom=False, parent_product=False):
def _find_special_rules(self, product, product_info, current_bom=False, parent_bom=False, parent_product=False):
return False

@api.model
Expand Down
9 changes: 6 additions & 3 deletions addons/mrp_subcontracting/report/mrp_report_bom_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,19 @@ def _need_special_rules(self, product_info, parent_bom=False, parent_product=Fal
return super()._need_special_rules(product_info, parent_bom, parent_product)

@api.model
def _find_special_rules(self, product, product_info, parent_bom=False, parent_product=False):
res = super()._find_special_rules(product, product_info, parent_bom, parent_product)
def _find_special_rules(self, product, product_info, current_bom=False, parent_bom=False, parent_product=False):
res = super()._find_special_rules(product, product_info, current_bom, parent_bom, parent_product)
if not parent_bom or not parent_product:
return res
# If no rules could be found within the warehouse, check if the product is a component from a subcontracted product.
parent_info = product_info.get(parent_product.id, {}).get(parent_bom.id, {})
if parent_info and parent_info.get('route_type') == 'subcontract':
# Since the product is subcontracted, check the subcontracted location for rules instead of the warehouse.
subcontracting_loc = parent_info['supplier'].partner_id.property_stock_subcontractor
return product._get_rules_from_location(subcontracting_loc)
found_rules = product._get_rules_from_location(subcontracting_loc)
if found_rules and self._is_resupply_rules(found_rules, current_bom):
# We only want to show the effective resupply (i.e. a form of manufacture or buy)
return found_rules
return res

@api.model
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 cf6fc85

Please sign in to comment.