[REF] mrp_subcontracting: Adapt subcontracting for BoM Overview

The 'Structure & Cost' report has been changed to 'Overview', changing
some of its functionnalities. The override previously done in
mrp_subcontracting must be adapted to match the changes done in the mrp
module.

Also, the report has been converted to OWL in the process, requiring
changes in the overriden files as well.

Finally, the now obsolete code only related to the old 'Structure &
Cost' report has been removed.

Task-2628323

Part-of: odoo/odoo#93194
This commit is contained in:
clesgow
2022-08-26 17:16:41 +02:00
parent 42f1d31130
commit 5e2d48fbfa
9 changed files with 184 additions and 84 deletions
+6 -1
View File
@@ -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',
@@ -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)
@@ -1,33 +0,0 @@
<odoo>
<template id="report_mrp_bom_line_inherit_mrp_subcontracting" inherit_id="mrp.report_mrp_bom_line">
<xpath expr="//t[@name='operations']" position="after">
<t t-if="data.get('subcontracting')">
<t t-set="space_td" t-value="'margin-left: '+ str(data['subcontracting']['level'] * 20) + 'px;'"/>
<tr class="o_mrp_bom_report_line" t-att-parent_id="data['bom'].id">
<td name="td_subcontracting">
<div t-att-style="space_td">
<div class="o_mrp_bom_no_fold">
Subcontracting: <a href="#" class="o_mrp_bom_action" t-att-data-res-id="data['subcontracting']['partner_id']" t-att-data-model="'res.partner'"><t t-esc="data['subcontracting']['name']"/></a>
</div>
</div>
</td>
<td/>
<t t-foreach="range(data.get('extra_column_count', 0))" t-as="index">
<td/>
</t>
<td class="text-end">
<span t-esc="data['subcontracting']['quantity']" t-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/>
</td>
<td groups="uom.group_uom"><span t-esc="data['subcontracting']['uom']"/></td>
<td class="o_mrp_prod_cost text-end">
<span t-esc="data['subcontracting']['prod_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
<td class="o_mrp_bom_cost text-end">
<span t-esc="data['subcontracting']['bom_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
<td/>
</tr>
</t>
</xpath>
</template>
</odoo>
@@ -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 },
});
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mrp_subcontracting.BomOverviewComponentsBlock" t-inherit="mrp.BomOverviewComponentsBlock" t-inherit-mode="extension" owl="1">
<xpath expr="//t[@name='byproducts']" position="after">
<t t-if="data.subcontracting">
<BomOverviewSpecialLine
bus="props.bus"
type="'subcontracting'"
showOptions="props.showOptions"
data="props.data"/>
</t>
</xpath>
</t>
</templates>
@@ -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);
}
});
@@ -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 || {};
},
});
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mrp_subcontracting.BomOverviewSpecialLine" t-inherit="mrp.BomOverviewSpecialLine" t-inherit-mode="extension" owl="1">
<xpath expr="//td[@name='td_mrp_bom']" position="inside">
<t t-if="props.type == 'subcontracting'">Subcontracting: <a href="#" t-on-click.prevent="goToSubcontractor" t-esc="subcontracting.name"/></t>
</xpath>
<xpath expr="//td[@name='quantity']" position="inside">
<span t-if="props.type == 'subcontracting'" t-esc="formatFloat(subcontracting.quantity)"/>
</xpath>
<xpath expr="//td[@name='uom']" position="inside">
<span t-if="props.type == 'subcontracting'" t-esc="subcontracting.uom"/>
</xpath>
<xpath expr="//td[@name='bom_cost']" position="inside">
<span t-if="props.type == 'subcontracting'" t-esc="formatMonetary(subcontracting.bom_cost)"/>
</xpath>
<xpath expr="//td[@name='prod_cost']" position="inside">
<span t-if="props.type == 'subcontracting'" t-esc="formatMonetary(subcontracting.prod_cost)"/>
</xpath>
</t>
</templates>
@@ -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):