[IMP] mrp: Add byproducts to MO Overview
Adds the `By-Products` section in the MO Overview as done in the BoM Overview. This allows to correctly factor in the cost share of the production's byproducts. Part of task-3251506 Part-of: odoo/odoo#114536
This commit is contained in:
@@ -54,12 +54,16 @@ class ReportMoOverview(models.AbstractModel):
|
||||
production = self.env['mrp.production'].browse(production_id)
|
||||
components = self._get_components_data(production, level=1, current_index='')
|
||||
operations = self._get_operations_data(production, level=1, current_index='')
|
||||
summary = self._get_mo_summary(production, components, operations)
|
||||
initial_mo_cost = sum(component.get('summary', {}).get('mo_cost', 0.0) for component in components)\
|
||||
+ operations.get('summary', {}).get('mo_cost', 0.0)
|
||||
byproducts_cost_portion, byproducts = self._get_byproducts_data(production, initial_mo_cost, level=1, current_index='')
|
||||
summary = self._get_mo_summary(production, components, initial_mo_cost, byproducts_cost_portion)
|
||||
return {
|
||||
'id': production.id,
|
||||
'summary': summary,
|
||||
'components': components,
|
||||
'operations': operations,
|
||||
'byproducts': byproducts,
|
||||
'extras': self._get_report_extra_lines(summary),
|
||||
}
|
||||
|
||||
@@ -73,10 +77,10 @@ class ReportMoOverview(models.AbstractModel):
|
||||
'unit_product_cost': unit_product_cost,
|
||||
}
|
||||
|
||||
def _get_mo_summary(self, production, components, operations):
|
||||
def _get_mo_summary(self, production, components, current_mo_cost, byproducts_cost_portion):
|
||||
product = production.product_id
|
||||
company = production.company_id or self.env.company
|
||||
mo_cost = sum(component.get('summary', {}).get('mo_cost', 0.0) for component in components)
|
||||
byproducts_cost_share = float_round(1 - byproducts_cost_portion, precision_rounding=0.0001)
|
||||
return {
|
||||
'level': 0,
|
||||
'model': production._name,
|
||||
@@ -93,7 +97,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
'quantity_on_hand': product.uom_id._compute_quantity(product.qty_available, production.product_uom_id) if product.type == 'product' else False,
|
||||
'quantity_reserved': 0.0,
|
||||
'receipt': self._check_planned_start(production.date_deadline, self._get_replenishment_receipt(production, components)),
|
||||
'mo_cost': company.currency_id.round(mo_cost + operations.get('summary', {}).get('mo_cost', 0.0)),
|
||||
'mo_cost': company.currency_id.round(current_mo_cost * byproducts_cost_share),
|
||||
'product_cost': company.currency_id.round(product.standard_price * production.product_uom_qty),
|
||||
'currency_id': company.currency_id.id,
|
||||
'currency': company.currency_id,
|
||||
@@ -187,6 +191,48 @@ class ReportMoOverview(models.AbstractModel):
|
||||
'details': operations,
|
||||
}
|
||||
|
||||
def _get_byproducts_data(self, production, current_mo_cost, level=0, current_index=False):
|
||||
currency = (production.company_id or self.env.company).currency_id
|
||||
byproducts = []
|
||||
byproducts_cost_portion = 0
|
||||
total_mo_cost = 0
|
||||
total_product_cost = 0
|
||||
for index, move_bp in enumerate(production.move_byproduct_ids):
|
||||
product = move_bp.product_id
|
||||
cost_share = move_bp.cost_share / 100
|
||||
byproducts_cost_portion += cost_share
|
||||
mo_cost = current_mo_cost * cost_share
|
||||
product_cost = product.standard_price * move_bp.product_uom._compute_quantity(move_bp.product_uom_qty, product.uom_id)
|
||||
total_mo_cost += mo_cost
|
||||
total_product_cost += product_cost
|
||||
byproducts.append({
|
||||
'level': level,
|
||||
'index': f"{current_index}B{index}",
|
||||
'model': product._name,
|
||||
'id': product.id,
|
||||
'name': product.display_name,
|
||||
'quantity': move_bp.product_uom_qty,
|
||||
'uom_name': move_bp.product_uom.display_name,
|
||||
'uom_precision': self._get_uom_precision(move_bp.product_uom.rounding),
|
||||
'mo_cost': currency.round(mo_cost),
|
||||
'mo_cost_decorator': self._get_comparison_decorator(product_cost, mo_cost, currency.rounding),
|
||||
'product_cost': currency.round(product_cost),
|
||||
'currency_id': currency.id,
|
||||
'currency': currency,
|
||||
})
|
||||
|
||||
return byproducts_cost_portion, {
|
||||
'summary': {
|
||||
'index': f"{current_index}B",
|
||||
'mo_cost': currency.round(total_mo_cost),
|
||||
'mo_cost_decorator': self._get_comparison_decorator(total_product_cost, total_mo_cost, currency.rounding),
|
||||
'product_cost': currency.round(total_product_cost),
|
||||
'currency_id': currency.id,
|
||||
'currency': currency,
|
||||
},
|
||||
'details': byproducts,
|
||||
}
|
||||
|
||||
def _get_components_data(self, production, replenish_data=False, level=0, current_index=False):
|
||||
if not replenish_data:
|
||||
replenish_data = {
|
||||
@@ -315,7 +361,13 @@ class ReportMoOverview(models.AbstractModel):
|
||||
if doc_in._name == 'mrp.production':
|
||||
replenishment['components'] = self._get_components_data(doc_in, replenish_data, level + 2, replenishment_index)
|
||||
replenishment['operations'] = self._get_operations_data(doc_in, level + 2, replenishment_index)
|
||||
replenishment['summary']['mo_cost'] = currency.round(sum(component.get('summary', {}).get('mo_cost', 0.0) for component in replenishment['components']) + replenishment['operations'].get('summary', {}).get('mo_cost', 0.0))
|
||||
initial_mo_cost = sum(component.get('summary', {}).get('mo_cost', 0.0) for component in replenishment['components'])\
|
||||
+ replenishment['operations'].get('summary', {}).get('mo_cost', 0.0)
|
||||
|
||||
byproducts_cost_portion, byproducts = self._get_byproducts_data(doc_in, initial_mo_cost, level + 2, replenishment_index)
|
||||
byproducts_cost_share = float_round(1 - byproducts_cost_portion, precision_rounding=0.0001)
|
||||
replenishment['byproducts'] = byproducts
|
||||
replenishment['summary']['mo_cost'] = initial_mo_cost * byproducts_cost_share
|
||||
replenishment['summary']['receipt'] = self._check_planned_start(production.date_start, self._get_replenishment_receipt(doc_in, replenishment.get('components', [])))
|
||||
replenishments.append(replenishment)
|
||||
forecast_line['already_used'] = True
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<t t-call="mrp.mo_overview_components">
|
||||
<t t-set="components" t-value="data['components']"/>
|
||||
<t t-set="operations" t-value="data['operations']"/>
|
||||
<t t-set="byproducts" t-value="data['byproducts']"/>
|
||||
</t>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@@ -99,6 +100,7 @@
|
||||
<t t-call="mrp.mo_overview_components">
|
||||
<t t-set="components" t-value="replenishment.get('components', [])"/>
|
||||
<t t-set="operations" t-value="replenishment.get('operations', {})"/>
|
||||
<t t-set="byproducts" t-value="replenishment.get('byproducts', {})"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
@@ -107,6 +109,10 @@
|
||||
<t t-set="summary" t-value="operations.get('summary', {})"/>
|
||||
<t t-set="operations" t-value="operations.get('details', [])"/>
|
||||
</t>
|
||||
<t t-call="mrp.mo_overview_byproducts">
|
||||
<t t-set="summary" t-value="byproducts.get('summary', {})"/>
|
||||
<t t-set="byproducts" t-value="byproducts.get('details', [])"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="mo_overview_operations">
|
||||
@@ -137,4 +143,32 @@
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="mo_overview_byproducts">
|
||||
<t t-if="byproducts">
|
||||
<tr>
|
||||
<td class="text-start">
|
||||
<span t-attf-style="margin-left: {{ (byproducts[0]['level'] - 1 if len(byproducts) else 0) * 20 }}px"/>
|
||||
By-Products
|
||||
</td>
|
||||
<td t-if="data['show_replenishments']" class="text-center"/>
|
||||
<td class="text-end"/>
|
||||
<td t-if="data['show_uom']" class="text-start"/>
|
||||
<td t-if="data['show_availabilities']" class="text-end"/>
|
||||
<td t-if="data['show_availabilities']" class="text-end"/>
|
||||
<td t-if="data['show_receipts']" class="text-end"/>
|
||||
<td t-if="data['show_mo_costs']" t-attf-class="text-end {{ 'text-' + summary['mo_cost_decorator'] if summary.get('mo_cost_decorator') else '' }}">
|
||||
<t t-out="summary['mo_cost']" t-options="{'widget': 'monetary', 'display_currency': summary['currency']}"/>
|
||||
</td>
|
||||
<td t-if="data['show_product_costs']" class="text-end">
|
||||
<t t-out="summary['product_cost']" t-options="{'widget': 'monetary', 'display_currency': summary['currency']}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-if="summary['index'] in data['unfolded_ids']" t-foreach="byproducts" t-as="byproduct">
|
||||
<t t-call="mrp.mo_overview_line">
|
||||
<t t-set="line" t-value="byproduct"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -50,10 +50,13 @@ export class MoOverview extends Component {
|
||||
}
|
||||
this.state.showOptions.uom = reportValues.context.show_uom;
|
||||
this.context = reportValues.context;
|
||||
// Main MO's operations & byproducts are always unfolded by default.
|
||||
if (reportValues.data?.operations?.summary?.index) {
|
||||
// Main MO's operations are always unfolded by default.
|
||||
this.unfoldedIds.add(reportValues.data.operations.summary.index);
|
||||
}
|
||||
if (reportValues.data?.byproducts?.summary?.index) {
|
||||
this.unfoldedIds.add(reportValues.data.byproducts.summary.index);
|
||||
}
|
||||
}
|
||||
|
||||
//---- Handlers ----
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<MoOverviewComponentsBlock
|
||||
components="state.data.components"
|
||||
operations="state.data.operations"
|
||||
byproducts="state.data.byproducts"
|
||||
showOptions="state.showOptions"/>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { MoOverviewOperationsBlock } from "../mo_overview_operations_block/mrp_mo_overview_operations_block";
|
||||
import { MoOverviewLine } from "../mo_overview_line/mrp_mo_overview_line";
|
||||
|
||||
export class MoOverviewByproductsBlock extends MoOverviewOperationsBlock {
|
||||
static template = "mrp.MoOverviewByproductsBlock";
|
||||
|
||||
//---- Getters ----
|
||||
|
||||
get hasByproducts() {
|
||||
return this.props?.byproducts?.length > 0;
|
||||
}
|
||||
|
||||
get level() {
|
||||
return this.hasByproducts ? this.props.byproducts[0].level - 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
MoOverviewByproductsBlock.components = {
|
||||
MoOverviewLine,
|
||||
};
|
||||
MoOverviewByproductsBlock.props = {
|
||||
// Keep all props except "operations"
|
||||
...(({ operations, ...props}) => props)(MoOverviewOperationsBlock.props),
|
||||
byproducts: Array,
|
||||
};
|
||||
MoOverviewByproductsBlock.props.summary.shape = {
|
||||
...MoOverviewByproductsBlock.props.summary.shape,
|
||||
product_cost: { type: Number, optional: true },
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp.MoOverviewByproductsBlock" owl="1">
|
||||
<t t-if="hasByproducts">
|
||||
<tr>
|
||||
<td class="text-start">
|
||||
<span t-attf-style="margin-left: {{ level * 20 }}px"/>
|
||||
<button t-on-click="toggleFolded" class="btn btn-light ps-0 py-0" t-attf-aria-label="{{ state.isFolded ? 'Unfold' : 'Fold' }}" t-attf-title="{{ state.isFolded ? 'Unfold' : 'Fold' }}">
|
||||
<i t-attf-class="fa fa-fw fa-caret-{{ state.isFolded ? 'right' : 'down' }}" role="img"/>
|
||||
By-Products
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-center" t-if="props.showOptions.replenishments"/>
|
||||
<td class="text-end"/>
|
||||
<td class="text-start" t-if="props.showOptions.uom"/>
|
||||
<td class="text-end" t-if="props.showOptions.availabilities"/>
|
||||
<td class="text-end" t-if="props.showOptions.availabilities"/>
|
||||
<td class="text-end" t-if="props.showOptions.receipts"/>
|
||||
<td t-attf-class="text-end {{ getColorClass(props.summary.mo_cost_decorator) }}" t-if="props.showOptions.moCosts" t-out="formatMonetary(props.summary.mo_cost)"/>
|
||||
<td class="text-end" t-if="props.showOptions.productCosts" t-out="formatMonetary(props.summary.product_cost)"/>
|
||||
</tr>
|
||||
<t t-if="!state.isFolded" t-foreach="props.byproducts" t-as="byproduct" t-key="byproduct.index">
|
||||
<MoOverviewLine data="byproduct" showOptions="props.showOptions"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+10
@@ -4,6 +4,7 @@ import { Component, onWillUpdateProps, useState } from "@odoo/owl";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
import { MoOverviewLine } from "../mo_overview_line/mrp_mo_overview_line";
|
||||
import { MoOverviewOperationsBlock } from "../mo_overview_operations_block/mrp_mo_overview_operations_block";
|
||||
import { MoOverviewByproductsBlock } from "../mo_overview_byproducts_block/mrp_mo_overview_byproducts_block";
|
||||
|
||||
export class MoOverviewComponentsBlock extends Component {
|
||||
static template = "mrp.MoOverviewComponentsBlock";
|
||||
@@ -81,6 +82,7 @@ export class MoOverviewComponentsBlock extends Component {
|
||||
MoOverviewComponentsBlock.components = {
|
||||
MoOverviewLine,
|
||||
MoOverviewOperationsBlock,
|
||||
MoOverviewByproductsBlock,
|
||||
MoOverviewComponentsBlock,
|
||||
};
|
||||
MoOverviewComponentsBlock.props = {
|
||||
@@ -94,6 +96,14 @@ MoOverviewComponentsBlock.props = {
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
byproducts: {
|
||||
type: Object,
|
||||
shape: {
|
||||
summary: Object,
|
||||
details: Array,
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
showOptions: {
|
||||
type: Object,
|
||||
shape: {
|
||||
|
||||
+6
@@ -22,6 +22,7 @@
|
||||
unfoldAll="state.unfoldAll"
|
||||
components="replenishment.components"
|
||||
operations="replenishment.operations"
|
||||
byproducts="replenishment.byproducts"
|
||||
showOptions="props.showOptions"/>
|
||||
</t>
|
||||
</t>
|
||||
@@ -31,6 +32,11 @@
|
||||
summary="props.operations.summary"
|
||||
operations="props.operations.details"
|
||||
showOptions="props.showOptions"/>
|
||||
<MoOverviewByproductsBlock
|
||||
unfoldAll="state.unfoldAll"
|
||||
summary="props.byproducts.summary"
|
||||
byproducts="props.byproducts.details"
|
||||
showOptions="props.showOptions"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
Reference in New Issue
Block a user