Browse Source
[WIP] mis_builder refactoring: split mis_builder.py in two
[WIP] mis_builder refactoring: split mis_builder.py in two
mis_report.py has the heavy stuff, and contains all the computation and rendering codepull/189/head
Stéphane Bidoul
9 years ago
3 changed files with 412 additions and 399 deletions
-
3mis_builder/models/__init__.py
-
398mis_builder/models/mis_report.py
-
410mis_builder/models/mis_report_instance.py
@ -0,0 +1,410 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# © 2014-2016 ACSONE SA/NV (<http://acsone.eu>) |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from openerp import api, fields, models, _ |
||||
|
|
||||
|
import datetime |
||||
|
import logging |
||||
|
|
||||
|
from .aep import AccountingExpressionProcessor as AEP |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class MisReportInstancePeriod(models.Model): |
||||
|
""" A MIS report instance has the logic to compute |
||||
|
a report template for a given date period. |
||||
|
|
||||
|
Periods have a duration (day, week, fiscal period) and |
||||
|
are defined as an offset relative to a pivot date. |
||||
|
""" |
||||
|
|
||||
|
@api.one |
||||
|
@api.depends('report_instance_id.pivot_date', |
||||
|
'report_instance_id.comparison_mode', |
||||
|
'type', 'offset', 'duration', 'mode') |
||||
|
def _compute_dates(self): |
||||
|
self.date_from = False |
||||
|
self.date_to = False |
||||
|
self.valid = False |
||||
|
report = self.report_instance_id |
||||
|
d = fields.Date.from_string(report.pivot_date) |
||||
|
if not report.comparison_mode: |
||||
|
self.date_from = report.date_from |
||||
|
self.date_to = report.date_to |
||||
|
self.valid = True |
||||
|
elif self.mode == 'fix': |
||||
|
self.date_from = self.manual_date_from |
||||
|
self.date_to = self.manual_date_to |
||||
|
self.valid = True |
||||
|
elif self.type == 'd': |
||||
|
date_from = d + datetime.timedelta(days=self.offset) |
||||
|
date_to = date_from + \ |
||||
|
datetime.timedelta(days=self.duration - 1) |
||||
|
self.date_from = fields.Date.to_string(date_from) |
||||
|
self.date_to = fields.Date.to_string(date_to) |
||||
|
self.valid = True |
||||
|
elif self.type == 'w': |
||||
|
date_from = d - datetime.timedelta(d.weekday()) |
||||
|
date_from = date_from + datetime.timedelta(days=self.offset * 7) |
||||
|
date_to = date_from + \ |
||||
|
datetime.timedelta(days=(7 * self.duration) - 1) |
||||
|
self.date_from = fields.Date.to_string(date_from) |
||||
|
self.date_to = fields.Date.to_string(date_to) |
||||
|
self.valid = True |
||||
|
elif self.type == 'date_range': |
||||
|
date_range_obj = self.env['date.range'] |
||||
|
current_periods = date_range_obj.search( |
||||
|
[('type_id', '=', self.date_range_type_id.id), |
||||
|
('date_start', '<=', d), |
||||
|
('date_end', '>=', d), |
||||
|
('company_id', '=', self.report_instance_id.company_id.id)]) |
||||
|
if current_periods: |
||||
|
all_periods = date_range_obj.search( |
||||
|
[('type_id', '=', self.date_range_type_id.id), |
||||
|
('company_id', '=', |
||||
|
self.report_instance_id.company_id.id)], |
||||
|
order='date_start') |
||||
|
all_period_ids = [p.id for p in all_periods] |
||||
|
p = all_period_ids.index(current_periods[0].id) + self.offset |
||||
|
if p >= 0 and p + self.duration <= len(all_period_ids): |
||||
|
periods = all_periods[p:p + self.duration] |
||||
|
self.date_from = periods[0].date_start |
||||
|
self.date_to = periods[-1].date_end |
||||
|
self.valid = True |
||||
|
|
||||
|
_name = 'mis.report.instance.period' |
||||
|
|
||||
|
name = fields.Char(size=32, required=True, |
||||
|
string='Description', translate=True) |
||||
|
mode = fields.Selection([('fix', 'Fix'), |
||||
|
('relative', 'Relative'), |
||||
|
], required=True, |
||||
|
default='fix') |
||||
|
type = fields.Selection([('d', _('Day')), |
||||
|
('w', _('Week')), |
||||
|
('date_range', _('Date Range')) |
||||
|
], |
||||
|
string='Period type') |
||||
|
date_range_type_id = fields.Many2one( |
||||
|
comodel_name='date.range.type', string='Date Range Type') |
||||
|
offset = fields.Integer(string='Offset', |
||||
|
help='Offset from current period', |
||||
|
default=-1) |
||||
|
duration = fields.Integer(string='Duration', |
||||
|
help='Number of periods', |
||||
|
default=1) |
||||
|
date_from = fields.Date(compute='_compute_dates', string="From") |
||||
|
date_to = fields.Date(compute='_compute_dates', string="To") |
||||
|
manual_date_from = fields.Date(string="From") |
||||
|
manual_date_to = fields.Date(string="To") |
||||
|
date_range_id = fields.Many2one( |
||||
|
comodel_name='date.range', |
||||
|
string='Date Range') |
||||
|
valid = fields.Boolean(compute='_compute_dates', |
||||
|
type='boolean', |
||||
|
string='Valid') |
||||
|
sequence = fields.Integer(string='Sequence', default=100) |
||||
|
report_instance_id = fields.Many2one('mis.report.instance', |
||||
|
string='Report Instance', |
||||
|
ondelete='cascade') |
||||
|
comparison_column_ids = fields.Many2many( |
||||
|
comodel_name='mis.report.instance.period', |
||||
|
relation='mis_report_instance_period_rel', |
||||
|
column1='period_id', |
||||
|
column2='compare_period_id', |
||||
|
string='Compare with') |
||||
|
normalize_factor = fields.Integer( |
||||
|
string='Factor', |
||||
|
help='Factor to use to normalize the period (used in comparison', |
||||
|
default=1) |
||||
|
subkpi_ids = fields.Many2many( |
||||
|
'mis.report.subkpi', |
||||
|
string="Sub KPI Filter") |
||||
|
|
||||
|
_order = 'sequence, id' |
||||
|
|
||||
|
_sql_constraints = [ |
||||
|
('duration', 'CHECK (duration>0)', |
||||
|
'Wrong duration, it must be positive!'), |
||||
|
('normalize_factor', 'CHECK (normalize_factor>0)', |
||||
|
'Wrong normalize factor, it must be positive!'), |
||||
|
('name_unique', 'unique(name, report_instance_id)', |
||||
|
'Period name should be unique by report'), |
||||
|
] |
||||
|
|
||||
|
@api.onchange('date_range_id') |
||||
|
def onchange_date_range(self): |
||||
|
for record in self: |
||||
|
record.manual_date_from = record.date_range_id.date_start |
||||
|
record.manual_date_to = record.date_range_id.date_end |
||||
|
record.name = record.date_range_id.name |
||||
|
|
||||
|
@api.multi |
||||
|
def _get_additional_move_line_filter(self): |
||||
|
""" Prepare a filter to apply on all move lines |
||||
|
|
||||
|
This filter is applied with a AND operator on all |
||||
|
accounting expression domains. This hook is intended |
||||
|
to be inherited, and is useful to implement filtering |
||||
|
on analytic dimensions or operational units. |
||||
|
|
||||
|
Returns an Odoo domain expression (a python list) |
||||
|
compatible with account.move.line.""" |
||||
|
self.ensure_one() |
||||
|
return [] |
||||
|
|
||||
|
@api.multi |
||||
|
def _get_additional_query_filter(self, query): |
||||
|
""" Prepare an additional filter to apply on the query |
||||
|
|
||||
|
This filter is combined to the query domain with a AND |
||||
|
operator. This hook is intended |
||||
|
to be inherited, and is useful to implement filtering |
||||
|
on analytic dimensions or operational units. |
||||
|
|
||||
|
Returns an Odoo domain expression (a python list) |
||||
|
compatible with the model of the query.""" |
||||
|
self.ensure_one() |
||||
|
return [] |
||||
|
|
||||
|
@api.multi |
||||
|
def drilldown(self, expr): |
||||
|
self.ensure_one() |
||||
|
# TODO FIXME: drilldown by account |
||||
|
if AEP.has_account_var(expr): |
||||
|
aep = AEP(self.env) |
||||
|
aep.parse_expr(expr) |
||||
|
aep.done_parsing(self.report_instance_id.company_id) |
||||
|
domain = aep.get_aml_domain_for_expr( |
||||
|
expr, |
||||
|
self.date_from, self.date_to, |
||||
|
self.report_instance_id.target_move, |
||||
|
self.report_instance_id.company_id) |
||||
|
domain.extend(self._get_additional_move_line_filter()) |
||||
|
return { |
||||
|
'name': expr + ' - ' + self.name, |
||||
|
'domain': domain, |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'res_model': 'account.move.line', |
||||
|
'views': [[False, 'list'], [False, 'form']], |
||||
|
'view_type': 'list', |
||||
|
'view_mode': 'list', |
||||
|
'target': 'current', |
||||
|
} |
||||
|
else: |
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class MisReportInstance(models.Model): |
||||
|
"""The MIS report instance combines everything to compute |
||||
|
a MIS report template for a set of periods.""" |
||||
|
|
||||
|
@api.one |
||||
|
@api.depends('date') |
||||
|
def _compute_pivot_date(self): |
||||
|
if self.date: |
||||
|
self.pivot_date = self.date |
||||
|
else: |
||||
|
self.pivot_date = fields.Date.context_today(self) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_company(self): |
||||
|
return self.env['res.company'].\ |
||||
|
_company_default_get('mis.report.instance') |
||||
|
|
||||
|
_name = 'mis.report.instance' |
||||
|
|
||||
|
name = fields.Char(required=True, |
||||
|
string='Name', translate=True) |
||||
|
description = fields.Char(related='report_id.description', |
||||
|
readonly=True) |
||||
|
date = fields.Date(string='Base date', |
||||
|
help='Report base date ' |
||||
|
'(leave empty to use current date)') |
||||
|
pivot_date = fields.Date(compute='_compute_pivot_date', |
||||
|
string="Pivot date") |
||||
|
report_id = fields.Many2one('mis.report', |
||||
|
required=True, |
||||
|
string='Report') |
||||
|
period_ids = fields.One2many('mis.report.instance.period', |
||||
|
'report_instance_id', |
||||
|
required=True, |
||||
|
string='Periods', |
||||
|
copy=True) |
||||
|
target_move = fields.Selection([('posted', 'All Posted Entries'), |
||||
|
('all', 'All Entries')], |
||||
|
string='Target Moves', |
||||
|
required=True, |
||||
|
default='posted') |
||||
|
company_id = fields.Many2one(comodel_name='res.company', |
||||
|
string='Company', |
||||
|
default=_default_company, |
||||
|
required=True) |
||||
|
landscape_pdf = fields.Boolean(string='Landscape PDF') |
||||
|
comparison_mode = fields.Boolean( |
||||
|
compute="_compute_comparison_mode", |
||||
|
inverse="_inverse_comparison_mode") |
||||
|
date_range_id = fields.Many2one( |
||||
|
comodel_name='date.range', |
||||
|
string='Date Range') |
||||
|
date_from = fields.Date(string="From") |
||||
|
date_to = fields.Date(string="To") |
||||
|
temporary = fields.Boolean(default=False) |
||||
|
|
||||
|
@api.multi |
||||
|
def save_report(self): |
||||
|
self.ensure_one() |
||||
|
self.write({'temporary': False}) |
||||
|
action = self.env.ref('mis_builder.mis_report_instance_view_action') |
||||
|
res = action.read()[0] |
||||
|
view = self.env.ref('mis_builder.mis_report_instance_view_form') |
||||
|
res.update({ |
||||
|
'views': [(view.id, 'form')], |
||||
|
'res_id': self.id, |
||||
|
}) |
||||
|
return res |
||||
|
|
||||
|
@api.model |
||||
|
def _vacuum_report(self, hours=24): |
||||
|
clear_date = fields.Datetime.to_string( |
||||
|
datetime.datetime.now() - datetime.timedelta(hours=hours)) |
||||
|
reports = self.search([ |
||||
|
('write_date', '<', clear_date), |
||||
|
('temporary', '=', True), |
||||
|
]) |
||||
|
_logger.debug('Vacuum %s Temporary MIS Builder Report', len(reports)) |
||||
|
return reports.unlink() |
||||
|
|
||||
|
@api.one |
||||
|
def copy(self, default=None): |
||||
|
default = dict(default or {}) |
||||
|
default['name'] = _('%s (copy)') % self.name |
||||
|
return super(MisReportInstance, self).copy(default) |
||||
|
|
||||
|
def _format_date(self, date): |
||||
|
# format date following user language |
||||
|
lang_model = self.env['res.lang'] |
||||
|
lang_id = lang_model._lang_get(self.env.user.lang) |
||||
|
date_format = lang_model.browse(lang_id).date_format |
||||
|
return datetime.datetime.strftime( |
||||
|
fields.Date.from_string(date), date_format) |
||||
|
|
||||
|
@api.multi |
||||
|
@api.depends('date_from') |
||||
|
def _compute_comparison_mode(self): |
||||
|
for instance in self: |
||||
|
instance.comparison_mode = bool(instance.period_ids) and\ |
||||
|
not bool(instance.date_from) |
||||
|
|
||||
|
@api.multi |
||||
|
def _inverse_comparison_mode(self): |
||||
|
for record in self: |
||||
|
if not record.comparison_mode: |
||||
|
if not record.date_from: |
||||
|
record.date_from = datetime.now() |
||||
|
if not record.date_to: |
||||
|
record.date_to = datetime.now() |
||||
|
record.period_ids.unlink() |
||||
|
record.write({'period_ids': [ |
||||
|
(0, 0, { |
||||
|
'name': 'Default', |
||||
|
'type': 'd', |
||||
|
}) |
||||
|
]}) |
||||
|
else: |
||||
|
record.date_from = None |
||||
|
record.date_to = None |
||||
|
|
||||
|
@api.onchange('date_range_id') |
||||
|
def onchange_date_range(self): |
||||
|
for record in self: |
||||
|
record.date_from = record.date_range_id.date_start |
||||
|
record.date_to = record.date_range_id.date_end |
||||
|
|
||||
|
@api.multi |
||||
|
def preview(self): |
||||
|
assert len(self) == 1 |
||||
|
view_id = self.env.ref('mis_builder.' |
||||
|
'mis_report_instance_result_view_form') |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'res_model': 'mis.report.instance', |
||||
|
'res_id': self.id, |
||||
|
'view_mode': 'form', |
||||
|
'view_type': 'form', |
||||
|
'view_id': view_id.id, |
||||
|
'target': 'current', |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def print_pdf(self): |
||||
|
self.ensure_one() |
||||
|
return { |
||||
|
'name': 'MIS report instance QWEB PDF report', |
||||
|
'model': 'mis.report.instance', |
||||
|
'type': 'ir.actions.report.xml', |
||||
|
'report_name': 'mis_builder.report_mis_report_instance', |
||||
|
'report_type': 'qweb-pdf', |
||||
|
'context': self.env.context, |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def export_xls(self): |
||||
|
self.ensure_one() |
||||
|
return { |
||||
|
'name': 'MIS report instance XLSX report', |
||||
|
'model': 'mis.report.instance', |
||||
|
'type': 'ir.actions.report.xml', |
||||
|
'report_name': 'mis.report.instance.xlsx', |
||||
|
'report_type': 'xlsx', |
||||
|
'context': self.env.context, |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def display_settings(self): |
||||
|
assert len(self.ids) <= 1 |
||||
|
view_id = self.env.ref('mis_builder.mis_report_instance_view_form') |
||||
|
return { |
||||
|
'type': 'ir.actions.act_window', |
||||
|
'res_model': 'mis.report.instance', |
||||
|
'res_id': self.id if self.id else False, |
||||
|
'view_mode': 'form', |
||||
|
'view_type': 'form', |
||||
|
'views': [(view_id.id, 'form')], |
||||
|
'view_id': view_id.id, |
||||
|
'target': 'current', |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def compute(self): |
||||
|
self.ensure_one() |
||||
|
aep = self.report_id._prepare_aep(self.company_id) |
||||
|
kpi_matrix = self.report_id._prepare_kpi_matrix() |
||||
|
for period in self.period_ids: |
||||
|
# add the column header |
||||
|
if period.date_from == period.date_to: |
||||
|
comment = self._format_date(period.date_from) |
||||
|
else: |
||||
|
# from, to |
||||
|
date_from = self._format_date(period.date_from) |
||||
|
date_to = self._format_date(period.date_to) |
||||
|
comment = _('from %s to %s') % (date_from, date_to) |
||||
|
self.report_id._declare_and_compute_period( |
||||
|
kpi_matrix, |
||||
|
period.id, |
||||
|
period.name, |
||||
|
comment, |
||||
|
aep, |
||||
|
period.date_from, |
||||
|
period.date_to, |
||||
|
self.target_move, |
||||
|
self.company_id, |
||||
|
period.subkpi_ids, |
||||
|
period._get_additional_move_line_filter, |
||||
|
period._get_additional_query_filter) |
||||
|
for comparison_column in period.comparison_column_ids: |
||||
|
kpi_matrix.declare_comparison(period.id, comparison_column.id) |
||||
|
kpi_matrix.compute_comparisons() |
||||
|
return kpi_matrix.as_dict() |
Write
Preview
Loading…
Cancel
Save
Reference in new issue