[REF] stock: convert client action "stock_report_generic"

This commit is to convert the "stock_report_generic" client action into owl.

closes odoo/odoo#112064

Taskid: 3175084
Related: odoo/upgrade#4309
Signed-off-by: Steve Van Essche <svs@odoo.com>
This commit is contained in:
FrancoisGe
2023-03-13 15:27:54 +01:00
parent a1c447bf84
commit 2e3d5cfa80
9 changed files with 267 additions and 567 deletions
+5 -9
View File
@@ -231,17 +231,13 @@ class MrpStockReport(models.TransientModel):
specific_paperformat_args={'data-report-margin-top': 17, 'data-report-header-spacing': 12}
)
def _get_html(self):
result = {}
rcontext = {}
def _get_main_lines(self):
context = dict(self.env.context)
rcontext['lines'] = self.with_context(context).get_lines()
result['html'] = self.env['ir.qweb']._render('stock.report_stock_inventory', rcontext)
return result
return self.with_context(context).get_lines()
@api.model
def get_html(self, given_context=None):
def get_main_lines(self, given_context=None):
res = self.search([('create_uid', '=', self.env.uid)], limit=1)
if not res:
return self.create({}).with_context(given_context)._get_html()
return res.with_context(given_context)._get_html()
return self.create({}).with_context(given_context)._get_main_lines()
return res.with_context(given_context)._get_main_lines()
@@ -0,0 +1,145 @@
/** @odoo-module **/
import { Component, onWillStart, useState } from "@odoo/owl";
import { download } from "@web/core/network/download";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Layout } from "@web/search/layout";
import { useSetupAction } from "@web/webclient/actions/action_hook";
function processLine(line) {
return { ...line, lines: [], isFolded: true };
}
function extractPrintData(lines) {
const data = [];
for (const line of lines) {
const { id, model_id, model, unfoldable, level } = line;
data.push({
id: id,
model_id: model_id,
model_name: model,
unfoldable,
level: level || 1,
});
if (!line.isFolded) {
data.push(...extractPrintData(line.lines));
}
}
return data;
}
export class TraceabilityReport extends Component {
static template = "stock.TraceabilityReport";
static components = { Layout };
setup() {
this.actionService = useService("action");
this.orm = useService("orm");
this.user = useService("user");
onWillStart(this.onWillStart);
useSetupAction({
getLocalState: () => ({
lines: [...this.state.lines],
}),
});
this.state = useState({
lines: this.props.state?.lines || [],
});
const { active_id, active_model, auto_unfold, context, lot_name, ttype, url } =
this.props.action.context;
this.controllerUrl = url;
this.context = context || {};
Object.assign(this.context, {
active_id: active_id || this.props.action.params.active_id,
auto_unfold: auto_unfold || false,
model: active_model || false,
lot_name: lot_name || false,
ttype: ttype || false,
});
this.display = {
controlPanel: { "top-right": false, "bottom-right": false },
searchPanel: false,
};
}
async onWillStart() {
if (!this.state.lines.length) {
const mainLines = await this.orm.call("stock.traceability.report", "get_main_lines", [
this.context,
]);
this.state.lines = mainLines.map(processLine);
}
}
onClickBoundLink(line) {
this.actionService.doAction({
type: "ir.actions.act_window",
res_model: line.res_model,
res_id: line.res_id,
views: [[false, "form"]],
target: "current",
});
}
onCLickOpenLot(line) {
this.actionService.doAction({
type: "ir.actions.client",
tag: "stock_report_generic",
name: line.lot_name !== undefined && line.lot_name.toString(),
context: {
active_id: line.lot_id,
active_model: "stock.lot",
url: "/stock/output_format/stock?active_id=:active_id&active_model=:active_model",
},
});
}
onClickUpDownStream(line) {
this.actionService.doAction({
type: "ir.actions.client",
tag: "stock_report_generic",
name: this.env._t("Traceability Report"),
context: {
active_id: line.model_id,
active_model: line.model,
auto_unfold: true,
lot_name: line.lot_name !== undefined && line.lot_name,
url: "/stock/output_format/stock/active_id",
},
});
}
onClickPrint() {
const data = JSON.stringify(extractPrintData(this.state.lines));
const url = this.controllerUrl
.replace(":active_id", this.context.active_id)
.replace(":active_model", this.context.model)
.replace("output_format", "pdf");
download({
data: { data },
url,
});
}
async toggleLine(line) {
line.isFolded = !line.isFolded;
if (!line.lines.length) {
line.lines = (
await this.orm.call("stock.traceability.report", "get_lines", [line.id], {
model_id: line.model_id,
model_name: line.model,
level: line.level + 30 || 1,
})
).map(processLine);
}
}
}
registry.category("actions").add("stock_report_generic", TraceabilityReport);
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="stock.TraceabilityReport" owl="1">
<div class="o_action">
<Layout display="display">
<t t-set-slot="layout-buttons">
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
<button type="button" class="btn btn-primary" t-on-click="() => this.onClickPrint()">Print</button>
</div>
</t>
<div class="container-fluid o_stock_reports_page o_stock_reports_no_print">
<t t-if="state.lines.length">
<h1 class="o_report_heading text-start">Traceability Report</h1>
<div class="o_stock_reports_table table-responsive">
<table class="table">
<thead>
<tr class="o_report_header">
<th class="o_report_line_header">Reference</th>
<th class="o_report_line_header">Product</th>
<th class="o_report_line_header">Date</th>
<th class="o_report_line_header">Lot/Serial #</th>
<th class="o_report_line_header">From</th>
<th class="o_report_line_header">To</th>
<th class="o_report_line_header">Quantity</th>
</tr>
</thead>
<tbody>
<t t-call="stock.ReportMRPLines">
<t t-set="lines" t-value="state.lines"/>
<t t-set="hasUpDown" t-value="true"/>
</t>
</tbody>
</table>
</div>
</t>
<h1 t-else="" class="text-center">No operation made on this lot.</h1>
</div>
</Layout>
</div>
</t>
<t t-name="stock.ReportMRPLines" owl="1">
<t t-foreach="lines" t-as="line" t-key="line.id">
<t t-set="column" t-value="0" />
<tr t-att-class="line.model === 'stock.move.line' ? 'o_stock_reports_level0' : 'o_stock_reports_default_style'">
<t t-foreach="line.columns" t-as="col" t-key="col_index">
<td t-att-class="line.unfoldable ? 'o_stock_reports_unfoldable' : ''">
<t t-if="col_first">
<span t-attf-style="margin-left: {{line.level}}px"/>
<t t-if="hasUpDown and line.is_used ">
<span role="img" title="Traceability Report" aria-label="Traceability Report" t-on-click.prevent="() => this.onClickUpDownStream(line)">
<i class="fa fa-fw fa-level-up fa-rotate-270"/>
</span>
</t>
<t t-elif="line.unfoldable">
<span class="o_stock_reports_unfoldable o_stock_reports_caret_icon" t-on-click="() => this.toggleLine(line)">
<i class="fa fa-fw" t-att-class="line.isFolded ? 'fa-caret-right' : 'fa-caret-down'" role="img" aria-label="Unfold" title="Unfold"/>
</span>
</t>
</t>
<span t-if="col and line.reference === col" t-att-class="!line.unfoldable ? 'o_stock_reports_nofoldable' : ''">
<a class="o_stock_reports_web_action" href="#" t-on-click.prevent="() => this.onClickBoundLink(line)" t-esc="col"/>
</span>
<span t-elif="col and line.lot_name === col">
<a class="o_stock_report_lot_action" t-on-click.prevent="() => this.onCLickOpenLot(line)" t-esc="col" />
</span>
<t t-elif="col" t-esc="col"/>
</td>
</t>
</tr>
<t t-if="!line.isFolded and line.lines.length" t-call="stock.ReportMRPLines">
<t t-set="lines" t-value="line.lines"/>
<t t-set="hasUpDown" t-value="false"/>
</t>
</t>
</t>
</templates>
@@ -1,111 +0,0 @@
odoo.define('stock.stock_report_generic', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var session = require('web.session');
var ReportWidget = require('stock.ReportWidget');
var framework = require('web.framework');
var QWeb = core.qweb;
var stock_report_generic = AbstractAction.extend({
hasControlPanel: true,
// Stores all the parameters of the action.
init: function(parent, action) {
this._super.apply(this, arguments);
this.actionManager = parent;
this.given_context = Object.assign({}, session.user_context);
this.controller_url = action.context.url;
if (action.context.context) {
this.given_context = action.context.context;
}
this.given_context.active_id = action.context.active_id || action.params.active_id;
this.given_context.model = action.context.active_model || false;
this.given_context.ttype = action.context.ttype || false;
this.given_context.auto_unfold = action.context.auto_unfold || false;
this.given_context.lot_name = action.context.lot_name || false;
},
willStart: function() {
return Promise.all([this._super.apply(this, arguments), this.get_html()]);
},
set_html: function() {
var self = this;
var def = Promise.resolve();
if (!this.report_widget) {
this.report_widget = new ReportWidget(this, this.given_context);
def = this.report_widget.appendTo(this.$('.o_content'));
}
return def.then(function () {
self.report_widget.$el.html(self.html);
self.report_widget.$el.find('.o_report_heading').html('<h1>Traceability Report</h1>');
if (self.given_context.auto_unfold) {
_.each(self.$el.find('.fa-caret-right'), function (line) {
self.report_widget.autounfold(line, self.given_context.lot_name);
});
}
});
},
start: async function() {
const props = this.getControlPanelProps();
this.controlPanelProps.cp_content = props;
await this._super(...arguments);
this.set_html();
},
getControlPanelProps: function() {
return { $buttons: this.$buttons }
},
// Fetches the html and is previous report.context if any, else create it
get_html: async function() {
const { html } = await this._rpc({
args: [this.given_context],
method: 'get_html',
model: 'stock.traceability.report',
});
this.html = html;
this.renderButtons();
},
// Updates the control panel and render the elements that have yet to be rendered
update_cp: function() {
if (!this.$buttons) {
this.renderButtons();
}
this.controlPanelProps.cp_content = { $buttons: this.$buttons };
return this.updateControlPanel();
},
renderButtons: function() {
var self = this;
this.$buttons = $(QWeb.render("stockReports.buttons", {}));
// pdf output
this.$buttons.bind('click', function () {
var $element = $(self.$el[0]).find('.o_stock_reports_table tbody tr');
var dict = [];
$element.each(function( index ) {
var $el = $($element[index]);
dict.push({
'id': $el.data('id'),
'model_id': $el.data('model_id'),
'model_name': $el.data('model'),
'unfoldable': $el.data('unfold'),
'level': $el.find('td:first').data('level') || 1
});
});
framework.blockUI();
var url_data = self.controller_url.replace(':active_id', self.given_context.active_id);
url_data = url_data.replace(':active_model', self.given_context.model);
session.get_file({
url: url_data.replace('output_format', 'pdf'),
data: {data: JSON.stringify(dict)},
complete: framework.unblockUI,
error: (error) => self.call('crash_manager', 'rpc_error', error),
});
});
return this.$buttons;
},
});
core.action_registry.add("stock_report_generic", stock_report_generic);
return stock_report_generic;
});
@@ -1,131 +0,0 @@
odoo.define('stock.ReportWidget', function (require) {
'use strict';
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var ReportWidget = Widget.extend({
events: {
'click span.o_stock_reports_foldable': 'fold',
'click span.o_stock_reports_unfoldable': 'unfold',
'click .o_stock_reports_web_action': 'boundLink',
'click .o_stock_reports_stream': 'updownStream',
'click .o_stock_report_lot_action': 'actionOpenLot'
},
init: function(parent) {
this._super.apply(this, arguments);
},
start: function() {
QWeb.add_template("/stock/static/src/xml/stock_traceability_report_line.xml");
return this._super.apply(this, arguments);
},
boundLink: function(e) {
e.preventDefault();
return this.do_action({
type: 'ir.actions.act_window',
res_model: $(e.target).data('res-model'),
res_id: $(e.target).data('active-id'),
views: [[false, 'form']],
target: 'current'
});
},
actionOpenLot: function(e) {
e.preventDefault();
var $el = $(e.target).parents('tr');
this.do_action({
type: 'ir.actions.client',
tag: 'stock_report_generic',
name: $el.data('lot_name') !== undefined && $el.data('lot_name').toString(),
context: {
active_id : $el.data('lot_id'),
active_model : 'stock.lot',
url: '/stock/output_format/stock?active_id=:active_id&active_model=:active_model',
},
});
},
updownStream: function(e) {
var $el = $(e.target).parents('tr');
this.do_action({
type: "ir.actions.client",
tag: 'stock_report_generic',
name: _t("Traceability Report"),
context: {
active_id : $el.data('model_id'),
active_model : $el.data('model'),
auto_unfold: true,
lot_name: $el.data('lot_name') !== undefined && $el.data('lot_name').toString(),
url: '/stock/output_format/stock/active_id'
},
});
},
removeLine: function(element) {
var self = this;
var el, $el;
var rec_id = element.data('id');
var $stockEl = element.nextAll('tr[data-parent_id=' + rec_id + ']')
for (el in $stockEl) {
$el = $($stockEl[el]).find(".o_stock_reports_domain_line_0, .o_stock_reports_domain_line_1");
if ($el.length === 0) {
break;
}
else {
var $nextEls = $($el[0]).parents("tr");
self.removeLine($nextEls);
$nextEls.remove();
}
$el.remove();
}
return true;
},
fold: function(e) {
this.removeLine($(e.target).parents('tr'));
var active_id = $(e.target).parents('tr').find('td.o_stock_reports_foldable').data('id');
$(e.target).parents('tr').find('td.o_stock_reports_foldable').attr('class', 'o_stock_reports_unfoldable ' + active_id); // Change the class, rendering, and remove line from model
$(e.target).parents('tr').find('span.o_stock_reports_foldable').replaceWith(QWeb.render("unfoldable", {lineId: active_id}));
$(e.target).parents('tr').toggleClass('o_stock_reports_unfolded');
},
autounfold: function(target, lot_name) {
var self = this;
var $CurretElement;
$CurretElement = $(target).parents('tr').find('td.o_stock_reports_unfoldable');
var active_id = $CurretElement.data('id');
var active_model_name = $CurretElement.data('model');
var active_model_id = $CurretElement.data('model_id');
var row_level = $CurretElement.data('level');
var $cursor = $(target).parents('tr');
this._rpc({
model: 'stock.traceability.report',
method: 'get_lines',
args: [parseInt(active_id, 10)],
kwargs: {
'model_id': active_model_id,
'model_name': active_model_name,
'level': parseInt(row_level) + 30 || 1
},
})
.then(function (lines) {// After loading the line
_.each(lines, function (line) { // Render each line
$cursor.after(QWeb.render("report_mrp_line", {l: line}));
$cursor = $cursor.next();
if ($cursor && line.unfoldable && line.lot_name == lot_name) {
self.autounfold($cursor.find(".fa-caret-right"), lot_name);
}
});
});
$CurretElement.attr('class', 'o_stock_reports_foldable ' + active_id); // Change the class, and rendering of the unfolded line
$(target).parents('tr').find('span.o_stock_reports_unfoldable').replaceWith(QWeb.render("foldable", {lineId: active_id}));
$(target).parents('tr').toggleClass('o_stock_reports_unfolded');
},
unfold: function(e) {
this.autounfold($(e.target));
},
});
return ReportWidget;
});
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="stockReports.buttons">
<button type="button" class='btn btn-primary o_stock-widget-pdf'>PRINT</button>
</t>
<div role="dialog" t-name='stockReports.errorModal' class="modal" id="editable_error" tabindex="-1" data-bs-backdrop="static" style="z-index:9999;">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<header class="modal-header">
<h3 class="modal-title">Error</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</header>
<main class="modal-body">
<p id='insert_error' class='text-center'></p>
</main>
</div>
</div>
</div>
</templates>
@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="foldable">
<span t-att-class="'o_stock_reports_foldable ' + lineId + ' o_stock_reports_caret_icon'"><i class="fa fa-fw fa-caret-down" role="img" aria-label="Fold" title="Fold"></i></span>
</t>
<t t-name="unfoldable">
<span t-att-class="'o_stock_reports_unfoldable ' + lineId + ' o_stock_reports_caret_icon'"><i class="fa fa-fw fa-caret-right" role="img" aria-label="Unfold" title="Unfold"></i></span>
</t>
<t t-name="report_mrp_line">
<t t-set="trclass" t-value="'o_stock_reports_default_style'"/>
<t t-if="l.model == 'stock.move.line'"><t t-set="trclass" t-value="'o_stock_reports_level0'"/></t>
<t t-set="space_td" t-value="'margin-left: '+ l.level + 'px;'"/>
<t t-set="domainClass" t-value="'o_stock_reports_domain_line_0'"/>
<t t-if="l.unfoldable == false">
<t t-set="spanclass" t-value="'o_stock_reports_nofoldable'" />
<t t-set="domainClass" t-value="'o_stock_reports_domain_line_1'"/>
</t>
<tr t-att-data-unfold="l.unfoldable" t-att-data-parent_id="l.parent_id" t-att-data-id="l.id" t-att-data-model_id="l.model_id" t-att-data-model="l.model" t-att-class="trclass" t-att-data-lot_name="l.lot_name" t-att-data-lot_id="l.lot_id">
<t t-if="l.unfoldable == true"><t t-set="tdclass" t-value="'o_stock_reports_unfoldable'" /></t>
<t t-set="column" t-value="0" />
<t t-foreach="l.columns" t-as="c">
<t t-set="column" t-value="column + 1" />
<td style="white-space: nowrap;" t-att-data-id="l.id" t-att-data-model="l.model" t-att-data-model_id="l.model_id" t-att-class="tdclass" t-att-data-level="l.level" t-att-data-lot_name="l.lot_name">
<t t-if="column == 1">
<span t-att-style="space_td" t-att-class="domainClass"></span>
<t t-if="l.unfoldable">
<span class="o_stock_reports_unfoldable o_stock_reports_caret_icon"><i class="fa fa-fw fa-caret-right" role="img" aria-label="Unfold" title="Unfold"></i></span>
</t>
</t>
<t t-if="l.reference == c">
<span t-if="c" t-att-class="spanclass">
<a t-att-data-active-id="l.res_id" t-att-data-res-model="l.res_model" class="o_stock_reports_web_action" href="#"><t t-esc="c"/></a>
</span>
</t><t t-elif="l.lot_name == c and l.lot_name != false">
<span>
<a class="o_stock_report_lot_action" href="#"><t t-esc="c"/></a>
</span>
</t>
<t t-if="l.reference != c and l.lot_name != c">
<t t-if="typeof c == 'string' || typeof c == 'number'">
<t t-esc="c"/>
</t>
<t t-if="typeof c != 'string' &amp; typeof c != 'number'"><span t-att-style="c[1]">
<t t-esc="c[0]"/>
</span></t>
</t>
</td>
</t>
</tr>
</t>
</templates>
@@ -1,174 +1,47 @@
odoo.define('stock.stock_traceability_report_backend_tests', function (require) {
"use strict";
/** @odoo-module **/
const LegacyControlPanel = require('web.ControlPanel');
const { ControlPanel } = require("@web/search/control_panel/control_panel");
const dom = require('web.dom');
const StockReportGeneric = require('stock.stock_report_generic');
const testUtils = require('web.test_utils');
import { getFixture } from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
const { dom: domUtils } = testUtils;
const {
destroy,
getFixture,
legacyExtraNextTick,
patchWithCleanup,
} = require("@web/../tests/helpers/utils");
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
let serverData;
let target;
const { onMounted, onWillUnmount } = owl;
/**
* Helper function to instantiate a stock report action.
* @param {Object} params
* @param {Object} params.action
* @param {boolean} [params.debug]
* @returns {Promise<StockReportGeneric>}
*/
async function createStockReportAction(params) {
const parent = await testUtils.createParent(params);
const report = new StockReportGeneric(parent, params.action);
const target = testUtils.prepareTarget(params.debug);
const _destroy = report.destroy;
report.destroy = function () {
report.destroy = _destroy;
parent.destroy();
};
const fragment = document.createDocumentFragment();
await report.appendTo(fragment);
dom.prepend(target, fragment, {
callbacks: [{ widget: report }],
in_DOM: true,
});
// Wait for the ReportWidget to be appended
await testUtils.nextTick();
return report;
}
QUnit.module('Stock', {}, function () {
QUnit.module('Traceability report');
QUnit.test("Rendering with no lines", async function (assert) {
assert.expect(1);
const template = `
<div class="container-fluid o_stock_reports_page o_stock_reports_no_print">
<div class="o_stock_reports_table table-responsive">
<span class="text-center">
<h1>No operation made on this lot.</h1>
</span>
</div>
</div>`;
const report = await createStockReportAction({
action: {
QUnit.module("Stock", {}, function (hooks) {
hooks.beforeEach(() => {
serverData = {
actions: {
42: {
id: 42,
name: "Stock report",
tag: "stock_report_generic",
type: "ir.actions.client",
context: {},
params: {},
},
data: {
'stock.traceability.report': {
fields: {},
get_html: () => ({ html: template }),
},
},
});
},
};
// HTML content is nested in a div inside of the content
assert.strictEqual(report.el.querySelector('.o_content > div').innerHTML, template,
"Displayed template should match");
target = getFixture();
});
report.destroy();
QUnit.module("Traceability report");
QUnit.test("Rendering with no lines", async function (assert) {
assert.expect(1);
const webClient = await createWebClient({
serverData,
mockRPC: async function (route) {
if (route === "/web/dataset/call_kw/stock.traceability.report/get_main_lines") {
return [];
}
},
});
QUnit.test("mounted is called once when returning on 'Stock report' from breadcrumb", async assert => {
// This test can be removed as soon as we don't mix legacy and owl layers anymore.
assert.expect(7);
let mountCount = 0;
patchWithCleanup(ControlPanel.prototype, {
setup() {
this._super();
onMounted(() => {
mountCount = mountCount + 1;
this.__uniqueId = mountCount;
assert.step(`mounted ${this.__uniqueId}`);
});
onWillUnmount(() => {
assert.step(`willUnmount ${this.__uniqueId}`);
});
},
});
patchWithCleanup(LegacyControlPanel.prototype, {
setup() {
this._super();
onMounted(() => {
mountCount = mountCount + 1;
this.__uniqueId = mountCount;
assert.step(`mounted ${this.__uniqueId} (legacy)`);
});
onWillUnmount(() => {
assert.step(`willUnmount ${this.__uniqueId} (legacy)`);
});
},
});
const serverData = {
models: {
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
},
records: [
{id: 1, display_name: "Genda Swami"},
],
},
},
views: {
'partner,false,form': '<form><field name="display_name"/></form>',
'partner,false,search': '<search></search>',
},
actions: {
42: {
id: 42,
name: "Stock report",
tag: 'stock_report_generic',
type: 'ir.actions.client',
context: {},
params: {},
},
},
};
const target = getFixture();
const webClient = await createWebClient({
serverData,
mockRPC: function (route) {
if (route === '/web/dataset/call_kw/stock.traceability.report/get_html') {
return Promise.resolve({
html: '<a class="o_stock_reports_web_action" href="#" data-active-id="1" data-res-model="partner">Go to form view</a>',
});
}
},
});
await doAction(webClient, 42);
await domUtils.click(target.querySelector('.o_stock_reports_web_action'));
await legacyExtraNextTick();
await domUtils.click(target.querySelector('.breadcrumb-item'));
await legacyExtraNextTick();
destroy(webClient);
assert.verifySteps([
'mounted 1 (legacy)',
'willUnmount 1 (legacy)',
'mounted 2',
'willUnmount 2',
'mounted 3 (legacy)',
'willUnmount 3 (legacy)',
]);
});
await doAction(webClient, 42);
assert.strictEqual(
target.querySelector(".o_stock_reports_page").textContent,
"No operation made on this lot."
);
});
});
@@ -1,80 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_mrp_line">
<t t-foreach="lines" t-as="a">
<t t-set="trclass" t-value="'o_stock_reports_default_style'"/>
<t t-if="a.get('model') == 'stock.move.line'"><t t-set="trclass" t-value="'o_stock_reports_level0'"/></t>
<t t-set="domainClass" t-value="'o_stock_reports_domain_line'"/>
<t t-if="a['unfoldable'] == false">
<t t-set="domainClass" t-value="'o_stock_reports_domain_line_1'"/>
</t>
<t t-set="column" t-value="0" />
<tr t-att-data-unfold="a['unfoldable']" t-att-data-parent_id="a['parent_id']" t-att-data-id="a['id']" t-att-data-model_id="a['model_id']" t-att-data-model="a['model']" t-att-data-lot_name="a['lot_name']" t-att-data-lot_id="a['lot_id']" t-att-class="trclass">
<t t-set="tdclass" t-value="''" />
<t t-set="spanclass" t-value="''" />
<t t-if="a.get('unfoldable')"><t t-set="tdclass" t-value="'o_stock_reports_unfoldable'" /></t>
<t t-if="not a.get('unfoldable')"><t t-set="spanclass" t-value="'o_stock_reports_nofoldable'" /></t>
<t t-set="column" t-value="0" />
<t t-foreach="a['columns']" t-as="c">
<t t-set="column" t-value="column + 1" />
<td t-att-data-id="a.get('id')" t-att-data-model="a['model']" t-att-data-model_id="a['model_id']" t-att-class="tdclass" t-att-data-level="a['level']">
<t t-if="column == 1">
<t t-if="a.get('unfoldable') and not a.get('is_used')">
<span t-att-data-id="a['id']" class="o_stock_reports_unfoldable o_stock_reports_caret_icon"><i class="fa fa-fw fa-caret-right" role="img" aria-label="Unfold" title="Unfold"></i></span>
</t>
<t t-if="a.get('is_used')">
<span role="img" class="o_stock_reports_stream" title="Traceability Report" aria-label="Traceability Report"><i class="fa fa-fw fa-level-up fa-rotate-270"></i></span>
</t>
</t>
<t t-if="a['reference'] == c">
<span t-if="c" t-att-class="spanclass">
<a t-att-data-active-id="a['res_id']" t-att-data-res-model="a['res_model']" class="o_stock_reports_web_action" href="#"><t t-esc="c" /></a>
</span>
</t>
<t t-elif="a['lot_name'] == c">
<span>
<a class="o_stock_report_lot_action" href="#"><t t-esc="c"/></a>
</span>
</t><t t-else="">
<t t-esc="c"/>
</t>
</td>
</t>
</tr>
</t>
</template>
<template id="report_stock_inventory">
<div class="container-fluid o_stock_reports_page o_stock_reports_no_print">
<span t-if="lines" class="o_report_heading text-start"><h1>Traceability Report</h1></span>
<span><t t-esc="context"/></span>
<div class="o_stock_reports_table table-responsive">
<t t-if="not lines">
<span class="text-center"><h1>No operation made on this lot.</h1></span>
</t>
<t t-if="lines">
<table class="table">
<thead>
<tr class="o_report_header">
<th class="o_report_line_header">Reference</th>
<th class="o_report_line_header">Product</th>
<th class="o_report_line_header">Date</th>
<th class="o_report_line_header">Lot/Serial #</th>
<th class="o_report_line_header">From</th>
<th class="o_report_line_header">To</th>
<th class="o_report_line_header">Quantity</th>
</tr>
</thead>
<tbody>
<t t-call="stock.report_mrp_line"/>
</tbody>
</table>
</t>
</div>
</div>
</template>
<template id="report_stock_inventory_print">
<t t-call="web.html_container">
<t t-set="head_start">