[IMP] mrp: add new bom and cost report

Add a new report in order to display:
- bom structure and cost
- bom structure
- bom cost
The user is allowed to print multiple bom reports at once.
Print the report dynamically depending what have been unfolded.

In html the rendering is lazy, when unfolding the client make a request
to the server in order to get the html that will be append to the DOM.

For the pdf the kwargs unfolded could be used in order to render everything
at once.

With the help of:
Dhaval Suthar <dut@odoo.com>
Jigar Patel <jat@openerp.com>

task-36741
This commit is contained in:
Hiral Bhavsar
2018-04-06 17:05:14 +02:00
committed by Simon Lejeune
parent 5a22b79af5
commit 9201f92a4f
10 changed files with 709 additions and 0 deletions
+2
View File
@@ -37,8 +37,10 @@
'views/mrp_templates.xml',
'views/stock_scrap_views.xml',
'report/mrp_report_views_main.xml',
'report/mrp_report_bom_structure.xml',
'report/mrp_production_templates.xml',
],
'qweb': ['static/src/xml/mrp.xml'],
'demo': [
'data/mrp_demo.xml',
],
+1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_report_bom_structure
@@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
import json
from odoo import api, models, _
from odoo.tools import float_round
class ReportBomStructure(models.AbstractModel):
_name = 'report.mrp.report_bom_structure'
@api.model
def get_report_values(self, docids, data=None):
docs = []
for bom_id in docids:
bom = self.env['mrp.bom'].browse(bom_id)
variant = data and data.get('variant')
candidates = variant and self.env['product.product'].browse(variant) or bom.product_tmpl_id.product_variant_ids
for product_variant_id in candidates:
if data and data.get('childs'):
doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=int(data.get('quantity')), child_bom_ids=json.loads(data.get('childs')))
else:
doc = self._get_pdf_line(bom_id, product_id=product_variant_id, unfolded=True)
doc['report_type'] = 'pdf'
doc['report_structure'] = data and data.get('report_type') or 'all'
docs.append(doc)
return {
'doc_ids': docids,
'doc_model': 'mrp.bom',
'docs': docs,
}
@api.model
def get_html(self, bom_id=False, searchQty=1, searchVariant=False):
res = self._get_report_data(bom_id=bom_id, searchQty=searchQty, searchVariant=searchVariant)
res['lines']['report_type'] = 'html'
res['lines']['report_structure'] = 'all'
res['lines'] = self.env.ref('mrp.report_mrp_bom').render({'data': res['lines']})
return res
@api.model
def get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False):
lines = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
return self.env.ref('mrp.report_mrp_bom_line').render({'data': lines})
@api.model
def get_operations(self, bom_id=False, qty=0, level=0):
bom = self.env['mrp.bom'].browse(bom_id)
lines = self._get_operation_line(bom.routing_id, qty, level)
values = {
'bom_id': bom_id,
'currency': self.env.user.company_id.currency_id,
'operations': lines,
}
return self.env.ref('mrp.report_mrp_operation_line').render({'data': values})
@api.model
def _get_report_data(self, bom_id, searchQty=0, searchVariant=False):
lines = {}
bom = self.env['mrp.bom'].browse(bom_id)
bom_quantity = searchQty or bom.product_qty
bom_product_variants = {}
bom_uom_name = ''
if bom:
bom_uom_name = bom.product_uom_id.name
# Get variants used for search
if not bom.product_id:
for variant in bom.product_tmpl_id.product_variant_ids:
bom_product_variants[variant.id] = variant.display_name
lines = self._get_bom(bom_id, product_id=searchVariant, line_qty=bom_quantity, level=1)
return {
'lines': lines,
'variants': bom_product_variants,
'bom_uom_name': bom_uom_name,
'bom_qty': bom_quantity,
'is_variant_applied': self.env.user.user_has_groups('product.group_product_variant') and len(bom_product_variants) > 1,
'is_uom_applied': self.env.user.user_has_groups('uom.group_uom')
}
def _get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False):
bom = self.env['mrp.bom'].browse(bom_id)
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)
# Display bom components for current selected product variant
if product_id:
product = self.env['product.product'].browse(int(product_id))
else:
product = bom.product_id or bom.product_tmpl_id.product_variant_id
operations = self._get_operation_line(bom.routing_id, (bom_quantity / bom.product_qty), 0)
lines = {
'bom': bom,
'bom_qty': bom_quantity,
'bom_prod_name': product.display_name,
'currency': self.env.user.company_id.currency_id,
'product': product,
'price': product.uom_id._compute_price(product.standard_price, bom.product_uom_id) * bom_quantity,
'total': sum([op['total'] for op in operations]),
'level': level or 0,
'operations': operations,
'operations_cost': sum([op['total'] for op in operations]),
'operations_time': sum([op['duration_expected'] for op in operations])
}
components, total = self._get_bom_lines(bom, bom_quantity, product, line_id, level)
lines['components'] = components
lines['total'] += total
return lines
def _get_bom_lines(self, bom, bom_quantity, product, line_id, level):
components = []
total = 0
for line in bom.bom_line_ids:
line_quantity = (bom_quantity) * line.product_qty
if line._skip_bom_line(product):
continue
price = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line_quantity
if line.child_bom_id:
factor = line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id) * line.child_bom_id.product_qty
total = self._get_price(line.child_bom_id, factor)
else:
total = price
components.append({
'prod_id': line.product_id.id,
'prod_name': line.product_id.display_name,
'prod_qty': line_quantity,
'prod_uom': line.product_uom_id.name,
'prod_cost': price,
'parent_id': bom.id,
'line_id': line.id,
'level': level or 0,
'total': total,
'child_bom': line.child_bom_id.id,
'phantom_bom': line.child_bom_id and line.child_bom_id.type == 'phantom' or False,
})
total += total
return components, total
def _get_operation_line(self, routing, qty, level):
operations = []
total = 0.0
for operation in routing.operation_ids:
duration_expected = qty * operation.time_cycle + operation.workcenter_id.time_stop + operation.workcenter_id.time_start
total = ((duration_expected / 60.0) * operation.workcenter_id.costs_hour)
operations.append({
'level': level or 0,
'operation': operation,
'name': operation.name + ' - ' + operation.workcenter_id.name,
'duration_expected': duration_expected,
'total': float_round(total, precision_rounding=self.env.user.company_id.currency_id.rounding),
})
return operations
def _get_price(self, bom, factor):
price = 0
if bom.routing_id:
operations = self._get_operation_line(bom.routing_id, factor, 0)
price += sum([op['total'] for op in operations])
for line in bom.bom_line_ids:
if line.child_bom_id:
qty = line.product_uom_id._compute_quantity(line.product_qty * factor, line.child_bom_id.product_uom_id)
sub_price = self._get_price(line.child_bom_id, qty)
price += sub_price * qty
else:
prod_qty = line.product_qty * factor
price += (line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * prod_qty)
return price
def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False):
data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty)
def get_sub_lines(bom, product_id, line_qty, line_id, level):
data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
bom_lines = data['components']
lines = []
for bom_line in bom_lines:
lines.append({
'name': bom_line['prod_name'],
'type': 'bom',
'quantity': bom_line['prod_qty'],
'uom': bom_line['prod_uom'],
'prod_cost': bom_line['prod_cost'],
'bom_cost': bom_line['total'],
'level': bom_line['level'],
})
if bom_line['child_bom'] and (unfolded or bom_line['child_bom'] in child_bom_ids):
line = self.env['mrp.bom.line'].browse(bom_line['line_id'])
lines += (get_sub_lines(line.child_bom_id, line.product_id, line.product_qty * data['bom_qty'], line, level + 1))
if data['operations']:
lines.append({
'name': _('Operations'),
'type': 'operation',
'quantity': data['operations_time'],
'uom': _('minutes'),
'bom_cost': data['operations_cost'],
'level': level,
})
for operation in data['operations']:
if unfolded or 'operation-' + str(bom.id) in child_bom_ids:
lines.append({
'name': operation['name'],
'type': 'operation',
'quantity': operation['duration_expected'],
'uom': _('minutes'),
'bom_cost': operation['total'],
'level': level + 1,
})
return lines
bom = self.env['mrp.bom'].browse(bom_id)
product = product_id or bom.product_id or bom.product_tmpl_id.product_variant_id
pdf_lines = get_sub_lines(bom, product, qty, False, 1)
data['components'] = []
data['lines'] = pdf_lines
return data
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="report_mrp_bom">
<t t-raw="'&lt;base href=%s&gt;' % base_url"/>
<div class="container o_mrp_bom_report_page">
<t t-if="data['components'] or data['lines']">
<div class="row">
<div class="col-md-12">
<h1>BoM Structure &amp; Cost</h1>
<h3>
<a t-if="data['report_type'] == 'html'" t-att-data-res-id="data['product'].id" t-att-data-model="data['product']._name" class="o_mrp_bom_action">
<t t-esc="data['bom_prod_name']"/>
</a>
<t t-else="" t-esc="data['bom_prod_name']"/>
</h3>
<h6 t-if="data['bom'].code">Reference: <t t-esc="data['bom'].code"/></h6>
</div>
</div>
<t t-set="currency" t-value="data['currency']"/>
<div class="row">
<div class="col-md-12">
<div class="mt16">
<table width="100%" class="o_mrp_bom_expandable">
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th groups="uom.group_uom">Unit of Measure</th>
<th t-if="data['report_structure'] != 'bom_cost'" class="o_mrp_prod_cost" title="This is the cost defined on the product.">Product Cost</th>
<th t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_bom_cost" title="This is the cost based on the BoM of the product. It is computed by summing the costs of the components and operations needed to build the product.">BoM Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span><a t-if="data['report_type'] == 'html'" t-att-data-res-id="data['product'].id" t-att-data-model="'product.product'" class="o_mrp_bom_action"><t t-esc="data['bom_prod_name']"/></a><t t-else="" t-esc="data['bom_prod_name']"/></span>
</td>
<td><span><t t-esc="data['bom_qty']" t-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/></span></td>
<td groups="uom.group_uom"><span><t t-esc="data['bom'].product_uom_id.name"/></span></td>
<td t-if="data['report_structure'] != 'bom_cost'" class="o_mrp_prod_cost">
<span><t t-esc="data['price']" t-options='{"widget": "monetary", "display_currency": currency}'/></span>
</td>
<td t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_bom_cost">
<span><t t-esc="data['total']" t-options='{"widget": "monetary", "display_currency": currency}'/></span>
</td>
</tr>
<t t-if="data['report_type'] == 'html'" t-call="mrp.report_mrp_bom_line"/>
<t t-if="data['report_type'] == 'pdf'" t-call="mrp.report_mrp_bom_pdf_line"/>
</tbody>
<tfoot>
<tr>
<td></td>
<td><span><strong>Unit Cost</strong></span></td>
<td groups="uom.group_uom"></td>
<td t-if="data['report_structure'] != 'bom_cost'" class="o_mrp_prod_cost">
<span><t t-esc="data['price']/data['bom_qty']" t-options='{"widget": "monetary", "display_currency": currency}'/></span>
</td>
<td t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_bom_cost">
<span><t t-esc="data['total']/data['bom_qty']" t-options='{"widget": "monetary", "display_currency": currency}'/></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</t>
<t t-else="">
<h1 class="text-center">No data available.</h1>
</t>
</div>
</template>
<template id="report_mrp_bom_line">
<t t-set="currency" t-value="data['currency']"/>
<t t-foreach="data['components']" t-as="l">
<t t-set="space_td" t-value="'margin-left: '+ str(l['level'] * 20) + 'px;'"/>
<tr class="o_mrp_bom_report_line" t-att-data-id="l['child_bom']" t-att-parent_id="l['parent_id']" t-att-data-line="l['line_id']" t-att-data-product_id="l['prod_id']" t-att-data-qty="l['prod_qty']" t-att-data-level="l['level']">
<td>
<span t-att-style="space_td"/>
<t t-if="l['child_bom']">
<span t-att-data-function="'get_bom'" class="o_mrp_bom_unfoldable fa fa-fw fa-caret-right"/>
</t>
<span t-att-class="None if l['child_bom'] else 'o_mrp_bom_no_fold'">
<a t-att-data-res-id="l['prod_id']" t-att-data-model="'product.product'" class="o_mrp_bom_action"><t t-esc="l['prod_name']"/></a>
</span>
<t t-if="l['phantom_bom']">
<span class="fa fa-dropbox" title="This is a BoM of type Kit!"/>
</t>
</td>
<td><span><t t-esc="l['prod_qty']" t-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/></span></td>
<td groups="uom.group_uom"><span><t t-esc="l['prod_uom']"/></span></td>
<td class="o_mrp_prod_cost">
<span t-esc="l['prod_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
<td class="o_mrp_bom_cost">
<span t-esc="l['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
</tr>
</t>
<t t-if="data['operations']">
<t t-set="space_td" t-value="'margin-left: '+ str(data['level'] * 20) + 'px;'"/>
<tr class="o_mrp_bom_report_line o_mrp_bom_cost" t-att-data-id="'operation-' + str(data['bom'].id)" t-att-data-bom-id="data['bom'].id" t-att-parent_id="data['bom'].id" t-att-data-qty="data['bom_qty']" t-att-data-level="data['level']">
<td>
<span t-att-style="space_td"/>
<span class="o_mrp_bom_unfoldable fa fa-fw fa-caret-right" t-att-data-function="'get_operations'"/>
Operations
</td>
<td>
<span t-esc="data['operations_time']" t-options='{"widget": "float_time"}'/>
</td>
<td groups="uom.group_uom"><span>Minutes</span></td>
<td class="o_mrp_prod_cost">
</td>
<td class="o_mrp_bom_cost">
<span t-esc="data['operations_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
</tr>
</t>
</template>
<template id="report_mrp_operation_line">
<t t-set="currency" t-value="data['currency']"/>
<t t-foreach="data['operations']" t-as="op">
<t t-set="space_td" t-value="'margin-left: '+ str(op['level'] * 20) + 'px;'"/>
<tr class="o_mrp_bom_report_line o_mrp_bom_cost" t-att-parent_id="'operation-' + str(data['bom_id'])">
<td>
<span t-att-style="space_td"/>
<a t-att-data-res-id="op['operation'].id" t-att-data-model="'mrp.routing.workcenter'" class="o_mrp_bom_action"><t t-esc="op['name']"/></a>
</td>
<td>
<span t-esc="op['duration_expected']" t-options='{"widget": "float_time"}'/>
</td>
<td groups="uom.group_uom"><span>Minutes</span></td>
<td class="o_mrp_prod_cost"></td>
<td class="o_mrp_bom_cost">
<span t-esc="op['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
</tr>
</t>
</template>
<template id="report_mrp_bom_pdf">
<t t-raw="'&lt;base href=%s&gt;' % base_url"/>
<t t-call="web.html_container">
<t t-call="mrp.report_mrp_bom"/>
</t>
</template>
<template id="report_mrp_bom_pdf_line">
<t t-set="currency" t-value="data['currency']"/>
<t t-foreach="data['lines']" t-as="l">
<t t-set="space_td" t-value="'margin-left: '+ str(l['level'] * 20) + 'px;'"/>
<tr t-if="data['report_structure'] != 'bom_structure' or l['type'] != 'operation'">
<td>
<span t-att-style="space_td"/>
<span><t t-esc="l['name']"/></span>
</td>
<td>
<span>
<t t-if="l['type'] == 'operation'" t-esc="l['quantity']" t-options='{"widget": "float_time"}'/>
<t t-if="l['type'] == 'bom'" t-esc="l['quantity']" t-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/>
</span>
</td>
<td groups="uom.group_uom"><span><t t-esc="l['uom']"/></span></td>
<td t-if="data['report_structure'] != 'bom_cost'" class="o_mrp_prod_cost">
<span t-if="'prod_cost' in l" t-esc="l['prod_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
<td t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_bom_cost">
<span t-esc="l['bom_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
</td>
</tr>
</t>
</template>
<template id="report_bom_structure">
<t t-set="data_report_landscape" t-value="True"/>
<t t-call="web.html_container">
<t t-call="web.basic_layout">
<t t-foreach="docs" t-as="data">
<div class="page">
<t t-raw="'&lt;base href=%s&gt;' % base_url"/>
<t t-call-assets="mrp.assets_common" t-js="False"/>
<t t-call="mrp.report_mrp_bom"/>
</div>
<p style="page-break-before:always;"> </p>
</t>
</t>
</t>
</template>
</odoo>
@@ -10,6 +10,14 @@
report_type="qweb-pdf"
print_report_name="'Production Order - %s' % object.name"
/>
<report
string="BoM Structure"
id="action_report_bom_structure"
model="mrp.bom"
name="mrp.report_bom_structure"
file="mrp.report_bom_structure"
report_type="qweb-pdf"
print_report_name="'Bom Structure - %s' % object.display_name"
/>
</data>
</odoo>
+195
View File
@@ -0,0 +1,195 @@
odoo.define('mrp.mrp_bom_report', function (require) {
'use strict';
var core = require('web.core');
var framework = require('web.framework');
var stock_report_generic = require('stock.stock_report_generic');
var QWeb = core.qweb;
var MrpBomReport = stock_report_generic.extend({
events: {
'click .o_mrp_bom_unfoldable': '_onClickUnfold',
'click .o_mrp_bom_foldable': '_onClickFold',
'click .o_mrp_bom_action': '_onClickAction',
},
get_html: function() {
var self = this;
var args = [
this.given_context.active_id,
this.given_context.searchQty || 1,
this.given_context.searchVariant,
];
return this._rpc({
model: 'report.mrp.report_bom_structure',
method: 'get_html',
args: args
})
.then(function (result) {
self.data = result;
});
},
set_html: function() {
var self = this;
return this._super().then(function () {
self.$el.html(self.data.lines);
self.renderSearch();
self.update_cp();
});
},
render_html: function(event, $el, result){
$el.after(result);
$(event.currentTarget).toggleClass('o_mrp_bom_foldable o_mrp_bom_unfoldable fa-caret-right fa-caret-down');
this._reload_report_type();
},
get_bom: function(event) {
var self = this;
var $parent = $(event.currentTarget).closest('tr');
var activeID = $parent.data('id');
var productID = $parent.data('product_id');
var lineID = $parent.data('line');
var qty = $parent.data('qty');
var level = $parent.data('level') || 0;
return this._rpc({
model: 'report.mrp.report_bom_structure',
method: 'get_bom',
args: [
activeID,
productID,
parseFloat(qty),
lineID,
level + 1
]
})
.then(function (result) {
self.render_html(event, $parent, result);
});
},
get_operations: function(event) {
var self = this;
var $parent = $(event.currentTarget).closest('tr');
var activeID = $parent.data('bom-id');
var qty = $parent.data('qty');
var level = $parent.data('level') || 0;
return this._rpc({
model: 'report.mrp.report_bom_structure',
method: 'get_operations',
args: [
activeID,
parseFloat(qty),
level + 1
]
})
.then(function (result) {
self.render_html(event, $parent, result);
});
},
update_cp: function () {
var status = {
cp_content: {
$buttons: this.$buttonPrint,
$searchview_buttons: this.$searchView
},
};
return this.update_control_panel(status);
},
renderSearch: function () {
this.$buttonPrint = $(QWeb.render('mrp.button'));
this.$buttonPrint.filter('.o_mrp_bom_print').on('click', this._onClickPrint.bind(this));
this.$buttonPrint.filter('.o_mrp_bom_print_unfolded').on('click', this._onClickPrint.bind(this));
this.$searchView = $(QWeb.render('mrp.report_bom_search', _.omit(this.data, 'lines')));
this.$searchView.find('.o_mrp_bom_report_qty').on('change', this._onChangeQty.bind(this));
this.$searchView.find('.o_mrp_bom_report_variants').on('change', this._onChangeVariants.bind(this));
this.$searchView.find('.o_mrp_bom_report_type').on('change', this._onChangeType.bind(this));
},
_onClickPrint: function (ev) {
var childBomIDs = _.map(this.$el.find('.o_mrp_bom_foldable').closest('tr'), function (el) {
return $(el).data('id');
});
framework.blockUI();
var reportname = 'mrp.report_bom_structure?docids=' + this.given_context.active_id;
if (! $(ev.currentTarget).hasClass('o_mrp_bom_print_unfolded')) {
reportname += '&quantity=' + (this.given_context.searchQty || 1) +
'&childs=' + JSON.stringify(childBomIDs) +
'&report_type=' + this.given_context.report_type;
}
if (this.given_context.searchVariant) {
reportname += '&variant=' + this.given_context.searchVariant;
}
var action = {
'type': 'ir.actions.report',
'report_type': 'qweb-pdf',
'report_name': reportname,
'report_file': 'mrp.report_bom_structure',
};
return this.do_action(action).then(function (){
framework.unblockUI();
});
},
_onChangeQty: function (ev) {
var qty = $(ev.currentTarget).val().trim();
if (qty) {
this.given_context.searchQty = parseFloat(qty);
this._reload();
}
},
_onChangeType: function (ev) {
var report_type = $("option:selected", $(ev.currentTarget)).data('type');
this.given_context.report_type = report_type;
this._reload_report_type();
},
_onChangeVariants: function (ev) {
this.given_context.searchVariant = $(ev.currentTarget).val();
this._reload();
},
_onClickUnfold: function (ev) {
var redirect_function = $(ev.currentTarget).data('function');
this[redirect_function](ev);
},
_onClickFold: function (ev) {
this._removeLines($(ev.currentTarget).closest('tr'));
$(ev.currentTarget).toggleClass('o_mrp_bom_foldable o_mrp_bom_unfoldable fa-caret-right fa-caret-down');
},
_onClickAction: function (ev) {
return this.do_action({
type: 'ir.actions.act_window',
res_model: $(ev.currentTarget).data('model'),
res_id: $(ev.currentTarget).data('res-id'),
views: [[false, 'form']],
target: 'current'
});
},
_reload: function () {
var self = this;
return this.get_html().then(function () {
self.$el.html(self.data.lines);
self._reload_report_type();
});
},
_reload_report_type: function () {
this.$('.o_mrp_bom_cost.o_hidden, .o_mrp_prod_cost.o_hidden').toggleClass('o_hidden');
if (this.given_context.report_type === 'bom_structure') {
this.$('.o_mrp_bom_cost').toggleClass('o_hidden');
}
if (this.given_context.report_type === 'bom_cost') {
this.$('.o_mrp_prod_cost').toggleClass('o_hidden');
}
},
_removeLines: function ($el) {
var self = this;
var activeID = $el.data('id');
_.each(this.$('tr[parent_id='+ activeID +']'), function (parent) {
var $parent = self.$(parent);
var $el = self.$('tr[parent_id='+ $parent.data('id') +']');
if ($el.length) {
self._removeLines($parent);
}
$parent.remove();
});
},
});
core.action_registry.add('mrp_bom_report', MrpBomReport);
return MrpBomReport;
});
@@ -0,0 +1,40 @@
.o_mrp_bom_report_page {
background-color: @odoo-view-background-color;
margin: 16px auto;
padding: 24px 16px;
.o_mrp_bom_action {
cursor: pointer;
}
// Manage expand icon
table.o_mrp_bom_expandable {
thead tr {
border-width: 2px;
border-top-style: groove;
border-bottom-style: groove;
}
tbody tr {
border-width: 1px;
border-top-style: solid;
border-bottom-style: groove;
.o_mrp_bom_unfoldable, .o_mrp_bom_foldable {
cursor: pointer;
}
}
thead tr th {
padding-left: 10px;
}
tbody, tfoot {
& > tr > td ~ td > span {
margin-left: 10px;
}
tr {
.o_mrp_bom_no_fold {
margin-left: 18px;
}
.o_mrp_bom_unfoldable, .o_mrp_bom_foldable {
margin-left: -2px;
}
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="mrp.button">
<button type="button" class="btn btn-primary btn-sm o_mrp_bom_print">print</button>
<button type="button" class="btn btn-primary btn-sm ml8 o_mrp_bom_print_unfolded">print unfolded</button>
</t>
<form class="form-inline" t-name="mrp.report_bom_search">
<div class="form-group col-md-4">
<label>Quantity:</label>
<div class="row">
<div class="col-md-6">
<input type="number" t-att-value="bom_qty" min="1" class="o_input o_mrp_bom_report_qty"/>
</div>
<div class="col-md-6">
<t t-if="is_uom_applied" t-esc="bom_uom_name"/>
</div>
</div>
</div>
<div t-if="is_variant_applied" class="form-group col-md-4">
<label>Variant:</label>
<select class="o_input o_mrp_bom_report_variants">
<option t-foreach="variants" t-as="variant" t-att-value="variant">
<t t-esc="variants[variant]"/>
</option>
</select>
</div>
<div class="form-group col-md-4">
<label>Report:</label>
<select class="o_input o_mrp_bom_report_type">
<option t-att-data-type="'all'">BoM Structure &amp; Cost</option>
<option t-att-data-type="'bom_structure'">BoM Structure</option>
<option t-att-data-type="'bom_cost'">BoM Cost</option>
</select>
</div>
</form>
</templates>
+8
View File
@@ -2,6 +2,12 @@
<odoo>
<data>
<!-- Bill of Materials -->
<record id="action_report_mrp_bom" model="ir.actions.client">
<field name="name">Cost &amp; Structure</field>
<field name="tag">mrp_bom_report</field>
<field name="context" eval="{'model': 'report.mrp.report_bom_structure'}" />
</record>
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="name">mrp.bom.form</field>
<field name="model">mrp.bom</field>
@@ -9,6 +15,8 @@
<form string="Bill of Material">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="%(action_report_mrp_bom)d" type="action"
class="oe_stat_button" icon="fa-bars" string="Cost &amp; Structure" attrs="{'invisible': [('bom_line_ids', '=', [])]}"/>
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
+7
View File
@@ -4,9 +4,16 @@
<xpath expr="." position="inside">
<script type="text/javascript" src="/mrp/static/src/js/mrp.js"></script>
<link rel="stylesheet" type="text/less" href="/mrp/static/src/less/mrp.less"/>
<script type="text/javascript" src="/mrp/static/src/js/mrp_bom_report.js"></script>
</xpath>
</template>
<template id="assets_common" name="mrp bom common assets" inherit_id="web.assets_common">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/mrp/static/src/less/mrp_bom_report.less" />
</xpath>
</template>
<template id="qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/mrp/static/tests/mrp_tests.js"/>