From 5fcdb06010f85fc85bc203d844f13678ed6ea7c4 Mon Sep 17 00:00:00 2001 From: Pierre Masereel Date: Tue, 6 Jun 2017 11:40:31 +0200 Subject: [PATCH] [MOV] stock: move traceability report from mrp_workorder --- addons/stock/__manifest__.py | 4 + addons/stock/controllers/main.py | 33 +++ .../data/stock_traceability_report_data.xml | 10 + addons/stock/models/__init__.py | 1 + addons/stock/models/stock_traceability.py | 263 ++++++++++++++++++ .../js/stock_traceability_report_backend.js | 113 ++++++++ .../js/stock_traceability_report_widgets.js | 117 ++++++++ .../src/less/stock_traceability_report.less | 78 ++++++ .../xml/stock_traceability_report_backend.xml | 23 ++ .../xml/stock_traceability_report_line.xml | 69 +++++ .../stock/views/report_stock_traceability.xml | 129 +++++++++ addons/stock/views/stock_template.xml | 13 + .../views/stock_traceability_report_views.xml | 37 +++ 13 files changed, 890 insertions(+) create mode 100644 addons/stock/data/stock_traceability_report_data.xml create mode 100644 addons/stock/models/stock_traceability.py create mode 100644 addons/stock/static/src/js/stock_traceability_report_backend.js create mode 100644 addons/stock/static/src/js/stock_traceability_report_widgets.js create mode 100644 addons/stock/static/src/less/stock_traceability_report.less create mode 100644 addons/stock/static/src/xml/stock_traceability_report_backend.xml create mode 100644 addons/stock/static/src/xml/stock_traceability_report_line.xml create mode 100644 addons/stock/views/report_stock_traceability.xml create mode 100644 addons/stock/views/stock_template.xml create mode 100644 addons/stock/views/stock_traceability_report_views.xml diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py index 2b7fd6d6b8f..2638289e67a 100644 --- a/addons/stock/__manifest__.py +++ b/addons/stock/__manifest__.py @@ -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', diff --git a/addons/stock/controllers/main.py b/addons/stock/controllers/main.py index 2fb1520fc34..c01954ce6bc 100644 --- a/addons/stock/controllers/main.py +++ b/addons/stock/controllers/main.py @@ -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///', 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))) diff --git a/addons/stock/data/stock_traceability_report_data.xml b/addons/stock/data/stock_traceability_report_data.xml new file mode 100644 index 00000000000..8e8f495753f --- /dev/null +++ b/addons/stock/data/stock_traceability_report_data.xml @@ -0,0 +1,10 @@ + + + + + Upstream Traceability + stock_report_generic + + + + diff --git a/addons/stock/models/__init__.py b/addons/stock/models/__init__.py index bb2733ff712..c31511010c0 100644 --- a/addons/stock/models/__init__.py +++ b/addons/stock/models/__init__.py @@ -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 diff --git a/addons/stock/models/stock_traceability.py b/addons/stock/models/stock_traceability.py new file mode 100644 index 00000000000..e71c50c7859 --- /dev/null +++ b/addons/stock/models/stock_traceability.py @@ -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() diff --git a/addons/stock/static/src/js/stock_traceability_report_backend.js b/addons/stock/static/src/js/stock_traceability_report_backend.js new file mode 100644 index 00000000000..4a47f5abef4 --- /dev/null +++ b/addons/stock/static/src/js/stock_traceability_report_backend.js @@ -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('

Downstream Traceability

'); + } + }); + }, + 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; +}); diff --git a/addons/stock/static/src/js/stock_traceability_report_widgets.js b/addons/stock/static/src/js/stock_traceability_report_widgets.js new file mode 100644 index 00000000000..82c612e17b1 --- /dev/null +++ b/addons/stock/static/src/js/stock_traceability_report_widgets.js @@ -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; + +}); diff --git a/addons/stock/static/src/less/stock_traceability_report.less b/addons/stock/static/src/less/stock_traceability_report.less new file mode 100644 index 00000000000..2950ec1361f --- /dev/null +++ b/addons/stock/static/src/less/stock_traceability_report.less @@ -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; + } + } +} diff --git a/addons/stock/static/src/xml/stock_traceability_report_backend.xml b/addons/stock/static/src/xml/stock_traceability_report_backend.xml new file mode 100644 index 00000000000..e0ff61dc1d8 --- /dev/null +++ b/addons/stock/static/src/xml/stock_traceability_report_backend.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/addons/stock/static/src/xml/stock_traceability_report_line.xml b/addons/stock/static/src/xml/stock_traceability_report_line.xml new file mode 100644 index 00000000000..ad96dc654c0 --- /dev/null +++ b/addons/stock/static/src/xml/stock_traceability_report_line.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/stock/views/report_stock_traceability.xml b/addons/stock/views/report_stock_traceability.xml new file mode 100644 index 00000000000..c975c7723ee --- /dev/null +++ b/addons/stock/views/report_stock_traceability.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + diff --git a/addons/stock/views/stock_template.xml b/addons/stock/views/stock_template.xml new file mode 100644 index 00000000000..aed3c368a9e --- /dev/null +++ b/addons/stock/views/stock_template.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/addons/stock/views/stock_traceability_report_views.xml b/addons/stock/views/stock_traceability_report_views.xml new file mode 100644 index 00000000000..6296e3cffd2 --- /dev/null +++ b/addons/stock/views/stock_traceability_report_views.xml @@ -0,0 +1,37 @@ + + + + + stock.production.lot.form.inherit.stock + stock.production.lot + + + + + + + + mrp.production.form.view.inherit.mrp.workorder + mrp.production + + +
+
+
+
+ + + stock.picking.form.view.inherit.mrp.workorder + stock.picking + + +
+
+
+
+ +