Files
odoo_source/addons/stock/report/stock_traceability.py
T
Gorash 880954ebfc [IMP] *: remove _render from ir.ui.view and simplify report
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
2022-03-29 10:56:15 +02:00

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()