[MOV] stock: move traceability report from mrp_workorder

This commit is contained in:
Pierre Masereel
2017-07-14 17:08:21 +02:00
committed by Simon Lejeune
parent eaca63277e
commit 5fcdb06010
13 changed files with 890 additions and 0 deletions
+4
View File
@@ -25,6 +25,7 @@
'security/stock_security.xml',
'security/ir.model.access.csv',
'views/stock_menu_views.xml',
'data/stock_traceability_report_data.xml',
'report/report_stock_forecast.xml',
'report/stock_report_views.xml',
@@ -57,6 +58,9 @@
'views/stock_move_views.xml',
'views/product_views.xml',
'views/stock_config_settings_views.xml',
'views/report_stock_traceability.xml',
'views/stock_traceability_report_views.xml',
'views/stock_template.xml',
'data/default_barcode_patterns.xml',
'data/stock_data.xml',
+33
View File
@@ -1,6 +1,10 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
from odoo.addons.web.controllers.main import _serialize_exception
from odoo.tools import html_escape
import json
class BarcodeController(http.Controller):
@@ -11,3 +15,32 @@ class BarcodeController(http.Controller):
return http.local_redirect('/web/login?redirect=/stock/barcode/')
return request.render('stock.barcode_index')
class StockReportController(http.Controller):
@http.route('/mrp_workorder/<string:output_format>/<string:report_name>/<int:report_id>', type='http', auth='user')
def report(self, output_format, report_name, token, report_id=False, **kw):
uid = request.session.uid
domain = [('create_uid', '=', uid)]
stock_traceability = request.env['stock.traceability.report'].sudo(uid).search(domain, limit=1)
line_data = json.loads(kw['data'])
try:
if output_format == 'pdf':
response = request.make_response(
stock_traceability.with_context(active_id=report_id).get_pdf(line_data),
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', 'attachment; filename=' + 'stock_traceability' + '.pdf;')
]
)
response.set_cookie('fileToken', token)
return response
except Exception as e:
se = _serialize_exception(e)
error = {
'code': 200,
'message': 'Odoo Server Error',
'data': se
}
return request.make_response(html_escape(json.dumps(error)))
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_stock_report" model="ir.actions.client">
<field name="name">Upstream Traceability</field>
<field name="tag">stock_report_generic</field>
<field name="context" eval="{'url': '/mrp_workorder/output_format/stock/active_id', 'model': 'stock.traceability.report'}" />
</record>
</odoo>
+1
View File
@@ -16,6 +16,7 @@ from . import stock_pack_operation
from . import stock_production_lot
from . import stock_picking
from . import stock_quant
from . import stock_traceability
from . import stock_warehouse
from . import stock_scrap
from . import web_planner
+263
View File
@@ -0,0 +1,263 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, _
rec = 0
def autoIncrement():
global rec
pStart = 1
pInterval = 1
if rec == 0:
rec = pStart
else:
rec += pInterval
return rec
class MrpStockReport(models.TransientModel):
_name = 'stock.traceability.report'
@api.model
def get_lines(self, line_id=None, **kw):
context = dict(self.env.context)
stream = context.get('ttype')
model = False
model_id = False
level = 1
parent_quant = False
if kw:
level = kw['level']
model = kw['model_name']
model_id = kw['model_id']
stream = kw['stream']
parent_quant = kw['parent_quant']
StockQuant = self.env['stock.quant']
context_ids = []
if context.get('active_id') and not context.get('model') or context.get('model') == 'stock.production.lot':
context_ids = StockQuant.search([['lot_id', '=', context.get('active_id')]])
elif context.get('active_id') and context.get('model') == 'mrp.production':
context_ids = self.env['mrp.production'].browse(context['active_id']).move_finished_ids.mapped('quant_ids')
elif context.get('active_id') and context.get('model') == 'stock.picking':
context_ids = self.env['stock.picking'].browse(context['active_id']).move_lines.mapped('quant_ids')
context.update({
'context_ids': context_ids or context.get('model') == 'stock.quant' and self.env[context.get('model')].browse(context.get('active_id')) or [],
'stream': stream
})
res = self.with_context(context)._lines(line_id, model_id=model_id, model=model, level=level, parent_quant=parent_quant)
return res
@api.model
def get_links(self, move):
res_model = ''
ref = ''
res_id = False
if move.picking_id:
res_model = 'stock.picking'
res_id = move.picking_id.id
ref = move.picking_id.name
elif move.raw_material_production_id or move.production_id:
res_model = 'mrp.production'
res_id = move.raw_material_production_id.id or move.production_id.id
ref = move.raw_material_production_id.name or move.production_id.name
elif move.unbuild_id or move.consume_unbuild_id:
res_model = 'mrp.unbuild'
res_id = move.consume_unbuild_id.id or move.unbuild_id.id
ref = move.consume_unbuild_id.name or move.unbuild_id.name
return res_model, res_id, ref
def make_dict_move(self, level, line_id, move, stream=False, final_vals=None):
res_model, res_id, ref = self.get_links(move)
data = {
'level': level,
'unfoldable': False,
'date': move.date,
'parent_id': line_id,
'model_id': move.id,
'model':'stock.move',
'product_id': move.product_id.display_name,
'product_qty_uom': False,
'location_source': move.location_id.name,
'location_destination': move.location_dest_id.name,
'reference_id': ref,
'res_id': res_id,
'stream': stream,
'res_model': res_model}
final_vals.append(data)
return final_vals
def make_dict_head(self, level, line_id, stream=False, move=False, quant=False, final_vals=None):
if move:
res_model, res_id, ref = self.get_links(move)
final_vals.append({
'level': level,
'unfoldable': True,
'model_id': move.id,
'parent_id': line_id,
'model': 'stock.move',
'parent_quant': quant.id if quant else False,
'product_id': move.product_id.display_name,
'product_qty_uom': False,
'location_source': move.location_id.name,
'location_destination': move.location_dest_id.name,
'reference_id': ref,
'res_id': res_id,
'stream': stream,
'res_model': res_model})
else:
final_vals.append({
'level': level,
'unfoldable': True if quant.history_ids else False,
'date': quant.lot_id.name,
'model_id': quant.id,
'parent_id': line_id,
'model':'stock.quant',
'product_id': quant.product_id.display_name,
'product_qty_uom': str(quant.qty) + ' ' + quant.product_id.uom_id.name,
'location_source': False,
'location_destination': False,
'stream': stream,
'reference_id': False})
return final_vals
@api.model
def upstream_traceability(self, level, stream=False, line_id=False, model=False, model_id=False, parent_quant=False, final_vals=None):
model_obj = self.env[model].browse(model_id)
if model == 'stock.quant':
for move in model_obj.history_ids.sorted(key=lambda r: r.date, reverse=True):
if move.production_id or move.unbuild_id:
final_vals = self.make_dict_head(level, stream=stream, line_id=line_id, move=move, quant=model_obj, final_vals=final_vals)
else:
final_vals = self.make_dict_move(level, stream=stream, line_id=line_id, move=move, final_vals=final_vals)
else:
if model_obj.production_id or model_obj.unbuild_id:
parent_quant = self.env['stock.quant'].browse(parent_quant)
for quant in parent_quant.consumed_quant_ids.filtered(lambda x: x.qty > 0):
final_vals = self.make_dict_head(level, line_id=line_id, stream=stream, move=False, quant=quant, final_vals=final_vals)
@api.model
def downstream_traceability(self, level, stream=False, line_id=False, model=False, model_id=False, parent_quant=False, final_vals=None):
model_obj = self.env[model].browse(model_id)
if model == 'stock.quant':
for move in model_obj.history_ids.sorted(key=lambda r: r.date):
if move.raw_material_production_id or move.consume_unbuild_id:
final_vals = self.make_dict_head(level, stream=stream, line_id=line_id, move=move, quant=model_obj, final_vals=final_vals)
else:
final_vals = self.make_dict_move(level, stream=stream, line_id=line_id, move=move, final_vals=final_vals)
else:
if model_obj.raw_material_production_id or model_obj.consume_unbuild_id:
parent_quant = self.env['stock.quant'].browse(parent_quant)
for quant in parent_quant.produced_quant_ids.filtered(lambda x: x.qty > 0):
final_vals = self.make_dict_head(level, stream=stream, line_id=line_id, move=False, quant=quant, final_vals=final_vals)
return True
@api.model
def _lines(self, line_id=None, model_id=False, model=False, level=0, parent_quant=False, **kw):
lines = []
context = dict(self.env.context)
final_vals = []
if model:
if context.get('stream') == "downstream" or context.get('stream') == 'downstream':
self.downstream_traceability(level, stream='downstream', line_id=line_id, model=model, model_id=model_id, parent_quant=parent_quant, final_vals=final_vals)
else:
self.upstream_traceability(level, stream='upstream', line_id=line_id, model=model, model_id=model_id, parent_quant=parent_quant, final_vals=final_vals)
else:
for quant in context['context_ids']:
final_vals = self.make_dict_head(level, stream=context.get('stream', False), line_id=line_id, move=False, quant=quant, final_vals=final_vals)
for data in final_vals:
lines.append({
'id': autoIncrement(),
'model': data['model'],
'model_id': data['model_id'],
'stream': data['stream'] or 'upstream',
'parent_id': data['parent_id'],
'parent_quant': data.get('parent_quant', False),
'type': 'line',
'reference': data.get('reference_id', False),
'res_id': data.get('res_id', False),
'res_model': data.get('res_model', False),
'name': _(data.get('lot_id', False)),
'columns': [data.get('reference_id', False) or data.get('product_id', False),
data.get('date', False),
data.get('product_qty_uom', 0),
data.get('location_source', False),
data.get('location_destination', False)],
'level': level,
'unfoldable': data['unfoldable'],
})
return lines
@api.multi
def get_pdf_lines(self, line_data=[]):
final_vals = []
lines = []
for line in line_data:
model = self.env[line['model_name']].browse(line['model_id'])
if line.get('unfoldable'):
if line['model_name'] == 'stock.quant':
final_vals = self.make_dict_head(line['level'], line_id=line['id'], move=False, quant=model, final_vals=final_vals)
else:
final_vals = self.make_dict_head(line['level'], line_id=line['id'], move=model, quant=False, final_vals=final_vals)
else:
if line['model_name'] == 'stock.move':
final_vals = self.make_dict_move(line['level'], line_id=line['id'], move=model, final_vals=final_vals)
else:
final_vals = self.make_dict_head(line['level'], line_id=line['id'], quant=model, final_vals=final_vals)
for data in final_vals:
lines.append({
'id': autoIncrement(),
'model': data['model'],
'model_id': data['model_id'],
'parent_id': data['parent_id'],
'stream': "%s" % (data['stream']),
'type': 'line',
'name': _(data.get('lot_id')),
'columns': [data.get('reference_id') or data.get('product_id'),
data.get('date'),
data.get('product_qty_uom', 0),
data.get('location_source'),
data.get('location_destination')],
'level': data['level'],
'unfoldable': data['unfoldable'],
})
return lines
def get_pdf(self, line_data=[]):
lines = self.with_context(print_mode=True).get_pdf_lines(line_data)
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
rcontext = {
'mode': 'print',
'base_url': base_url,
}
body = self.env['ir.ui.view'].render_template(
"mrp_workorder.report_stock_inventory_print",
values=dict(rcontext, lines=lines, report=self, context=self),
)
header = self.env['ir.actions.report'].render_template("web.internal_layout", values=rcontext)
header = self.env['ir.actions.report'].render_template("web.minimal_layout", values=dict(rcontext, subst=True, body=header))
landscape = True
return self.env['ir.actions.report']._run_wkhtmltopdf(
[self.env['ir.actions.report'].create_wkhtmltopdf_obj(header, body, None)],
landscape, self.env.user.company_id.paperformat_id,
specific_paperformat_args={'data-report-margin-top': 10, 'data-report-header-spacing': 10}
)
def _get_html(self):
result = {}
rcontext = {}
context = dict(self.env.context)
rcontext['lines'] = self.with_context(context).get_lines()
result['html'] = self.env.ref('mrp_workorder.report_stock_inventory').render(rcontext)
return result
@api.model
def get_html(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()
@@ -0,0 +1,113 @@
odoo.define('mrp_workorder.stock_report_generic', function (require) {
'use strict';
var core = require('web.core');
var Widget = require('web.Widget');
var ControlPanelMixin = require('web.ControlPanelMixin');
var session = require('web.session');
var ReportWidget = require('mrp_workorder.ReportWidget');
var framework = require('web.framework');
var crash_manager = require('web.crash_manager');
var QWeb = core.qweb;
var stock_report_generic = Widget.extend(ControlPanelMixin, {
// Stores all the parameters of the action.
init: function(parent, action) {
this.actionManager = parent;
this.given_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;
return this._super.apply(this, arguments);
},
willStart: function() {
return this.get_html();
},
set_html: function() {
var self = this;
var def = $.when();
if (!this.report_widget) {
this.report_widget = new ReportWidget(this, this.given_context);
def = this.report_widget.appendTo(this.$el);
}
def.then(function () {
self.report_widget.$el.html(self.html);
if(self.given_context['ttype'] == 'downstream'){
self.report_widget.$el.find('.o_report_heading').html('<h1>Downstream Traceability</h1>');
}
});
},
start: function() {
this.set_html();
return this._super();
},
// Fetches the html and is previous report.context if any, else create it
get_html: function() {
var self = this;
var defs = [];
return this._rpc({
model: 'stock.traceability.report',
method: 'get_html',
args: [self.given_context],
})
.then(function (result) {
self.html = result.html;
self.renderButtons();
defs.push(self.update_cp());
return $.when.apply($, defs);
});
},
// Updates the control panel and render the elements that have yet to be rendered
update_cp: function() {
if (!this.$buttons) {
this.renderButtons();
}
var status = {
breadcrumbs: this.actionManager.get_breadcrumbs(),
cp_content: {$buttons: this.$buttons},
};
return this.update_control_panel(status);
},
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']);
session.get_file({
url: url_data.replace('output_format', 'pdf'),
data: {data: JSON.stringify(dict)},
complete: framework.unblockUI,
error: crash_manager.rpc_error.bind(crash_manager),
});
});
return this.$buttons;
},
do_show: function() {
this._super();
this.update_cp();
},
});
core.action_registry.add("stock_report_generic", stock_report_generic);
return stock_report_generic;
});
@@ -0,0 +1,117 @@
odoo.define('mrp_workorder.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',
},
init: function(parent) {
this._super.apply(this, arguments);
},
start: function() {
QWeb.add_template("/mrp_workorder/static/src/xml/stock_traceability_report_line.xml");
return this._super.apply(this, arguments);
},
boundLink: function(e) {
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'
});
},
updownStream: function(e) {
var stream = $(e.target).parent().data('stream');
var $el = $(e.target).parents('tr');
var string = "Upstream Traceability"
if (stream == 'downstream') {
string = "Downstream Traceability"
}
this.do_action({
type: "ir.actions.client",
tag: 'stock_report_generic',
name: _t(string),
context: {
active_id : $el.data('model_id'),
active_model : $el.data('model'),
ttype: stream || false,
url: '/mrp_workorder/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');
},
unfold: function(e) {
var $CurretElement;
$CurretElement = $(e.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 stream = $CurretElement.data('stream');
var parent_quant = $CurretElement.data('parent_quant');
var $cursor = $(e.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,
'stream': stream || 'upstream',
'parent_quant': parseInt(parent_quant) || false,
'level': parseInt(row_level) + 30 || 1
},
})
.then(function (lines) {// After loading the line
var line;
for (line in lines) { // Render each line
$cursor.after(QWeb.render("report_mrp_line", {l: lines[line]}));
$cursor = $cursor.next();
}
});
$CurretElement.attr('class', 'o_stock_reports_foldable ' + active_id); // Change the class, and rendering of the unfolded line
$(e.target).parents('tr').find('span.o_stock_reports_unfoldable').replaceWith(QWeb.render("foldable", {lineId: active_id}));
$(e.target).parents('tr').toggleClass('o_stock_reports_unfolded');
},
});
return ReportWidget;
});
@@ -0,0 +1,78 @@
.o_stock_reports_lines(@border-width: 5px, @color: inherit, @font-weight: inherit, @border-top-style: initial, @border-bottom-style: initial) {
border-width: @border-width;
border-left-style: hidden;
border-right-style: hidden;
color: @color;
font-weight: @font-weight;
border-top-style: @border-top-style;
border-bottom-style: @border-bottom-style;
}
.o_stock_reports_body_print {
background-color: white;
color: black;
.o_stock_reports_level0 {
.o_stock_reports_lines(@border-width: 1px, @font-weight: bold, @border-top-style: solid, @border-bottom-style: groove);
}
}
.o_stock_reports_page {
background-color: @odoo-view-background-color;
color: @odoo-main-text-color;
&.o_stock_reports_no_print {
padding-bottom: 125px;
margin: @odoo-horizontal-padding auto;
.o-webclient-padding(@top: @odoo-sheet-vpadding, @bottom: @odoo-sheet-vpadding);
.o_stock_reports_level0 {
.o_stock_reports_lines(@border-width: 1px, @color: @odoo-brand-primary, @font-weight: bold, @border-top-style: solid, @border-bottom-style: groove);
}
.o_stock_reports_table {
white-space: nowrap;
margin-bottom: 228px;
margin-top: 30px;
}
.o_report_line_header {
padding-left: 10px;
}
.o_report_header {
border-top-style: solid;
border-top-style: groove;
border-bottom-style: groove;
border-width: 2px;
}
}
.o_stock_reports_unfolded {
display: inline-block;
}
table > tbody > tr > td ~ td > span {
margin-left: 10px;
}
.o_stock_reports_unfolded td + td {
visibility: hidden;
}
div.o_stock_reports_web_action,
span.o_stock_reports_web_action, i.fa,
span.o_stock_reports_unfoldable, span.o_stock_reports_foldable, a.o_stock_reports_web_action {
cursor: pointer;
}
.o_stock_reports_caret_icon {
margin-left: -3px;
}
th {
border-bottom: thin groove;
}
.o_stock_reports_level1 {
.o_stock_reports_lines(@border-width: 2px, @border-top-style: hidden, @border-bottom-style: solid);
}
.o_stock_reports_level2 {
.o_stock_reports_lines(@border-width: 1px, @border-top-style: solid, @border-bottom-style: solid);
> td > span:last-child {
margin-left: 25px;
}
}
.o_stock_reports_default_style {
.o_stock_reports_lines(@border-width: 0px, @border-top-style: solid, @border-bottom-style: solid);
> td > span:last-child {
margin-left: 50px;
}
}
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="stockReports.buttons">
<button type="button" class='btn btn-primary btn-sm o_stock-widget-pdf'>PRINT</button>
</t>
<div t-name='stockReports.errorModal' class="modal" id="editable_error" tabindex="-1" data-backdrop="static" role="dialog" aria-hidden="true" style="z-index:9999;">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Error</h3>
</div>
<div class="modal-body">
<p id='insert_error' class='text-center'></p>
</div>
</div>
</div>
</div>
</templates>
@@ -0,0 +1,69 @@
<?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"></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"></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.quant'"><t t-set="trclass" t-value="'o_stock_reports_level0'"/></t>
<t t-if="l.model == 'stock.move'"><t t-set="trclass" t-value="'o_stock_reports_level1'"/></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="domainClass" t-value="'o_stock_reports_domain_line_1'"/>
</t>
<tr t-att-data-type="l.type" t-att-data-unfold="l.unfoldable" t-att-data-parent_id="l.parent_id" t-att-data-id="l.id" t-att-data-stream="'l.stream'" t-att-data-model_id="l.model_id" t-att-data-model="l.model" t-att-class="trclass">
<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-stream="l.stream" 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-parent_quant="l.parent_quant">
<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"></i></span>
</t>
</t>
<t t-if="l.res_model == 'mrp.production' and l.reference == c">
<span t-if="c">
<a t-att-data-active-id="l.res_id" t-att-data-res-model="l.res_model" class="o_stock_reports_web_action"><t t-raw="c" /></a>
</span>
</t>
<t t-if="l.res_model == 'stock.picking' and l.reference == c">
<span t-if="c">
<a t-att-data-active-id="l.res_id" t-att-data-res-model="l.res_model" class="o_stock_reports_web_action"><t t-raw="c" /></a>
</span>
</t>
<t t-if="l.res_model == 'mrp.unbuild' and l.reference == c">
<span t-if="c">
<a t-att-data-active-id="l.res_id" t-att-data-res-model="l.res_model" class="o_stock_reports_web_action"><t t-raw="c" /></a>
</span>
</t>
<t t-if="l.reference != c">
<t t-if="typeof c == 'string' || typeof c == 'number'"><span>
<t t-raw="c"/>
</span></t>
<t t-if="typeof c != 'string' &amp; typeof c != 'number'"><span t-att-style="c[1]">
<t t-raw="c[0]"/>
</span></t>
</t>
</td>
</t>
<td>
<t t-if="l.model == 'stock.quant'">
<span t-if="l.stream == 'downstream'" class="o_stock_reports_stream text-muted" data-stream="upstream" title="Upstream Traceability"><i class="fa fa-arrow-up"></i></span>
<span t-if="l.stream == 'upstream'" class="o_stock_reports_stream" data-stream="downstream" title="Downstream Traceability"><i class="fa fa-arrow-down"></i></span>
</t>
</td>
</tr>
</t>
</templates>
@@ -0,0 +1,129 @@
<?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.quant'"><t t-set="trclass" t-value="'o_stock_reports_level0'"/></t>
<t t-if="a.get('model') == 'stock.move'"><t t-set="trclass" t-value="'o_stock_reports_level1'"/></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-type="a['type']" t-att-data-stream="a['stream']" 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-class="trclass">
<t t-set="tdclass" t-value="''" />
<t t-if="a.get('unfoldable')"><t t-set="tdclass" t-value="'o_stock_reports_unfoldable'" /></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-data-stream="a['stream']" t-att-class="tdclass" t-att-data-level="a['level']" t-att-data-parent_quant="a['parent_quant']">
<t t-if="column == 1">
<span t-att-data-active-id="a['id']" t-att-class="a['type'] + ' ' + domainClass"></span>
<t t-if="a.get('unfoldable')">
<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"></i></span>
</t>
</t>
<span>
<t t-raw="c" />
</span>
</td>
</t>
<td>
<t t-if="a.get('model') == 'stock.quant'">
<span t-if="a['stream'] == 'downstream'" class="o_stock_reports_stream text-muted" data-stream="upstream" title="Upstream Traceability"><i class="fa fa-arrow-up"></i></span>
<span t-if="a['stream'] == 'upstream'" class="o_stock_reports_stream" data-stream="downstream" title="Downstream Traceability"><i class="fa fa-arrow-down"></i></span>
</t>
</td>
</tr>
</t>
</template>
<template id="report_stock_inventory">
<div class="container o_stock_reports_page o_stock_reports_no_print">
<span t-if="lines" class="o_report_heading text-left"><h1>Upstream Traceability</h1></span>
<span><t t-esc="context"/></span>
<div class="o_stock_reports_table">
<t t-if="not lines">
<span class="text-center"><h1>No operation made on this lot.</h1></span>
</t>
<t t-if="lines">
<table width='100%' class="table-responsive">
<thead>
<tr class="o_report_header">
<th class="o_report_line_header">PRODUCT</th>
<th class="o_report_line_header"></th>
<th class="o_report_line_header">QUANTITY</th>
<th class="o_report_line_header">LOCATION SOURCE</th>
<th class="o_report_line_header">LOCATION DESTINATION</th>
<th></th>
</tr>
</thead>
<tbody>
<t t-call="mrp_workorder.report_mrp_line"/>
</tbody>
</table>
</t>
</div>
</div>
</template>
<template id="assets_stock_print_report" inherit_id="web.assets_common" active="True">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/mrp_workorder/static/src/less/stock_traceability_report.less" />
</xpath>
</template>
<template id="report_stock_inventory_print">
<t t-raw="'&lt;base href=%s&gt;' % base_url"/>
<t t-call="web.html_container">
<t t-call-assets="mrp_workorder.assets_stock_print_report" t-js="False"/>
<t t-call='mrp_workorder.report_stock_body_print'/>
</t>
</template>
<template id="report_stock_body_print">
<div class="container o_stock_reports_page o_stock_reports_no_print">
<span class="text-left"><h3>Traceability Report</h3></span>
<div class="table-responsive">
<table width='100%' class="o_stock_reports_table">
<thead>
<tr class="o_report_header">
<th class="o_report_line_header">PRODUCT</th>
<th class="o_report_line_header"></th>
<th class="o_report_line_header">QUANTITY</th>
<th class="o_report_line_header">LOCATION SOURCE</th>
<th class="o_report_line_header">LOCATION DESTINATION</th>
</tr>
</thead>
<tbody>
<t t-set="space_td" t-value="'margin-left: 0'"/>
<t t-foreach="lines" t-as="a">
<t t-set="space_td" t-value="'margin-left: '+ str(a['level']) + 'px;'"/>
<t t-set="trclass" t-value="'o_stock_reports_default_style'"/>
<t t-if="a.get('model') == 'stock.quant'"><t t-set="trclass" t-value="'o_stock_reports_level0'"/></t>
<t t-if="a.get('model') == 'stock.move'"><t t-set="trclass" t-value="'o_stock_reports_level1'"/></t>
<t t-set="column" t-value="0" />
<tr t-att-data-type="a['type']" t-att-data-stream="a['stream']" t-att-data-id="a['id']" t-att-data-model="a['model']" t-att-class="trclass">
<t t-set="tdclass" t-value="''" />
<t t-if="a.get('unfoldable')"><t t-set="tdclass" t-value="'o_stock_reports_unfoldable'" /></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 t-if="column == 1">
<span t-att-style="space_td"></span>
</t>
<span>
<t t-raw="c" />
</span>
</td>
</t>
</tr>
</t>
</tbody>
</table>
</div>
</div>
</template>
</odoo>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="mrp_workorder_assets_backend" name="mrp_workorder assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mrp_workorder/static/src/js/mrp_workorder.js"></script>
<script type="text/javascript" src="/mrp_workorder/static/src/js/stock_traceability_report_backend.js"></script>
<script type="text/javascript" src="/mrp_workorder/static/src/js/stock_traceability_report_widgets.js"></script>
<link rel="stylesheet" href="/mrp_workorder/static/src/less/stock_traceability_report.less" />
</xpath>
</template>
</odoo>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_production_lot_form_inherit_mrp_workorder" model="ir.ui.view">
<field name="name">stock.production.lot.form.inherit.stock</field>
<field name="model">stock.production.lot</field>
<field name="inherit_id" ref="stock.view_production_lot_form"/>
<field name="arch" type="xml">
<button name="action_traceability" position="replace">
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Up/Down Traceability" type="action" attrs="{'invisible': [('quant_ids','=', [])]}"/>
</button>
</field>
</record>
<record id="mrp_production_form_view_inherit_mrp_workorder" model="ir.ui.view">
<field name="name">mrp.production.form.view.inherit.mrp.workorder</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Up/Down Traceability" type="action" attrs="{'invisible': [('state','!=', 'done')]}"/>
</div>
</field>
</record>
<record id="view_picking_form_inherit_mrp_workorder" model="ir.ui.view">
<field name="name">stock.picking.form.view.inherit.mrp.workorder</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Up/Down Traceability" type="action" attrs="{'invisible': [('state','!=', 'done')]}"/>
</div>
</field>
</record>
</odoo>