diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py index c5ec547e89d..9685c8313fe 100644 --- a/addons/mrp_subcontracting/__manifest__.py +++ b/addons/mrp_subcontracting/__manifest__.py @@ -25,7 +25,6 @@ 'views/subcontracting_portal_templates.xml', 'views/stock_location_views.xml', 'wizard/stock_picking_return_views.xml', - 'report/mrp_report_bom_structure.xml', ], 'demo': [ 'data/mrp_subcontracting_demo.xml', @@ -34,6 +33,12 @@ 'web.assets_tests': [ 'mrp_subcontracting/static/tests/tours/subcontracting_portal_tour.js', ], + 'web.assets_backend': [ + 'mrp_subcontracting/static/src/components/**/*.js', + ], + 'web.assets_qweb': [ + 'mrp_subcontracting/static/**/*.xml', + ], 'mrp_subcontracting.assets_qweb': [ ('include', 'web.assets_qweb'), 'mrp_subcontracting/static/src/subcontracting_portal/subcontracting_portal.xml', diff --git a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py index 84761475bbc..a99f0fac147 100644 --- a/addons/mrp_subcontracting/report/mrp_report_bom_structure.py +++ b/addons/mrp_subcontracting/report/mrp_report_bom_structure.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, _ - +from odoo import api, models, _ class ReportBomStructure(models.AbstractModel): _inherit = 'report.mrp.report_bom_structure' @@ -19,46 +18,78 @@ class ReportBomStructure(models.AbstractModel): 'level': level or 0 } - def _get_price(self, bom, factor, product): - price = super()._get_price(bom, factor, product) - if bom and bom.type == 'subcontract': - bom_quantity = bom.product_qty * factor - seller = product._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) + def _get_bom_data(self, bom, warehouse, product=False, line_qty=False, bom_line=False, level=0, parent_bom=False, index=0, product_info=False, ignore_stock=False): + res = super()._get_bom_data(bom, warehouse, product, line_qty, bom_line, level, parent_bom, index, product_info, ignore_stock) + if bom.type == 'subcontract' and not self.env.context.get('minimized', False): + seller = res['product']._select_seller(quantity=res['quantity'], uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) if seller: - price += seller.product_uom._compute_price(seller.price, product.uom_id) * bom_quantity - return price - - def _get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False): - res = super(ReportBomStructure, self)._get_bom(bom_id, product_id, line_qty, line_id, level) - bom = res['bom'] - if bom and bom.type == 'subcontract': - bom_quantity = line_qty - if line_id: - current_line = self.env['mrp.bom.line'].browse(int(line_id)) - bom_quantity = current_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id) - - seller = res['product']._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) - if seller: - res['subcontracting'] = self._get_subcontracting_line(bom, seller, level, bom_quantity) - res['total'] += res['subcontracting']['bom_cost'] - res['bom_cost'] += res['subcontracting']['bom_cost'] + res['subcontracting'] = self._get_subcontracting_line(bom, seller, level + 1, res['quantity']) + if not self.env.context.get('minimized', False): + res['bom_cost'] += res['subcontracting']['bom_cost'] return res - def _get_sub_lines(self, bom, product_id, line_qty, line_id, level, unfolded_ids, unfolded): - res = super()._get_sub_lines(bom, product_id, line_qty, line_id, level, unfolded_ids, unfolded) - if bom and bom.type == 'subcontract': - product = self.env['product.product'].browse(product_id) + def _get_bom_array_lines(self, data, level, unfolded_ids, unfolded, parent_unfolded): + lines = super()._get_bom_array_lines(data, level, unfolded_ids, unfolded, parent_unfolded) - bom_quantity = line_qty - if line_id: - current_line = self.env['mrp.bom.line'].browse(int(line_id)) - bom_quantity = current_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id) + if data.get('subcontracting'): + subcontract_info = data['subcontracting'] + lines.append({ + 'name': _("Subcontracting: %s", subcontract_info['name']), + 'type': 'subcontract', + 'uom': False, + 'quantity': subcontract_info['quantity'], + 'bom_cost': subcontract_info['bom_cost'], + 'prod_cost': subcontract_info['prod_cost'], + 'level': subcontract_info['level'], + 'visible': level == 1 or unfolded or parent_unfolded + }) + return lines - seller = product._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) - if seller: - values_sub = self._get_subcontracting_line(bom, seller, level, bom_quantity) - values_sub['type'] = 'bom' - values_sub['name'] = _("Subcontracting: ") + values_sub['name'] - res.append(values_sub) + @api.model + def _format_route_info(self, rules, rules_delay, warehouse, product, bom, quantity): + res = super()._format_route_info(rules, rules_delay, warehouse, product, bom, quantity) + subcontract_rules = [rule for rule in rules if rule.action == 'buy' and bom and bom.type == 'subcontract'] + if subcontract_rules: + supplier = product._select_seller(quantity=quantity, uom_id=product.uom_id, params={'subcontractor_ids': bom.subcontractor_ids}) + if supplier: + return { + 'route_type': 'subcontract', + 'route_name': subcontract_rules[0].route_id.display_name, + 'route_detail': supplier.display_name, + 'lead_time': supplier.delay + rules_delay, + 'supplier_delay': supplier.delay + rules_delay, + 'manufacture_delay': product.produce_delay, + 'supplier': supplier, + } return res + + @api.model + def _get_quantities_info(self, product, bom_uom, parent_bom, product_info): + if parent_bom and parent_bom.type == 'subcontract' and product.detailed_type == 'product': + parent_product = parent_bom.product_id or parent_bom.product_tmpl_id.product_variant_id + route_info = product_info[parent_product.id].get(parent_bom.id, {}) + if route_info and route_info['route_type'] == 'subcontract': + subcontracting_loc = route_info['supplier'].partner_id.property_stock_subcontractor + subloc_product = product.with_context(location=subcontracting_loc.id, warehouse=False).read(['free_qty', 'qty_available'])[0] + stock_loc = f"subcontract_{subcontracting_loc.id}" + if not product_info[product.id]['consumptions'].get(stock_loc, False): + product_info[product.id]['consumptions'][stock_loc] = 0 + return { + 'free_qty': product.uom_id._compute_quantity(subloc_product['free_qty'], bom_uom), + 'on_hand_qty': product.uom_id._compute_quantity(subloc_product['qty_available'], bom_uom), + 'stock_loc': stock_loc, + } + + return super()._get_quantities_info(product, bom_uom, parent_bom, product_info) + + @api.model + def _get_resupply_availability(self, route_info, components): + if route_info.get('route_type') == 'subcontract': + max_component_delay = self._get_max_component_delay(components) + if max_component_delay is False: + return ('unavailable', False) + produce_delay = route_info.get('manufacture_delay', 0) + max_component_delay + supplier_delay = route_info.get('supplier_delay', 0) + return ('estimated', max(produce_delay, supplier_delay)) + return super()._get_resupply_availability(route_info, components) diff --git a/addons/mrp_subcontracting/report/mrp_report_bom_structure.xml b/addons/mrp_subcontracting/report/mrp_report_bom_structure.xml deleted file mode 100644 index fc9fb7f978a..00000000000 --- a/addons/mrp_subcontracting/report/mrp_report_bom_structure.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.js b/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.js new file mode 100644 index 00000000000..69d1c3a48cf --- /dev/null +++ b/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.js @@ -0,0 +1,9 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { BomOverviewComponentsBlock } from "@mrp/components/bom_overview_components_block/mrp_bom_overview_components_block"; +import { BomOverviewSpecialLine } from "@mrp/components/bom_overview_special_line/mrp_bom_overview_special_line"; + +patch(BomOverviewComponentsBlock, "mrp_subcontracting", { + components: { ...BomOverviewComponentsBlock.components, BomOverviewSpecialLine }, +}); diff --git a/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.xml b/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.xml new file mode 100644 index 00000000000..c15bfbd7cc1 --- /dev/null +++ b/addons/mrp_subcontracting/static/src/components/bom_overview_components_block/mrp_bom_overview_components_block.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/addons/mrp_subcontracting/static/src/components/bom_overview_line/mrp_bom_overview_line.js b/addons/mrp_subcontracting/static/src/components/bom_overview_line/mrp_bom_overview_line.js new file mode 100644 index 00000000000..ac62ea922ce --- /dev/null +++ b/addons/mrp_subcontracting/static/src/components/bom_overview_line/mrp_bom_overview_line.js @@ -0,0 +1,16 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { BomOverviewLine } from "@mrp/components/bom_overview_line/mrp_bom_overview_line"; + +patch(BomOverviewLine.prototype, "mrp_subcontracting", { + /** + * @override + */ + async goToRoute(routeType) { + if (routeType == "subcontract") { + return this.goToAction(this.data.link_id, this.data.link_model); + } + return this._super(...arguments); + } +}); diff --git a/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.js b/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.js new file mode 100644 index 00000000000..e237d5a7a66 --- /dev/null +++ b/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.js @@ -0,0 +1,33 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { useService } from "@web/core/utils/hooks"; +import { BomOverviewSpecialLine } from "@mrp/components/bom_overview_special_line/mrp_bom_overview_special_line"; + +patch(BomOverviewSpecialLine.prototype, "mrp_subcontracting", { + setup() { + this._super.apply(); + this.actionService = useService("action"); + }, + + //---- Handlers ---- + + async goToSubcontractor() { + return this.actionService.doAction({ + type: "ir.actions.act_window", + res_model: "res.partner", + res_id: this.subcontracting.partner_id, + views: [[false, "form"]], + target: "current", + context: { + active_id: this.subcontracting.partner_id, + }, + }); + }, + + //---- Getters ---- + + get subcontracting() { + return this.props.data.subcontracting || {}; + }, +}); diff --git a/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.xml b/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.xml new file mode 100644 index 00000000000..3e86ea27148 --- /dev/null +++ b/addons/mrp_subcontracting/static/src/components/bom_overview_special_line/mrp_bom_overview_special_line.xml @@ -0,0 +1,26 @@ + + + + + + Subcontracting: + + + + + + + + + + + + + + + + + + + + diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py index 7ab0ff6c24a..a738cb26b70 100644 --- a/addons/mrp_subcontracting/tests/test_subcontracting.py +++ b/addons/mrp_subcontracting/tests/test_subcontracting.py @@ -591,28 +591,25 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=1, searchVariant=False) subcontracting_values = report_values['lines']['subcontracting'] self.assertEqual(subcontracting_values['name'], self.subcontractor_partner1.display_name) - self.assertEqual(report_values['lines']['total'], 20) # 10 For subcontracting + 5 for comp1 + 5 for subcontracting of comp2_bom - self.assertEqual(report_values['lines']['bom_cost'], 20) + self.assertEqual(report_values['lines']['bom_cost'], 20) # 10 For subcontracting + 5 for comp1 + 5 for subcontracting of comp2_bom self.assertEqual(subcontracting_values['bom_cost'], 10) self.assertEqual(subcontracting_values['prod_cost'], 10) - self.assertEqual(report_values['lines']['components'][0]['total'], 5) - self.assertEqual(report_values['lines']['components'][1]['total'], 5) + self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 5) + self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 5) report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=3, searchVariant=False) subcontracting_values = report_values['lines']['subcontracting'] - self.assertEqual(report_values['lines']['total'], 60) # 30 for subcontracting + 15 for comp1 + 15 for subcontracting of comp2_bom - self.assertEqual(report_values['lines']['bom_cost'], 60) + self.assertEqual(report_values['lines']['bom_cost'], 60) # 30 for subcontracting + 15 for comp1 + 15 for subcontracting of comp2_bom self.assertEqual(subcontracting_values['bom_cost'], 30) self.assertEqual(subcontracting_values['prod_cost'], 30) - self.assertEqual(report_values['lines']['components'][0]['total'], 15) - self.assertEqual(report_values['lines']['components'][1]['total'], 15) + self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 15) + self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 15) report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=5, searchVariant=False) subcontracting_values = report_values['lines']['subcontracting'] - self.assertEqual(report_values['lines']['total'], 80) # 50 for subcontracting + 25 for comp1 + 5 for subcontracting of comp2_bom - self.assertEqual(report_values['lines']['bom_cost'], 80) + self.assertEqual(report_values['lines']['bom_cost'], 80) # 50 for subcontracting + 25 for comp1 + 5 for subcontracting of comp2_bom self.assertEqual(subcontracting_values['bom_cost'], 50) self.assertEqual(subcontracting_values['prod_cost'], 50) - self.assertEqual(report_values['lines']['components'][0]['total'], 25) - self.assertEqual(report_values['lines']['components'][1]['total'], 5) + self.assertEqual(report_values['lines']['components'][0]['bom_cost'], 25) + self.assertEqual(report_values['lines']['components'][1]['bom_cost'], 5) def test_several_backorders(self): def process_picking(picking, qty):