From ebef2c3e4f53eda7f1c06ad96cdd9352941930e7 Mon Sep 17 00:00:00 2001 From: clesgow Date: Fri, 1 Sep 2023 10:50:05 +0200 Subject: [PATCH] [IMP] mrp{,_account}: Integrate Cost Analysis data into Overview The point of the MO Overview is to be a single place where you can quickly get all info for a single Manufacturing Order. Since we have all data the cost_analysis is already fetching, we add those data in the report when the production is done. Also use the Stock Valuation Layers when available for component costs to better reflect the real cost of production. Task-3251506 Part-of: odoo/odoo#114536 --- addons/mrp/report/mrp_report_mo_overview.py | 127 +++++++++++++++++- addons/mrp/report/mrp_report_mo_overview.xml | 88 +++++++++++- .../components/mo_overview/mrp_mo_overview.js | 20 ++- .../mo_overview/mrp_mo_overview.xml | 60 ++++++++- .../mrp_mo_overview_byproducts_block.xml | 1 + .../mrp_mo_overview_display_filter.js | 2 + .../mo_overview_line/mrp_mo_overview_line.js | 1 + .../mo_overview_line/mrp_mo_overview_line.xml | 1 + .../mrp_mo_overview_operations_block.js | 10 +- .../mrp_mo_overview_operations_block.xml | 3 +- addons/mrp_account/__init__.py | 1 + addons/mrp_account/report/__init__.py | 4 + .../report/mrp_report_mo_overview.py | 16 +++ 13 files changed, 318 insertions(+), 16 deletions(-) create mode 100644 addons/mrp_account/report/__init__.py create mode 100644 addons/mrp_account/report/mrp_report_mo_overview.py diff --git a/addons/mrp/report/mrp_report_mo_overview.py b/addons/mrp/report/mrp_report_mo_overview.py index 27343f4c6af..5a9051ef62d 100644 --- a/addons/mrp/report/mrp_report_mo_overview.py +++ b/addons/mrp/report/mrp_report_mo_overview.py @@ -49,6 +49,9 @@ class ReportMoOverview(models.AbstractModel): doc['show_receipts'] = data.get('receipts') == '1' if doc['show_receipts']: footer_colspan += 1 + doc['show_unit_costs'] = data.get('unitCosts') == '1' + if doc['show_unit_costs']: + footer_colspan += 1 doc['show_mo_costs'] = data.get('moCosts') == '1' doc['show_real_costs'] = data.get('realCosts') == '1' doc['show_uom'] = self.env.user.user_has_groups('uom.group_uom') @@ -76,6 +79,7 @@ class ReportMoOverview(models.AbstractModel): initial_mo_cost, initial_real_cost = self._compute_cost_sums(components, operations) remaining_cost_share, byproducts = self._get_byproducts_data(production, initial_mo_cost, initial_real_cost, level=1, current_index='') summary = self._get_mo_summary(production, components, initial_mo_cost, initial_real_cost, remaining_cost_share) + extra_lines = self._get_report_extra_lines(summary, components, operations, production.state == 'done') return { 'id': production.id, 'name': production.display_name, @@ -83,18 +87,77 @@ class ReportMoOverview(models.AbstractModel): 'components': components, 'operations': operations, 'byproducts': byproducts, - 'extras': self._get_report_extra_lines(summary), + 'extras': extra_lines, + 'cost_breakdown': self._get_cost_breakdown_data(production, extra_lines, remaining_cost_share), } - def _get_report_extra_lines(self, summary): - currency = summary.get('currency', self.env.company) + def _get_report_extra_lines(self, summary, components, operations, production_done=False): + currency = summary.get('currency', self.env.company.currency_id) unit_mo_cost = currency.round(summary.get('mo_cost', 0) / summary.get('quantity', 1)) unit_real_cost = currency.round(summary.get('real_cost', 0) / summary.get('quantity', 1)) - return { + extras = { 'unit_mo_cost': unit_mo_cost, 'unit_mo_cost_decorator': self._get_comparison_decorator(unit_real_cost, unit_mo_cost, currency.rounding), 'unit_real_cost': unit_real_cost, } + if production_done: + production_qty = summary.get('quantity', 1.0) + extras['total_mo_cost_components'] = sum(compo.get('summary', {}).get('mo_cost', 0.0) for compo in components) + extras['total_real_cost_components'] = sum(compo.get('summary', {}).get('real_cost', 0.0) for compo in components) + extras['total_mo_cost_components_decorator'] = self._get_comparison_decorator(extras['total_real_cost_components'], extras['total_mo_cost_components'], currency.rounding) + extras['unit_mo_cost_components'] = extras['total_mo_cost_components'] / production_qty + extras['unit_real_cost_components'] = extras['total_real_cost_components'] / production_qty + extras['total_mo_cost_operations'] = operations.get('summary', {}).get('mo_cost', 0.0) + extras['total_real_cost_operations'] = operations.get('summary', {}).get('real_cost', 0.0) + extras['total_mo_cost_operations_decorator'] = self._get_comparison_decorator(extras['total_real_cost_operations'], extras['total_mo_cost_operations'], currency.rounding) + extras['unit_mo_cost_operations'] = extras['total_mo_cost_operations'] / production_qty + extras['unit_real_cost_operations'] = extras['total_real_cost_operations'] / production_qty + extras['total_mo_cost'] = extras['total_mo_cost_components'] + extras['total_mo_cost_operations'] + extras['total_real_cost'] = extras['total_real_cost_components'] + extras['total_real_cost_operations'] + extras['total_mo_cost_decorator'] = self._get_comparison_decorator(extras['total_real_cost'], extras['total_mo_cost'], currency.rounding) + return extras + + def _get_cost_breakdown_data(self, production, extras, remaining_cost_share): + if production.state != 'done' or not production.move_byproduct_ids: + return [] + + # Prepare byproducts data + quantities_by_product = defaultdict(float) + total_cost_by_product = defaultdict(float) + component_cost_by_product = defaultdict(float) + operation_cost_by_product = defaultdict(float) + for bp_move in production.move_byproduct_ids: + # Byproducts without cost share are irrelevant in a cost breakdrown. + if bp_move.state == 'cancel' or float_is_zero(bp_move.cost_share, precision_digits=2): + continue + # As UoMs can vary, we use the default UoM of each product + quantities_by_product[bp_move.product_id] += bp_move.product_qty + cost_share = bp_move.cost_share / 100 + total_cost_by_product[bp_move.product_id] += extras['total_real_cost'] * cost_share + component_cost_by_product[bp_move.product_id] += extras['total_real_cost_components'] * cost_share + operation_cost_by_product[bp_move.product_id] += extras['total_real_cost_operations'] * cost_share + + # Add finished product to its own default UoM (not the production UoM) + breakdown_lines = [self._format_cost_breakdown_lines(0, production.product_id.display_name, production.product_id.uom_id.display_name, + (extras['total_real_cost_components'] * remaining_cost_share) / production.product_uom_qty, + (extras['total_real_cost_operations'] * remaining_cost_share) / production.product_uom_qty, + (extras['total_real_cost'] * remaining_cost_share) / production.product_uom_qty)] + for index, product in enumerate(quantities_by_product.keys()): + breakdown_lines.append(self._format_cost_breakdown_lines(index + 1, product.display_name, product.uom_id.display_name, + component_cost_by_product[product] / quantities_by_product[product], + operation_cost_by_product[product] / quantities_by_product[product], + total_cost_by_product[product] / quantities_by_product[product])) + return breakdown_lines + + def _format_cost_breakdown_lines(self, index, product_name, uom_name, component_cost, operation_cost, total_cost): + return { + 'index': f"BR{index}", + 'name': product_name, + 'unit_avg_cost_component': component_cost, + 'unit_avg_cost_operation': operation_cost, + 'unit_avg_total_cost': total_cost, + 'uom_name': uom_name, + } def _get_mo_summary(self, production, components, current_mo_cost, current_real_cost, remaining_cost_share): currency = (production.company_id or self.env.company).currency_id @@ -117,6 +180,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)), + 'unit_cost': self._get_unit_cost(production.move_finished_ids.filtered(lambda m: m.product_id == production.product_id)), 'mo_cost': currency.round(mo_cost), 'mo_cost_decorator': self._get_comparison_decorator(real_cost, mo_cost, currency.rounding), 'real_cost': currency.round(real_cost), @@ -124,6 +188,9 @@ class ReportMoOverview(models.AbstractModel): 'currency': currency, } + def _get_unit_cost(self, move): + return move.product_id.uom_id._compute_price(move.product_id.standard_price, move.product_uom) + def _format_state(self, record): if record._name != 'mrp.production' or record.state not in ('draft', 'confirmed') or not record.move_raw_ids: return dict(record._fields['state']._description_selection(self.env)).get(record.state) @@ -165,6 +232,8 @@ class ReportMoOverview(models.AbstractModel): return 'success' def _get_operations_data(self, production, level=0, current_index=False): + if production.state == "done": + return self._get_finished_operation_data(production, level, current_index) currency = (production.company_id or self.env.company).currency_id operation_uom = _("Minutes") operations = [] @@ -202,6 +271,7 @@ class ReportMoOverview(models.AbstractModel): 'quantity_decorator': self._get_comparison_decorator(workorder.duration_expected, wo_duration, 0.01), 'uom_name': operation_uom, 'production_id': production.id, + 'unit_cost': expected_cost / workorder.duration_expected, 'mo_cost': mo_cost, 'real_cost': real_cost, 'currency_id': currency.id, @@ -226,6 +296,46 @@ class ReportMoOverview(models.AbstractModel): 'details': operations, } + def _get_finished_operation_data(self, production, level=0, current_index=False): + currency = (production.company_id or self.env.company).currency_id + done_operation_uom = _("Hours") + operations = [] + total_duration = total_duration_expected = total_cost = 0 + for index, workorder in enumerate(production.workorder_ids): + hourly_cost = workorder.costs_hour or workorder.workcenter_id.costs_hour + duration = workorder.get_duration() / 60 + total_duration += duration + total_duration_expected += workorder.duration_expected + operation_cost = duration * hourly_cost + total_cost += operation_cost + operations.append({ + 'level': level, + 'index': f"{current_index}W{index}", + 'name': f"{workorder.workcenter_id.display_name}: {workorder.display_name}", + 'quantity': duration, + 'uom_name': done_operation_uom, + 'uom_precision': 4, + 'unit_cost': hourly_cost, + 'mo_cost': currency.round(operation_cost), + 'real_cost': currency.round(operation_cost), + 'currency_id': currency.id, + 'currency': currency, + }) + return { + 'summary': { + 'index': f"{current_index}W", + 'done': True, + 'quantity': total_duration, + 'quantity_decorator': self._get_comparison_decorator(total_duration_expected, total_duration, 0.01), + 'mo_cost': total_cost, + 'real_cost': total_cost, + 'uom_name': done_operation_uom, + 'currency_id': currency.id, + 'currency': currency, + }, + 'details': operations, + } + def _get_byproducts_data(self, production, current_mo_cost, current_real_cost, level=0, current_index=False): currency = (production.company_id or self.env.company).currency_id byproducts = [] @@ -249,6 +359,7 @@ class ReportMoOverview(models.AbstractModel): '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), + 'unit_cost': self._get_unit_cost(move_bp), 'mo_cost': currency.round(mo_cost), 'mo_cost_decorator': self._get_comparison_decorator(real_cost, mo_cost, currency.rounding), 'real_cost': currency.round(real_cost), @@ -328,6 +439,7 @@ class ReportMoOverview(models.AbstractModel): 'quantity_on_hand': product.uom_id._compute_quantity(product.qty_available, move_raw.product_uom) if product.type == 'product' else False, 'quantity_reserved': self._get_reserved_qty(move_raw, production.warehouse_id, replenish_data), 'receipt': self._check_planned_start(production.date_start, self._get_component_receipt(product, move_raw, production.warehouse_id, replenishments, replenish_data)), + 'unit_cost': self._get_unit_cost(move_raw), 'mo_cost': currency.round(replenish_mo_cost + missing_quantity_cost), 'real_cost': currency.round(self._get_component_real_cost(move_raw, move_raw.product_uom_qty)), 'currency_id': currency.id, @@ -346,8 +458,7 @@ class ReportMoOverview(models.AbstractModel): def _get_component_real_cost(self, move_raw, quantity): if float_is_zero(quantity, precision_rounding=move_raw.product_uom.rounding): return 0 - product = move_raw.product_id - return product.standard_price * move_raw.product_uom._compute_quantity(quantity, product.uom_id) + return self._get_unit_cost(move_raw) * quantity def _check_planned_start(self, mo_planned_start, receipt): if mo_planned_start and receipt.get('date', False) and receipt['date'] > mo_planned_start: @@ -413,8 +524,9 @@ class ReportMoOverview(models.AbstractModel): 'quantity': line_quantity, 'uom_name': move_raw.product_uom.display_name, 'uom_precision': self._get_uom_precision(forecast_line['uom_id']['rounding']), + 'unit_cost': self._get_unit_cost(move_raw), 'mo_cost': forecast_line.get('cost', self._get_replenishment_mo_cost(product, line_quantity, move_raw.product_uom, currency, forecast_line.get('move_in'))), - 'real_cost': currency.round(move_raw.product_uom._compute_quantity(line_quantity, product.uom_id) * product.standard_price), + 'real_cost': currency.round(self._get_component_real_cost(move_raw, line_quantity)), 'currency_id': currency.id, 'currency': currency, } @@ -474,6 +586,7 @@ class ReportMoOverview(models.AbstractModel): else: to_order_line['summary']['mo_cost'] = currency.round(product.standard_price * move_raw.product_uom._compute_quantity(missing_quantity, product.uom_id)) to_order_line['summary']['receipt'] = self._format_receipt_date('unavailable') + to_order_line['summary']['unit_cost'] = currency.round(to_order_line['summary']['mo_cost'] / missing_quantity) to_order_line['summary']['mo_cost_decorator'] = self._get_comparison_decorator(to_order_line['summary']['real_cost'], to_order_line['summary']['mo_cost'], currency.rounding) replenishments.append(to_order_line) diff --git a/addons/mrp/report/mrp_report_mo_overview.xml b/addons/mrp/report/mrp_report_mo_overview.xml index 28e8fdc8755..92155369a74 100644 --- a/addons/mrp/report/mrp_report_mo_overview.xml +++ b/addons/mrp/report/mrp_report_mo_overview.xml @@ -25,6 +25,7 @@ Free to use / On Hand Reserved Receipt + Unit Cost MO Cost Real Cost @@ -46,7 +47,83 @@ + + + Total Cost of Components + + + + + + + + + + Cost of Components per unit + (in ) + + + + + + + + + + Total Cost of Operations + + + + + + + + + + Cost of Operations per unit + (in ) + + + + + + + + + + Total Cost of Production + + + + + + + + + +

