diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py index d7f4cd29256..8faa26110cc 100644 --- a/addons/mrp/report/mrp_report_bom_structure.py +++ b/addons/mrp/report/mrp_report_bom_structure.py @@ -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 diff --git a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py index f884281fe2a..332f3b862b8 100644 --- a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py +++ b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py @@ -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') diff --git a/addons/purchase_mrp/report/mrp_report_bom_structure.py b/addons/purchase_mrp/report/mrp_report_bom_structure.py index a9a64c39329..8b1089161da 100644 --- a/addons/purchase_mrp/report/mrp_report_bom_structure.py +++ b/addons/purchase_mrp/report/mrp_report_bom_structure.py @@ -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)