summaryrefslogtreecommitdiff
path: root/addons/mrp/report
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mrp/report
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mrp/report')
-rw-r--r--addons/mrp/report/__init__.py6
-rw-r--r--addons/mrp/report/mrp_production_templates.xml204
-rw-r--r--addons/mrp/report/mrp_report_bom_structure.py261
-rw-r--r--addons/mrp/report/mrp_report_bom_structure.xml221
-rw-r--r--addons/mrp/report/mrp_report_views_main.xml44
-rw-r--r--addons/mrp/report/mrp_zebra_production_templates.xml41
-rw-r--r--addons/mrp/report/report_deliveryslip.xml81
-rw-r--r--addons/mrp/report/report_stock_forecasted.py37
-rw-r--r--addons/mrp/report/report_stock_forecasted.xml19
-rw-r--r--addons/mrp/report/report_stock_rule.py17
-rw-r--r--addons/mrp/report/report_stock_rule.xml32
11 files changed, 963 insertions, 0 deletions
diff --git a/addons/mrp/report/__init__.py b/addons/mrp/report/__init__.py
new file mode 100644
index 00000000..b2dc5ecc
--- /dev/null
+++ b/addons/mrp/report/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import mrp_report_bom_structure
+from . import report_stock_forecasted
+from . import report_stock_rule
diff --git a/addons/mrp/report/mrp_production_templates.xml b/addons/mrp/report/mrp_production_templates.xml
new file mode 100644
index 00000000..650123b8
--- /dev/null
+++ b/addons/mrp/report/mrp_production_templates.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+<template id="report_mrporder">
+ <t t-call="web.html_container">
+ <t t-foreach="docs" t-as="o">
+ <t t-call="web.internal_layout">
+ <div class="page">
+ <div class="oe_structure"/>
+ <div class="row">
+ <div class="col-7">
+ <h2><span t-field="o.name"/></h2>
+ </div>
+ <div class="col-5">
+ <span class="text-right">
+ <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', o.name, 600, 100)" style="width:350px;height:60px"/>
+ </span>
+ </div>
+ </div>
+ <div class="row mt32 mb32">
+ <div class="col-3" t-if="o.origin">
+ <strong>Source Document:</strong><br/>
+ <span t-field="o.origin"/>
+ </div>
+ <div class="col-3">
+ <strong>Responsible:</strong><br/>
+ <span t-field="o.user_id"/>
+ </div>
+ </div>
+
+ <div class="row mt32 mb32">
+ <div class="col-3">
+ <strong>Finished Product:</strong><br/>
+ <span t-field="o.product_id"/>
+ </div>
+ <div class="col-3" t-if="o.product_description_variants">
+ <strong>Description:</strong><br/>
+ <span t-field="o.product_description_variants"/>
+ </div>
+ <div class="col-3">
+ <strong>Quantity to Produce:</strong><br/>
+ <span t-field="o.product_qty"/>
+ <span t-field="o.product_uom_id.name" groups="uom.group_uom"/>
+ </div>
+ </div>
+
+ <div t-if="o.workorder_ids">
+ <h3>
+ <span t-if="o.state == 'done'">Operations Done</span>
+ <span t-else="">Operations Planned</span>
+ </h3>
+ <table class="table table-sm">
+ <tr>
+ <th><strong>Operation</strong></th>
+ <th><strong>WorkCenter</strong></th>
+ <th><strong>No. Of Minutes</strong></th>
+ </tr>
+ <tr t-foreach="o.workorder_ids" t-as="line2">
+ <td><span t-field="line2.name"/></td>
+ <td><span t-field="line2.workcenter_id.name"/></td>
+ <td>
+ <span t-if="o.state != 'done'" t-field="line2.duration_expected"/>
+ <span t-if="o.state == 'done'" t-field="line2.duration"/>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <h3 t-if="o.move_raw_ids">
+ <span t-if="o.state == 'done'">
+ Consumed Products
+ </span>
+ <span t-else="">
+ Products to Consume
+ </span>
+ </h3>
+
+ <table class="table table-sm" t-if="o.move_raw_ids">
+ <t t-set="has_product_barcode" t-value="any(m.product_id.barcode for m in o.move_raw_ids)"/>
+ <thead>
+ <tr>
+ <th>Product</th>
+ <th t-attf-class="{{ 'text-right' if not has_product_barcode else '' }}">Quantity</th>
+ <th t-if="has_product_barcode" width="15%" class="text-center">Barcode</th>
+ </tr>
+ </thead>
+ <tbody>
+ <t t-if="o.move_raw_ids">
+ <tr t-foreach="o.move_raw_ids" t-as="raw_line">
+ <td>
+ <span t-field="raw_line.product_id"/>
+ </td>
+ <td t-attf-class="{{ 'text-right' if not has_product_barcode else '' }}">
+ <span t-field="raw_line.product_uom_qty"/>
+ <span t-field="raw_line.product_uom" groups="uom.group_uom"/>
+ </td>
+ <td t-if="has_product_barcode" width="15%" class="text-center">
+ <t t-if="raw_line.product_id.barcode">
+ <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', raw_line.product_id.barcode, 600, 100)" style="width:100%;height:35px" alt="Barcode"/>
+ </t>
+ </td>
+ </tr>
+ </t>
+ </tbody>
+ </table>
+ <div class="oe_structure"/>
+ </div>
+ </t>
+ </t>
+ </t>
+</template>
+
+<template id="label_production_view_pdf">
+ <t t-call="web.basic_layout">
+ <div class="page">
+ <t t-set="uom_categ_unit" t-value="env.ref('uom.product_uom_categ_unit')"/>
+ <t t-foreach="docs" t-as="production">
+ <t t-foreach="production.move_finished_ids" t-as="move">
+ <t t-if="production.state == 'done'">
+ <t t-set="move_lines" t-value="move.move_line_ids.filtered(lambda x: x.state == 'done' and x.qty_done)"/>
+ </t>
+ <t t-else="">
+ <t t-set="move_lines" t-value="move.move_line_ids.filtered(lambda x: x.state != 'done' and x.product_qty)"/>
+ </t>
+ <t t-foreach="move_lines" t-as="move_line">
+ <t t-if="move_line.product_uom_id.category_id == uom_categ_unit">
+ <t t-set="qty" t-value="int(move_line.qty_done)"/>
+ </t>
+ <t t-else="">
+ <t t-set="qty" t-value="1"/>
+ </t>
+ <t t-foreach="range(qty)" t-as="item">
+ <t t-translation="off">
+ <div style="display: inline-table; height: 10rem; width: 32%;">
+ <table class="table table-bordered" style="border: 2px solid black;" t-if="production.move_finished_ids">
+ <tr>
+ <th class="table-active text-left" style="height:4rem;">
+ <span t-esc="move.product_id.display_name"/>
+ <br/>
+ <span>Quantity:</span>
+ <t t-if="move_line.product_uom_id.category_id == uom_categ_unit">
+ <span>1.0</span>
+ <span t-field="move_line.product_uom_id" groups="uom.group_uom"/>
+ </t>
+ <t t-else="">
+ <span t-esc="move_line.product_uom_qty" t-if="move_line.state !='done'"/>
+ <span t-esc="move_line.qty_done" t-if="move_line.state =='done'"/>
+ <span t-field="move_line.product_uom_id" groups="uom.group_uom"/>
+ </t>
+ </th>
+ </tr>
+ <t t-if="move_line.product_id.tracking != 'none'">
+ <tr>
+ <td class="text-center align-middle">
+ <t t-if="move_line.lot_name or move_line.lot_id">
+ <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', move_line.lot_name or move_line.lot_id.name, 600, 150)" style="width:100%;height:4rem" alt="Barcode"/>
+ <span t-esc="move_line.lot_name or move_line.lot_id.name"/>
+ </t>
+ <t t-else="">
+ <span class="text-muted">No barcode available</span>
+ </t>
+ </td>
+ </tr>
+ </t>
+ <t t-if="move_line.product_id.tracking == 'none'">
+ <tr>
+ <td class="text-center align-middle" style="height: 6rem;">
+ <t t-if="move_line.product_id.barcode">
+ <img t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', move_line.product_id.barcode, 600, 150)" style="width:100%;height:4rem" alt="Barcode"/>
+ <span t-esc="move_line.product_id.barcode"/>
+ </t>
+ <t t-else="">
+ <span class="text-muted">No barcode available</span>
+ </t>
+ </td>
+ </tr>
+ </t>
+ </table>
+ </div>
+ </t>
+ </t>
+ </t>
+ </t>
+ </t>
+ </div>
+ </t>
+</template>
+
+<template id="production_message">
+ <t t-if="move.move_id.raw_material_production_id">
+ <t t-set="message">Consumed</t>
+ </t>
+ <t t-if="move.move_id.production_id">
+ <t t-set="message">Produced</t>
+ </t>
+ <strong><t t-esc="message"/> quantity has been updated.</strong>
+</template>
+
+<template id="track_production_move_template">
+ <div>
+ <t t-call="mrp.production_message"/>
+ <t t-call="stock.message_body"/>
+ </div>
+</template>
+</odoo>
diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py
new file mode 100644
index 00000000..a726e342
--- /dev/null
+++ b/addons/mrp/report/mrp_report_bom_structure.py
@@ -0,0 +1,261 @@
+# -*- 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'
+ _description = 'BOM Structure Report'
+
+ @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.get('variant')
+ candidates = variant and self.env['product.product'].browse(variant) or bom.product_id or bom.product_tmpl_id.product_variant_ids
+ quantity = float(data.get('quantity', 1))
+ for product_variant_id in candidates.ids:
+ if data and data.get('childs'):
+ doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, child_bom_ids=json.loads(data.get('childs')))
+ else:
+ doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, unfolded=True)
+ doc['report_type'] = 'pdf'
+ doc['report_structure'] = data and data.get('report_type') or 'all'
+ docs.append(doc)
+ if not candidates:
+ if data and data.get('childs'):
+ doc = self._get_pdf_line(bom_id, qty=quantity, child_bom_ids=json.loads(data.get('childs')))
+ else:
+ doc = self._get_pdf_line(bom_id, qty=quantity, 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']['has_attachments'] = res['lines']['attachments'] or any(component['attachments'] for component in res['lines']['components'])
+ 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, float_round(qty / bom.product_qty, precision_rounding=1, rounding_method='UP'), level)
+ values = {
+ 'bom_id': bom_id,
+ 'currency': self.env.company.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 or 1
+ 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)
+ company = bom.company_id or self.env.company
+ 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) or 0
+ # 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
+ if product:
+ price = product.uom_id._compute_price(product.with_company(company).standard_price, bom.product_uom_id) * bom_quantity
+ attachments = self.env['mrp.document'].search(['|', '&', ('res_model', '=', 'product.product'),
+ ('res_id', '=', product.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', product.product_tmpl_id.id)])
+ else:
+ # Use the product template instead of the variant
+ price = bom.product_tmpl_id.uom_id._compute_price(bom.product_tmpl_id.with_company(company).standard_price, bom.product_uom_id) * bom_quantity
+ attachments = self.env['mrp.document'].search([('res_model', '=', 'product.template'), ('res_id', '=', bom.product_tmpl_id.id)])
+ operations = self._get_operation_line(bom, float_round(bom_quantity / bom.product_qty, precision_rounding=1, rounding_method='UP'), 0)
+ lines = {
+ 'bom': bom,
+ 'bom_qty': bom_quantity,
+ 'bom_prod_name': product.display_name,
+ 'currency': company.currency_id,
+ 'product': product,
+ 'code': bom and bom.display_name or '',
+ 'price': price,
+ 'total': sum([op['total'] for op in operations]),
+ 'level': level or 0,
+ 'operations': operations,
+ 'operations_cost': sum([op['total'] for op in operations]),
+ 'attachments': attachments,
+ '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 / (bom.product_qty or 1.0)) * line.product_qty
+ if line._skip_bom_line(product):
+ continue
+ company = bom.company_id or self.env.company
+ price = line.product_id.uom_id._compute_price(line.product_id.with_company(company).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
+ sub_total = self._get_price(line.child_bom_id, factor, line.product_id)
+ else:
+ sub_total = price
+ sub_total = self.env.company.currency_id.round(sub_total)
+ components.append({
+ 'prod_id': line.product_id.id,
+ 'prod_name': line.product_id.display_name,
+ 'code': line.child_bom_id and line.child_bom_id.display_name or '',
+ 'prod_qty': line_quantity,
+ 'prod_uom': line.product_uom_id.name,
+ 'prod_cost': company.currency_id.round(price),
+ 'parent_id': bom.id,
+ 'line_id': line.id,
+ 'level': level or 0,
+ 'total': sub_total,
+ 'child_bom': line.child_bom_id.id,
+ 'phantom_bom': line.child_bom_id and line.child_bom_id.type == 'phantom' or False,
+ 'attachments': self.env['mrp.document'].search(['|', '&',
+ ('res_model', '=', 'product.product'), ('res_id', '=', line.product_id.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', line.product_id.product_tmpl_id.id)]),
+
+ })
+ total += sub_total
+ return components, total
+
+ def _get_operation_line(self, bom, qty, level):
+ operations = []
+ total = 0.0
+ for operation in bom.operation_ids:
+ operation_cycle = float_round(qty / operation.workcenter_id.capacity, precision_rounding=1, rounding_method='UP')
+ duration_expected = operation_cycle * 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': self.env.company.currency_id.round(total),
+ })
+ return operations
+
+ def _get_price(self, bom, factor, product):
+ price = 0
+ if bom.operation_ids:
+ # routing are defined on a BoM and don't have a concept of quantity.
+ # It means that the operation time are defined for the quantity on
+ # the BoM (the user produces a batch of products). E.g the user
+ # product a batch of 10 units with a 5 minutes operation, the time
+ # will be the 5 for a quantity between 1-10, then doubled for
+ # 11-20,...
+ operation_cycle = float_round(factor, precision_rounding=1, rounding_method='UP')
+ operations = self._get_operation_line(bom, operation_cycle, 0)
+ price += sum([op['total'] for op in operations])
+
+ for line in bom.bom_line_ids:
+ if line._skip_bom_line(product):
+ continue
+ if line.child_bom_id:
+ qty = line.product_uom_id._compute_quantity(line.product_qty * factor, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty
+ sub_price = self._get_price(line.child_bom_id, qty, line.product_id)
+ price += sub_price
+ else:
+ prod_qty = line.product_qty * factor
+ company = bom.company_id or self.env.company
+ not_rounded_price = line.product_id.uom_id._compute_price(line.product_id.with_context(force_comany=company.id).standard_price, line.product_uom_id) * prod_qty
+ price += company.currency_id.round(not_rounded_price)
+ return price
+
+ def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False):
+
+ 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'],
+ 'code': bom_line['code'],
+ 'child_bom': bom_line['child_bom'],
+ 'prod_id': bom_line['prod_id']
+ })
+ 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.id, bom_line['prod_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_id = product_id or bom.product_id.id or bom.product_tmpl_id.product_variant_id.id
+ data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty)
+ pdf_lines = get_sub_lines(bom, product_id, qty, False, 1)
+ data['components'] = []
+ data['lines'] = pdf_lines
+ return data
diff --git a/addons/mrp/report/mrp_report_bom_structure.xml b/addons/mrp/report/mrp_report_bom_structure.xml
new file mode 100644
index 00000000..28963924
--- /dev/null
+++ b/addons/mrp/report/mrp_report_bom_structure.xml
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <template id="report_mrp_bom">
+ <div class="container o_mrp_bom_report_page">
+ <t t-if="data.get('components') or data.get('lines')">
+ <div class="row">
+ <div class="col-lg-12">
+ <h1 style="display:inline;">BoM Structure </h1>
+ <h1 style="display:inline;" t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_prod_cost">&amp; Cost</h1>
+ <h3>
+ <a href="#" 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-lg-12">
+ <div class="mt16">
+ <table width="100%" class="o_mrp_bom_expandable">
+ <thead>
+ <tr>
+ <th>Product</th>
+ <th name="th_mrp_bom_h">BoM</th>
+ <th class="text-right">Quantity</th>
+ <th class="text-left" groups="uom.group_uom">Unit of Measure</th>
+ <th t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_prod_cost text-right" 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 text-right" 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>
+ <th t-if="data['report_type'] == 'html' and data['has_attachments']" class="o_mrp_has_attachments" title="Files attached to the product">Attachments</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <span><a href="#" 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 name="td_mrp_bom">
+ <div><a href="#" t-if="data['report_type'] == 'html'" t-att-data-res-id="data['bom'].id" t-att-data-model="'mrp.bom'" class="o_mrp_bom_action"><t t-esc="data['code']"/></a><t t-else="" t-esc="data['code']"/></div>
+ </td>
+ <td class="text-right"><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_structure'" class="o_mrp_prod_cost text-right">
+ <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 text-right">
+ <span><t t-esc="data['total']" t-options='{"widget": "monetary", "display_currency": currency}'/></span>
+ </td>
+ <td t-if="data['report_type'] == 'html'">
+ <span>
+ <t t-if="data['attachments']">
+ <a href="#" role="button" t-att-data-res-id="data['attachments'].ids" t-att-data-model="'mrp.document'" class="o_mrp_show_attachment_action fa fa-fw o_button_icon fa-files-o"/>
+ </t>
+ </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 name="td_mrp_bom_f"></td>
+ <td t-if="data['report_structure'] != 'bom_structure'" class="text-right o_mrp_prod_cost"><span><strong>Unit Cost</strong></span></td>
+ <td groups="uom.group_uom"></td>
+ <td t-if="data['report_structure'] != 'bom_structure'" class="o_mrp_prod_cost text-right">
+ <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 text-right">
+ <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>
+ <div t-att-style="space_td">
+ <t t-if="l['child_bom']">
+ <div t-att-data-function="'get_bom'" class="o_mrp_bom_unfoldable fa fa-fw fa-caret-right" style="display:inline-block;" role="img" aria-label="Unfold" title="Unfold"/>
+ </t>
+ <div t-att-class="None if l['child_bom'] else 'o_mrp_bom_no_fold'" style="display:inline-block;">
+ <a href="#" 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>
+ </div>
+ <t t-if="l['phantom_bom']">
+ <div class="fa fa-dropbox" title="This is a BoM of type Kit!" role="img" aria-label="This is a BoM of type Kit!"/>
+ </t>
+ </div>
+ </td>
+ <td name="td_mrp_bom">
+ <div>
+ <a href="#" t-att-data-res-id="l['child_bom']" t-att-data-model="'mrp.bom'" class="o_mrp_bom_action"><t t-esc="l['code']"/></a>
+ </div>
+ </td>
+ <td class="text-right"><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 text-right">
+ <span t-esc="l['prod_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td class="o_mrp_bom_cost text-right">
+ <span t-esc="l['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td>
+ <span>
+ <t t-if="l['attachments']">
+ <a href="#" role="button" t-att-data-res-id="l['attachments'].ids" t-att-data-model="'mrp.document'" class="o_mrp_show_attachment_action fa fa-fw o_button_icon fa-files-o"/>
+ </t>
+ </span>
+ </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 name="td_opr">
+ <span t-att-style="space_td"/>
+ <span class="o_mrp_bom_unfoldable fa fa-fw fa-caret-right" t-att-data-function="'get_operations'" role="img" aria-label="Unfold" title="Unfold"/>
+ Operations
+ </td>
+ <td/>
+ <td class="text-right">
+ <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 text-right">
+ <span t-esc="data['operations_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <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 name="td_opr_line">
+ <span t-att-style="space_td"/>
+ <a href="#" 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/>
+ <td class="text-right">
+ <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 text-right">
+ <span t-esc="op['total']" t-options='{"widget": "monetary", "display_currency": currency}'/>
+ </td>
+ <td/>
+ </tr>
+ </t>
+ </template>
+
+ <template id="report_mrp_bom_pdf">
+ <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>
+ <div t-att-style="space_td">
+ <div><t t-esc="l['name']"/></div>
+ </div>
+ </td>
+ <td name="td_mrp_code">
+ <div t-if="l.get('code')" t-esc="l['code']" />
+ </td>
+ <td class="text-right">
+ <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_structure'" class="o_mrp_prod_cost text-right">
+ <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 text-right">
+ <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.basic_layout">
+ <t t-call-assets="mrp.assets_common" t-js="False"/>
+ <t t-foreach="docs" t-as="data">
+ <div class="page">
+ <t t-call="mrp.report_mrp_bom"/>
+ </div>
+ <p style="page-break-before:always;"> </p>
+ </t>
+ </t>
+ </template>
+</odoo>
diff --git a/addons/mrp/report/mrp_report_views_main.xml b/addons/mrp/report/mrp_report_views_main.xml
new file mode 100644
index 00000000..2f74cf7c
--- /dev/null
+++ b/addons/mrp/report/mrp_report_views_main.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record id="action_report_production_order" model="ir.actions.report">
+ <field name="name">Production Order</field>
+ <field name="model">mrp.production</field>
+ <field name="report_type">qweb-pdf</field>
+ <field name="report_name">mrp.report_mrporder</field>
+ <field name="report_file">mrp.report.mrp_production_templates</field>
+ <field name="print_report_name">'Production Order - %s' % object.name</field>
+ <field name="binding_model_id" ref="model_mrp_production"/>
+ <field name="binding_type">report</field>
+ </record>
+ <record id="action_report_bom_structure" model="ir.actions.report">
+ <field name="name">BoM Structure</field>
+ <field name="model">mrp.bom</field>
+ <field name="report_type">qweb-pdf</field>
+ <field name="report_name">mrp.report_bom_structure</field>
+ <field name="report_file">mrp.report_bom_structure</field>
+ <field name="print_report_name">'Bom Structure - %s' % object.display_name</field>
+ <field name="binding_model_id" ref="model_mrp_bom"/>
+ <field name="binding_type">report</field>
+ </record>
+ <record id="label_manufacture_template" model="ir.actions.report">
+ <field name="name">Finished Product Label (ZPL)</field>
+ <field name="model">mrp.production</field>
+ <field name="report_type">qweb-text</field>
+ <field name="report_name">mrp.label_production_view</field>
+ <field name="report_file">mrp.label_production_view</field>
+ <field name="binding_model_id" ref="model_mrp_production"/>
+ <field name="binding_type">report</field>
+ </record>
+ <record id="action_report_finished_product" model="ir.actions.report">
+ <field name="name">Finished Product Label (PDF)</field>
+ <field name="model">mrp.production</field>
+ <field name="report_type">qweb-pdf</field>
+ <field name="report_name">mrp.label_production_view_pdf</field>
+ <field name="report_file">mrp.label_production_view_pdf</field>
+ <field name="print_report_name">'Finished products - %s' % object.name</field>
+ <field name="binding_model_id" ref="model_mrp_production"/>
+ <field name="binding_type">report</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/mrp/report/mrp_zebra_production_templates.xml b/addons/mrp/report/mrp_zebra_production_templates.xml
new file mode 100644
index 00000000..5244d68e
--- /dev/null
+++ b/addons/mrp/report/mrp_zebra_production_templates.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data>
+ <template id="label_production_view">
+ <t t-set="uom_categ_unit" t-value="env.ref('uom.product_uom_categ_unit')"/>
+ <t t-foreach="docs" t-as="production">
+ <t t-foreach="production.move_finished_ids" t-as="move">
+ <t t-foreach="move.move_line_ids" t-as="move_line">
+ <t t-if="move_line.product_uom_id.category_id == uom_categ_unit">
+ <t t-set="qty" t-value="int(move_line.qty_done)"/>
+ </t>
+ <t t-else="">
+ <t t-set="qty" t-value="1"/>
+ </t>
+ <t t-foreach="range(qty)" t-as="item">
+ <t t-translation="off">
+^XA
+^FO100,50
+^A0N,44,33^FD<t t-esc="move_line.product_id.display_name"/>^FS
+<t t-if="move_line.product_id.tracking != 'none' and move_line.lot_id">
+^FO100,100
+^A0N,44,33^FDLN/SN: <t t-esc="move_line.lot_id.name"/>^FS
+^FO100,150^BY3
+^BCN,100,Y,N,N
+^FD<t t-esc="move_line.lot_id.name"/>^FS
+^XZ
+</t>
+<t t-if="move_line.product_id.tracking == 'none' and move_line.product_id.barcode">
+^FO100,100^BY3
+^BCN,100,Y,N,N
+^FD<t t-esc="move_line.product_id.barcode"/>^FS
+^XZ
+</t>
+ </t>
+ </t>
+ </t>
+ </t>
+ </t>
+ </template>
+ </data>
+</odoo>
diff --git a/addons/mrp/report/report_deliveryslip.xml b/addons/mrp/report/report_deliveryslip.xml
new file mode 100644
index 00000000..88370ca6
--- /dev/null
+++ b/addons/mrp/report/report_deliveryslip.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="stock_report_delivery_document_inherit_mrp" inherit_id="stock.report_delivery_document">
+ <!-- needs to be set before so elif directly follows if later on -->
+ <xpath expr="//t[@name='has_packages']" position="before">
+ <!-- get only the top level kits' (i.e. no subkit) move lines for easier mapping later on + we ignore subkit groupings-->
+ <!-- note that move.name uses top level kit's product.template.display_name value instead of product.template.name -->
+ <t t-set="has_kits" t-value="o.move_line_ids.filtered(lambda l: l.move_id.bom_line_id and l.move_id.bom_line_id.bom_id.product_tmpl_id.display_name == l.move_id.name)"/>
+ </xpath>
+ <xpath expr="//t[@name='no_package_section']" position="before">
+ <t t-set="has_kits" t-value="move_lines.filtered(lambda l: l.move_id.bom_line_id and l.move_id.bom_line_id.bom_id.product_tmpl_id.display_name == l.move_id.name)"/>
+ <t t-if="has_kits">
+ <!-- print the products not in a package or kit first -->
+ <t t-set="move_lines" t-value="move_lines.filtered(lambda m: not m.move_id.bom_line_id)"/>
+ </t>
+ </xpath>
+ <xpath expr="//t[@name='no_package_move_lines']" position="inside">
+ <t t-call="mrp.stock_report_delivery_kit_sections"/>
+ </xpath>
+ <xpath expr="//t[@name='has_packages']" position="after">
+ <!-- Additional use case: group by kits when no packages exist and then apply use case 1. (serial/lot numbers used/printed) -->
+ <t t-elif="has_kits and not has_packages">
+ <t t-call="mrp.stock_report_delivery_kit_sections"/>
+ <t t-call="mrp.stock_report_delivery_no_kit_section"/>
+ </t>
+ </xpath>
+ </template>
+
+ <template id="stock_report_delivery_kit_sections">
+ <!-- do another map to get unique top level kits -->
+ <t t-set="kits" t-value="has_kits.mapped('move_id.bom_line_id.bom_id.product_tmpl_id')"/>
+ <t t-foreach="kits" t-as="kit">
+ <tr t-att-class="'bg-200 font-weight-bold o_line_section'">
+ <td colspan="99">
+ <span t-esc="kit.display_name"/>
+ </td>
+ </tr>
+ <t t-set="kit_move_lines" t-value="has_kits.filtered(lambda l: l.move_id.name == kit.display_name)"/>
+ <t t-if="has_serial_number">
+ <tr t-foreach="kit_move_lines" t-as="move_line">
+ <t t-set="description" t-as="move_line.move_id.description_picking"/>
+ <t t-if="description == kit.name">
+ <t t-set="description" t-value=""/>
+ </t>
+ <t t-call="stock.stock_report_delivery_has_serial_move_line"/>
+ </tr>
+ </t>
+ <t t-else="">
+ <!-- move line description by default is the product_template.name (kit name), instead of display_name-->
+ <t t-set="aggregated_lines" t-value="kit_move_lines._get_aggregated_product_quantities(kit_name=kit.name)"/>
+ <t t-if="aggregated_lines">
+ <t t-call="stock.stock_report_delivery_aggregated_move_lines"/>
+ </t>
+ </t>
+ </t>
+ </template>
+
+ <!-- No kit section is expected to only be called in no packages case -->
+ <template id="stock_report_delivery_no_kit_section">
+ <!-- Do another section for kit-less products if they exist -->
+ <t t-set="no_kit_move_lines" t-value="o.move_line_ids.filtered(lambda l: not l.move_id.bom_line_id)"/>
+ <t t-if="no_kit_move_lines">
+ <tr t-att-class="'bg-200 font-weight-bold o_line_section'">
+ <td colspan="99">
+ <span>Products not associated with a kit</span>
+ </td>
+ </tr>
+ <t t-if="has_serial_number">
+ <tr t-foreach="no_kit_move_lines" t-as="move_line">
+ <t t-call="stock.stock_report_delivery_has_serial_move_line"/>
+ </tr>
+ </t>
+ <t t-else="">
+ <t t-set="aggregated_lines" t-value="no_kit_move_lines._get_aggregated_product_quantities()"/>
+ <t t-if="aggregated_lines">
+ <t t-call="stock.stock_report_delivery_aggregated_move_lines"/>
+ </t>
+ </t>
+ </t>
+ </template>
+</odoo>
diff --git a/addons/mrp/report/report_stock_forecasted.py b/addons/mrp/report/report_stock_forecasted.py
new file mode 100644
index 00000000..0e403f97
--- /dev/null
+++ b/addons/mrp/report/report_stock_forecasted.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class ReplenishmentReport(models.AbstractModel):
+ _inherit = 'report.stock.report_product_product_replenishment'
+
+ def _move_draft_domain(self, product_template_ids, product_variant_ids, wh_location_ids):
+ in_domain, out_domain = super()._move_draft_domain(product_template_ids, product_variant_ids, wh_location_ids)
+ in_domain += [('production_id', '=', False)]
+ out_domain += [('raw_material_production_id', '=', False)]
+ return in_domain, out_domain
+
+ def _compute_draft_quantity_count(self, product_template_ids, product_variant_ids, wh_location_ids):
+ res = super()._compute_draft_quantity_count(product_template_ids, product_variant_ids, wh_location_ids)
+ res['draft_production_qty'] = {}
+ domain = self._product_domain(product_template_ids, product_variant_ids)
+ domain += [('state', '=', 'draft')]
+
+ # Pending incoming quantity.
+ mo_domain = domain + [('location_dest_id', 'in', wh_location_ids)]
+ grouped_mo = self.env['mrp.production'].read_group(mo_domain, ['product_qty:sum'], 'product_id')
+ res['draft_production_qty']['in'] = sum(mo['product_qty'] for mo in grouped_mo)
+
+ # Pending outgoing quantity.
+ move_domain = domain + [
+ ('raw_material_production_id', '!=', False),
+ ('location_id', 'in', wh_location_ids),
+ ]
+ grouped_moves = self.env['stock.move'].read_group(move_domain, ['product_qty:sum'], 'product_id')
+ res['draft_production_qty']['out'] = sum(move['product_qty'] for move in grouped_moves)
+ res['qty']['in'] += res['draft_production_qty']['in']
+ res['qty']['out'] += res['draft_production_qty']['out']
+
+ return res
diff --git a/addons/mrp/report/report_stock_forecasted.xml b/addons/mrp/report/report_stock_forecasted.xml
new file mode 100644
index 00000000..fb074afd
--- /dev/null
+++ b/addons/mrp/report/report_stock_forecasted.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="mrp_report_product_product_replenishment" inherit_id="stock.report_product_product_replenishment">
+ <xpath expr="//tr[@name='draft_picking_in']" position="after">
+ <tr t-if="docs['draft_production_qty']['in']" name="draft_mo_in">
+ <td colspan="2">Production of Draft MO</td>
+ <td t-esc="docs['draft_production_qty']['in']" class="text-right"/>
+ <td t-esc="docs['uom']" groups="uom.group_uom"/>
+ </tr>
+ </xpath>
+ <xpath expr="//tr[@name='draft_picking_out']" position="after">
+ <tr t-if="docs['draft_production_qty']['out']" name="draft_mo_out">
+ <td colspan="2">Component of Draft MO</td>
+ <td t-esc="-docs['draft_production_qty']['out']" class="text-right"/>
+ <td t-esc="docs['uom']" groups="uom.group_uom"/>
+ </tr>
+ </xpath>
+ </template>
+</odoo>
diff --git a/addons/mrp/report/report_stock_rule.py b/addons/mrp/report/report_stock_rule.py
new file mode 100644
index 00000000..2f25ca6a
--- /dev/null
+++ b/addons/mrp/report/report_stock_rule.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models
+
+
+class ReportStockRule(models.AbstractModel):
+ _inherit = 'report.stock.report_stock_rule'
+
+ @api.model
+ def _get_rule_loc(self, rule, product_id):
+ """ We override this method to handle manufacture rule which do not have a location_src_id.
+ """
+ res = super(ReportStockRule, self)._get_rule_loc(rule, product_id)
+ if rule.action == 'manufacture':
+ res['source'] = product_id.property_stock_production
+ return res
diff --git a/addons/mrp/report/report_stock_rule.xml b/addons/mrp/report/report_stock_rule.xml
new file mode 100644
index 00000000..e87c60e9
--- /dev/null
+++ b/addons/mrp/report/report_stock_rule.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <template id="mrp_report_stock_rule" inherit_id="stock.report_stock_rule">
+ <xpath expr="//div[hasclass('o_report_stock_rule_rule')]/t" position="before">
+ <t t-if="rule[0].action == 'manufacture'">
+ <t t-if="rule[1] == 'origin'">
+ <t t-call="stock.report_stock_rule_left_arrow"/>
+ </t>
+ </t>
+ </xpath>
+ <xpath expr="//div[hasclass('o_report_stock_rule_rule')]/t[last()]" position="after">
+ <t t-if="rule[0].action == 'manufacture'">
+ <t t-if="rule[1] == 'destination'">
+ <t t-call="stock.report_stock_rule_right_arrow"/>
+ </t>
+ </t>
+ </xpath>
+ <xpath expr="//div[hasclass('o_report_stock_rule_rule_name')]/span" position="before">
+ <t t-if="rule[0].action == 'manufacture'">
+ <i class="fa fa-wrench fa-fw" t-attf-style="color: #{color};"/>
+ </t>
+ </xpath>
+ <xpath expr="//div[hasclass('o_report_stock_rule_legend')]" position="inside">
+ <div class="o_report_stock_rule_legend_line">
+ <div class="o_report_stock_rule_legend_label">Manufacture</div>
+ <div class="o_report_stock_rule_legend_symbol">
+ <div class="fa fa-wrench fa-fw" t-attf-style="color: #{color};"/>
+ </div>
+ </div>
+ </xpath>
+ </template>
+</odoo>