Cost Breakdown of Products

+ + + + + + + + + + + + + + +
ProductAvg Cost of Components per UnitAvg Cost of Operations per UnitAvg Total Cost per UnitUnit of Measure
+ + + + +
+
@@ -73,6 +150,9 @@ + + + @@ -119,13 +199,14 @@ - - - + + + + @@ -154,6 +235,7 @@ + diff --git a/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.js b/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.js index a05f94fe37b..0184f0192dc 100644 --- a/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.js +++ b/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.js @@ -21,7 +21,6 @@ export class MoOverview extends Component { this.state = useState({ data: {}, - isDone: false, showOptions: this.getDefaultConfig(), }); @@ -41,12 +40,12 @@ export class MoOverview extends Component { [this.activeId], ); this.state.data = reportValues.data; - this.state.isDone = ['done', 'cancel'].some(doneState => reportValues.data?.summary?.state === doneState); - if (this.state.isDone) { + if (this.isProductionDone) { // Hide Availabilities / Receipts / Status columns when the MO is done. this.state.showOptions.availabilities = false; this.state.showOptions.receipts = false; this.state.showOptions.replenishments = false; + this.state.showOptions.unitCosts = true; } this.state.showOptions.uom = reportValues.context.show_uom; this.context = reportValues.context; @@ -92,6 +91,7 @@ export class MoOverview extends Component { replenishments: true, availabilities: true, receipts: true, + unitCosts: false, moCosts: true, realCosts: true, }; @@ -127,6 +127,10 @@ export class MoOverview extends Component { return this.state.showOptions.receipts; } + get showUnitCosts() { + return this.state.showOptions.unitCosts; + } + get showMoCosts() { return this.state.showOptions.moCosts; } @@ -139,12 +143,21 @@ export class MoOverview extends Component { return this.state.data?.summary?.state === "done"; } + get hasOperations() { + return this.state.data?.operations?.details?.length > 0; + } + + get hasBreakdown() { + return this.state.data?.cost_breakdown?.length > 0; + } + get totalColspan() { let colspan = 2; // Name & Quantity if (this.showReplenishments) colspan++; if (this.showAvailabilities) colspan += 2; // Free to use / On Hand & Reserved if (this.showUom) colspan++; if (this.showReceipts) colspan++; + if (this.showUnitCosts) colspan++; return colspan; } @@ -153,6 +166,7 @@ export class MoOverview extends Component { + `&replenishments=${+this.state.showOptions.replenishments}` + `&availabilities=${+this.state.showOptions.availabilities}` + `&receipts=${+this.state.showOptions.receipts}` + + `&unitCosts=${+this.state.showOptions.unitCosts}` + `&moCosts=${+this.state.showOptions.moCosts}` + `&realCosts=${+this.state.showOptions.realCosts}` + `&unfoldedIds=${JSON.stringify(Array.from(this.unfoldedIds))}`; diff --git a/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.xml b/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.xml index 64a29168660..366d1ad3580 100644 --- a/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.xml +++ b/addons/mrp/static/src/components/mo_overview/mrp_mo_overview.xml @@ -4,7 +4,7 @@
- + @@ -24,6 +24,7 @@ Free to use / On Hand Reserved Receipt + Unit Cost MO Cost Real Cost @@ -42,8 +43,65 @@ + + + Total Cost of Components + + + + + + Cost of Components per unit + (in ) + + + + + + Total Cost of Operations + + + + + + Cost of Operations per unit + (in ) + + + + + + Total Cost of Production + + + + + +
+

Cost Breakdown of Products

+ + + + + + + + + + + + + + +
ProductAvg Cost of Components per UnitAvg Cost of Operations per UnitAvg Total Cost per UnitUnit of Measure
+ + + + +
+
diff --git a/addons/mrp/static/src/components/mo_overview_byproducts_block/mrp_mo_overview_byproducts_block.xml b/addons/mrp/static/src/components/mo_overview_byproducts_block/mrp_mo_overview_byproducts_block.xml index a1c52d59f5c..987730c60d1 100644 --- a/addons/mrp/static/src/components/mo_overview_byproducts_block/mrp_mo_overview_byproducts_block.xml +++ b/addons/mrp/static/src/components/mo_overview_byproducts_block/mrp_mo_overview_byproducts_block.xml @@ -17,6 +17,7 @@ + diff --git a/addons/mrp/static/src/components/mo_overview_display_filter/mrp_mo_overview_display_filter.js b/addons/mrp/static/src/components/mo_overview_display_filter/mrp_mo_overview_display_filter.js index a41d2fe9350..dff4e2e0527 100644 --- a/addons/mrp/static/src/components/mo_overview_display_filter/mrp_mo_overview_display_filter.js +++ b/addons/mrp/static/src/components/mo_overview_display_filter/mrp_mo_overview_display_filter.js @@ -10,6 +10,7 @@ export const SHOW_OPTIONS = { replenishments: Boolean, availabilities: Boolean, receipts: Boolean, + unitCosts: Boolean, moCosts: Boolean, realCosts: Boolean, }, @@ -26,6 +27,7 @@ export class MoOverviewDisplayFilter extends BomOverviewDisplayFilter { } this.displayOptions = { ...(this.displayOptions || {}), + unitCosts: _t("Unit Costs"), moCosts: _t("MO Costs"), realCosts: _t("Real Costs"), }; diff --git a/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.js b/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.js index 60756986a4c..942ac3fe96e 100644 --- a/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.js +++ b/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.js @@ -158,6 +158,7 @@ MoOverviewLine.props = { }, optional: true, }, + unit_cost: { type: Number, optional: true }, mo_cost: Number, mo_cost_decorator: { type: [String, Boolean], optional: true }, real_cost: Number, diff --git a/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.xml b/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.xml index 722db97f07b..8713e590a53 100644 --- a/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.xml +++ b/addons/mrp/static/src/components/mo_overview_line/mrp_mo_overview_line.xml @@ -35,6 +35,7 @@
+ diff --git a/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.js b/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.js index 278200fb5e1..085497fbebc 100644 --- a/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.js +++ b/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.js @@ -2,7 +2,7 @@ import { Component, useState } from "@odoo/owl"; import { useBus } from "@web/core/utils/hooks"; -import { formatFloatTime, formatMonetary } from "@web/views/fields/formatters"; +import { formatFloat, formatFloatTime, formatMonetary } from "@web/views/fields/formatters"; import { MoOverviewLine } from "../mo_overview_line/mrp_mo_overview_line"; import { SHOW_OPTIONS } from "../mo_overview_display_filter/mrp_mo_overview_display_filter"; @@ -57,6 +57,13 @@ export class MoOverviewOperationsBlock extends Component { get index() { return this.props.summary.index; } + + get totalQuantity() { + // Float for Hours when displaying done productions, FloatTime for Minutes otherwise. + return this.props.summary?.done ? + formatFloat(this.props.summary.quantity, { digits: [false, this.props.operations[0].uom_precision || undefined] }) : + formatFloatTime(this.props.summary.quantity) + } } MoOverviewOperationsBlock.components = { @@ -77,6 +84,7 @@ MoOverviewOperationsBlock.props = { uom_name: { type: String, optional: true }, currency_id: { type: Number, optional: true }, currency: { type: String, optional: true }, + done: { type: Boolean, optional: true }, }, }, showOptions: SHOW_OPTIONS, diff --git a/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.xml b/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.xml index 2ab7d406a90..cde612b3a0e 100644 --- a/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.xml +++ b/addons/mrp/static/src/components/mo_overview_operations_block/mrp_mo_overview_operations_block.xml @@ -12,11 +12,12 @@ - + + diff --git a/addons/mrp_account/__init__.py b/addons/mrp_account/__init__.py index 274ebd30be4..77019e2f005 100644 --- a/addons/mrp_account/__init__.py +++ b/addons/mrp_account/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import report def _configure_journals(env): diff --git a/addons/mrp_account/report/__init__.py b/addons/mrp_account/report/__init__.py new file mode 100644 index 00000000000..37b544e72ce --- /dev/null +++ b/addons/mrp_account/report/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mrp_report_mo_overview diff --git a/addons/mrp_account/report/mrp_report_mo_overview.py b/addons/mrp_account/report/mrp_report_mo_overview.py new file mode 100644 index 00000000000..6008f8caf4f --- /dev/null +++ b/addons/mrp_account/report/mrp_report_mo_overview.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.tools import float_is_zero + +class ReportMoOverview(models.AbstractModel): + _inherit = 'report.mrp.report_mo_overview' + + def _get_unit_cost(self, move): + valuation_layers = move.sudo().stock_valuation_layer_ids + layers_quantity = sum(valuation_layers.mapped('quantity')) + if valuation_layers and not float_is_zero(layers_quantity, precision_rounding=valuation_layers.uom_id.rounding): + unit_price = sum(valuation_layers.mapped('value')) / layers_quantity + return move.product_id.uom_id._compute_price(unit_price, move.product_uom) + return super()._get_unit_cost(move)