There were inconsistencies in the calls to `_render`. * the view context could contain information that misled developers. Indeed, the context and value of the view are not supposed to be found in the rendering. Thus by calling `ir.qweb` with the name of the template, we ensure that there is no unwanted information and in addition the cache key is that of the name of the template which saves a query. * the context used for rendering was modified by a method on `ir.ui.view`, except this is not information used by this model. There is now a `_prepare_environment` method residing on `ir.qweb`. This method allows to modify the value dictionary as well as the context in which the rendering will be done. This preparation of the data as well as my security check is done only once per rendering. This also saves some queries * Freeze options for rendering were inconsistent. It could be that options on which rendering depends were not part of the cache key. Thus, depending on the user who generated the generation of the rendering function, there was or was not information in the template. For example for automatic branding. This is no longer possible, because it is the context that is used. The options serving as a cache key are only recorded for information (for the profiling system for example). A simplification of the `ir.qweb.field` models could be made. The report rendering and call `ir.qweb` instead of `ir.ui.view`. Part-of: odoo/odoo#85110
248 lines
10 KiB
Python
248 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, models, _
|
|
from odoo.tools import config
|
|
from odoo.tools import format_datetime
|
|
from markupsafe import Markup
|
|
|
|
|
|
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'
|
|
_description = 'Traceability Report'
|
|
|
|
@api.model
|
|
def _get_move_lines(self, move_lines, line_id=None):
|
|
lines_seen = move_lines
|
|
lines_todo = list(move_lines)
|
|
while lines_todo:
|
|
move_line = lines_todo.pop(0)
|
|
# if MTO
|
|
if move_line.move_id.move_orig_ids:
|
|
lines = move_line.move_id.move_orig_ids.mapped('move_line_ids').filtered(
|
|
lambda m: m.lot_id == move_line.lot_id and m.state == 'done'
|
|
) - lines_seen
|
|
# if MTS
|
|
elif move_line.location_id.usage == 'internal':
|
|
lines = self.env['stock.move.line'].search([
|
|
('product_id', '=', move_line.product_id.id),
|
|
('lot_id', '=', move_line.lot_id.id),
|
|
('location_dest_id', '=', move_line.location_id.id),
|
|
('id', 'not in', lines_seen.ids),
|
|
('date', '<=', move_line.date),
|
|
('state', '=', 'done')
|
|
])
|
|
else:
|
|
continue
|
|
if line_id is None or line_id in lines.ids:
|
|
lines_todo += list(lines)
|
|
lines_seen |= lines
|
|
return lines_seen - move_lines
|
|
|
|
@api.model
|
|
def get_lines(self, line_id=None, **kw):
|
|
context = dict(self.env.context)
|
|
model = kw and kw['model_name'] or context.get('model')
|
|
rec_id = kw and kw['model_id'] or context.get('active_id')
|
|
level = kw and kw['level'] or 1
|
|
lines = self.env['stock.move.line']
|
|
move_line = self.env['stock.move.line']
|
|
if rec_id and model == 'stock.lot':
|
|
lines = move_line.search([
|
|
('lot_id', '=', context.get('lot_name') or rec_id),
|
|
('state', '=', 'done'),
|
|
])
|
|
elif rec_id and model == 'stock.move.line' and context.get('lot_name'):
|
|
record = self.env[model].browse(rec_id)
|
|
dummy, is_used = self._get_linked_move_lines(record)
|
|
if is_used:
|
|
lines = is_used
|
|
elif rec_id and model in ('stock.picking', 'mrp.production'):
|
|
record = self.env[model].browse(rec_id)
|
|
if model == 'stock.picking':
|
|
lines = record.move_ids.move_line_ids.filtered(lambda m: m.lot_id and m.state == 'done')
|
|
else:
|
|
lines = record.move_finished_ids.mapped('move_line_ids').filtered(lambda m: m.state == 'done')
|
|
move_line_vals = self._lines(line_id, model_id=rec_id, model=model, level=level, move_lines=lines)
|
|
final_vals = sorted(move_line_vals, key=lambda v: v['date'], reverse=True)
|
|
lines = self._final_vals_to_lines(final_vals, level)
|
|
return lines
|
|
|
|
@api.model
|
|
def _get_reference(self, move_line):
|
|
res_model = ''
|
|
ref = ''
|
|
res_id = False
|
|
picking_id = move_line.picking_id or move_line.move_id.picking_id
|
|
if picking_id:
|
|
res_model = 'stock.picking'
|
|
res_id = picking_id.id
|
|
ref = picking_id.name
|
|
elif move_line.move_id.is_inventory:
|
|
res_model = 'stock.move'
|
|
res_id = move_line.move_id.id
|
|
ref = 'Inventory Adjustment'
|
|
elif move_line.move_id.scrapped and move_line.move_id.scrap_ids:
|
|
res_model = 'stock.scrap'
|
|
res_id = move_line.move_id.scrap_ids[0].id
|
|
ref = move_line.move_id.scrap_ids[0].name
|
|
return res_model, res_id, ref
|
|
|
|
@api.model
|
|
def _quantity_to_str(self, from_uom, to_uom, qty):
|
|
""" workaround to apply the float rounding logic of t-esc on data prepared server side """
|
|
qty = from_uom._compute_quantity(qty, to_uom, rounding_method='HALF-UP')
|
|
return self.env['ir.qweb.field.float'].value_to_html(qty, {'decimal_precision': 'Product Unit of Measure'})
|
|
|
|
def _get_usage(self, move_line):
|
|
usage = ''
|
|
if (move_line.location_id.usage == 'internal') and (move_line.location_dest_id.usage == 'internal'):
|
|
usage = 'internal'
|
|
elif (move_line.location_id.usage != 'internal') and (move_line.location_dest_id.usage == 'internal'):
|
|
usage = 'in'
|
|
else:
|
|
usage = 'out'
|
|
return usage
|
|
|
|
def _make_dict_move(self, level, parent_id, move_line, unfoldable=False):
|
|
res_model, res_id, ref = self._get_reference(move_line)
|
|
dummy, is_used = self._get_linked_move_lines(move_line)
|
|
data = [{
|
|
'level': level,
|
|
'unfoldable': unfoldable,
|
|
'date': move_line.move_id.date,
|
|
'parent_id': parent_id,
|
|
'is_used': bool(is_used),
|
|
'usage': self._get_usage(move_line),
|
|
'model_id': move_line.id,
|
|
'model': 'stock.move.line',
|
|
'product_id': move_line.product_id.display_name,
|
|
'product_qty_uom': "%s %s" % (self._quantity_to_str(move_line.product_uom_id, move_line.product_id.uom_id, move_line.qty_done), move_line.product_id.uom_id.name),
|
|
'lot_name': move_line.lot_id.name,
|
|
'lot_id': move_line.lot_id.id,
|
|
'location_source': move_line.location_id.name,
|
|
'location_destination': move_line.location_dest_id.name,
|
|
'reference_id': ref,
|
|
'res_id': res_id,
|
|
'res_model': res_model}]
|
|
return data
|
|
|
|
@api.model
|
|
def _final_vals_to_lines(self, final_vals, level):
|
|
lines = []
|
|
for data in final_vals:
|
|
lines.append({
|
|
'id': autoIncrement(),
|
|
'model': data['model'],
|
|
'model_id': data['model_id'],
|
|
'parent_id': data['parent_id'],
|
|
'usage': data.get('usage', False),
|
|
'is_used': data.get('is_used', False),
|
|
'lot_name': data.get('lot_name', False),
|
|
'lot_id': data.get('lot_id', False),
|
|
'reference': data.get('reference_id', False),
|
|
'res_id': data.get('res_id', False),
|
|
'res_model': data.get('res_model', False),
|
|
'columns': [data.get('reference_id', False),
|
|
data.get('product_id', False),
|
|
format_datetime(self.env, data.get('date', False), tz=False, dt_format=False),
|
|
data.get('lot_name', False),
|
|
data.get('location_source', False),
|
|
data.get('location_destination', False),
|
|
data.get('product_qty_uom', 0)],
|
|
'level': level,
|
|
'unfoldable': data['unfoldable'],
|
|
})
|
|
return lines
|
|
|
|
def _get_linked_move_lines(self, move_line):
|
|
""" This method will return the consumed line or produced line for this operation."""
|
|
return False, False
|
|
|
|
@api.model
|
|
def _lines(self, line_id=None, model_id=False, model=False, level=0, move_lines=[], **kw):
|
|
final_vals = []
|
|
lines = move_lines or []
|
|
if model and line_id:
|
|
move_line = self.env[model].browse(model_id)
|
|
move_lines, is_used = self._get_linked_move_lines(move_line)
|
|
if move_lines:
|
|
lines = move_lines
|
|
else:
|
|
# Traceability in case of consumed in.
|
|
lines = self._get_move_lines(move_line, line_id=line_id)
|
|
for line in lines:
|
|
unfoldable = False
|
|
if line.consume_line_ids or (line.lot_id and self._get_move_lines(line) and model != "stock.lot"):
|
|
unfoldable = True
|
|
final_vals += self._make_dict_move(level, parent_id=line_id, move_line=line, unfoldable=unfoldable)
|
|
return final_vals
|
|
|
|
def get_pdf_lines(self, line_data=[]):
|
|
lines = []
|
|
for line in line_data:
|
|
model = self.env[line['model_name']].browse(line['model_id'])
|
|
unfoldable = False
|
|
if line.get('unfoldable'):
|
|
unfoldable = True
|
|
final_vals = self._make_dict_move(line['level'], parent_id=line['id'], move_line=model, unfoldable=unfoldable)
|
|
lines.append(self._final_vals_to_lines(final_vals, line['level'])[0])
|
|
return lines
|
|
|
|
def get_pdf(self, line_data=None):
|
|
line_data = [] if line_data is None else 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,
|
|
}
|
|
|
|
context = dict(self.env.context)
|
|
if not config['test_enable']:
|
|
context['commit_assetsbundle'] = True
|
|
if context.get('active_id') and context.get('active_model'):
|
|
rcontext['reference'] = self.env[context.get('active_model')].browse(int(context.get('active_id'))).display_name
|
|
|
|
body = self.env['ir.ui.view'].with_context(context)._render_template(
|
|
"stock.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=Markup(header.decode())))
|
|
|
|
return self.env['ir.actions.report']._run_wkhtmltopdf(
|
|
[body],
|
|
header=header.decode(),
|
|
landscape=True,
|
|
specific_paperformat_args={'data-report-margin-top': 17, 'data-report-header-spacing': 12}
|
|
)
|
|
|
|
def _get_html(self):
|
|
result = {}
|
|
rcontext = {}
|
|
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
|
|
|
|
@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()
|