[IMP] mis_builder: new api
parent
aeb1b48cdf
commit
203891ddb5
|
@ -23,17 +23,13 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil import parser
|
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from openerp import api
|
|
||||||
from openerp.api import Environment
|
from openerp import api, fields, models, _
|
||||||
from openerp.osv import orm, fields
|
|
||||||
from openerp import tools
|
|
||||||
from openerp.tools.safe_eval import safe_eval
|
from openerp.tools.safe_eval import safe_eval
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
from .aep import AccountingExpressionProcessor as AEP
|
from .aep import AccountingExpressionProcessor as AEP
|
||||||
|
|
||||||
|
@ -53,14 +49,13 @@ def _get_selection_label(selection, value):
|
||||||
|
|
||||||
|
|
||||||
def _utc_midnight(d, tz_name, add_day=0):
|
def _utc_midnight(d, tz_name, add_day=0):
|
||||||
d = datetime.strptime(d, tools.DEFAULT_SERVER_DATE_FORMAT)
|
d = fields.Date.from_string(d)
|
||||||
if add_day:
|
if add_day:
|
||||||
d = d + timedelta(days=add_day)
|
d = d + timedelta(days=add_day)
|
||||||
utc_tz = pytz.timezone('UTC')
|
utc_tz = pytz.timezone('UTC')
|
||||||
context_tz = pytz.timezone(tz_name)
|
context_tz = pytz.timezone(tz_name)
|
||||||
local_timestamp = context_tz.localize(d, is_dst=False)
|
local_timestamp = context_tz.localize(d, is_dst=False)
|
||||||
return datetime.strftime(local_timestamp.astimezone(utc_tz),
|
return fields.Datetime.to_string(local_timestamp.astimezone(utc_tz))
|
||||||
tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
|
|
||||||
|
|
||||||
def _python_var(var_str):
|
def _python_var(var_str):
|
||||||
|
@ -71,7 +66,7 @@ def _is_valid_python_var(name):
|
||||||
return re.match("[_A-Za-z][_a-zA-Z0-9]*$", name)
|
return re.match("[_A-Za-z][_a-zA-Z0-9]*$", name)
|
||||||
|
|
||||||
|
|
||||||
class mis_report_kpi(orm.Model):
|
class MisReportKpi(models.Model):
|
||||||
""" A KPI is an element (ie a line) of a MIS report.
|
""" A KPI is an element (ie a line) of a MIS report.
|
||||||
|
|
||||||
In addition to a name and description, it has an expression
|
In addition to a name and description, it has an expression
|
||||||
|
@ -84,151 +79,138 @@ class mis_report_kpi(orm.Model):
|
||||||
|
|
||||||
_name = 'mis.report.kpi'
|
_name = 'mis.report.kpi'
|
||||||
|
|
||||||
_columns = {
|
name = fields.Char(size=32, required=True,
|
||||||
'name': fields.char(size=32, required=True,
|
string='Name')
|
||||||
string='Name'),
|
description = fields.Char(required=True,
|
||||||
'description': fields.char(required=True,
|
|
||||||
string='Description',
|
string='Description',
|
||||||
translate=True),
|
translate=True)
|
||||||
'expression': fields.char(required=True,
|
expression = fields.Char(required=True,
|
||||||
string='Expression'),
|
string='Expression')
|
||||||
'default_css_style': fields.char(
|
default_css_style = fields.Char(string='Default CSS style')
|
||||||
string='Default CSS style'),
|
css_style = fields.Char(string='CSS style expression')
|
||||||
'css_style': fields.char(string='CSS style expression'),
|
type = fields.Selection([('num', _('Numeric')),
|
||||||
'type': fields.selection([('num', _('Numeric')),
|
|
||||||
('pct', _('Percentage')),
|
('pct', _('Percentage')),
|
||||||
('str', _('String'))],
|
('str', _('String'))],
|
||||||
required=True,
|
required=True,
|
||||||
string='Type'),
|
string='Type',
|
||||||
'divider': fields.selection([('1e-6', _('µ')),
|
default='num')
|
||||||
|
divider = fields.Selection([('1e-6', _('µ')),
|
||||||
('1e-3', _('m')),
|
('1e-3', _('m')),
|
||||||
('1', _('1')),
|
('1', _('1')),
|
||||||
('1e3', _('k')),
|
('1e3', _('k')),
|
||||||
('1e6', _('M'))],
|
('1e6', _('M'))],
|
||||||
string='Factor'),
|
string='Factor',
|
||||||
'dp': fields.integer(string='Rounding'),
|
default='1')
|
||||||
'suffix': fields.char(size=16, string='Suffix'),
|
dp = fields.Integer(string='Rounding', default=0)
|
||||||
'compare_method': fields.selection([('diff', _('Difference')),
|
suffix = fields.Char(size=16, string='Suffix')
|
||||||
|
compare_method = fields.Selection([('diff', _('Difference')),
|
||||||
('pct', _('Percentage')),
|
('pct', _('Percentage')),
|
||||||
('none', _('None'))],
|
('none', _('None'))],
|
||||||
required=True,
|
required=True,
|
||||||
string='Comparison Method'),
|
string='Comparison Method',
|
||||||
'sequence': fields.integer(string='Sequence'),
|
default='pct')
|
||||||
'report_id': fields.many2one('mis.report', string='Report',
|
sequence = fields.Integer(string='Sequence', default=100)
|
||||||
ondelete='cascade'),
|
report_id = fields.Many2one('mis.report',
|
||||||
}
|
string='Report',
|
||||||
|
ondelete='cascade')
|
||||||
_defaults = {
|
|
||||||
'type': 'num',
|
|
||||||
'divider': '1',
|
|
||||||
'dp': 0,
|
|
||||||
'compare_method': 'pct',
|
|
||||||
'sequence': 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
_order = 'sequence, id'
|
_order = 'sequence, id'
|
||||||
|
|
||||||
def _check_name(self, cr, uid, ids, context=None):
|
@api.one
|
||||||
for record_name in self.read(cr, uid, ids, ['name']):
|
@api.constrains('name')
|
||||||
if not _is_valid_python_var(record_name['name']):
|
def _check_name(self):
|
||||||
return False
|
return _is_valid_python_var(self.name)
|
||||||
return True
|
|
||||||
|
|
||||||
_constraints = [
|
@api.onchange('name')
|
||||||
(_check_name, 'The name must be a valid python identifier', ['name']),
|
def _onchange_name(self):
|
||||||
]
|
if self.name and not _is_valid_python_var(self.name):
|
||||||
|
return {
|
||||||
|
'warning': {
|
||||||
|
'title': 'Invalid name %s' % self.name,
|
||||||
|
'message': 'The name must be a valid python identifier'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def onchange_name(self, cr, uid, ids, name, context=None):
|
@api.onchange('description')
|
||||||
res = {}
|
def _onchange_description(self):
|
||||||
if name and not _is_valid_python_var(name):
|
|
||||||
res['warning'] = {
|
|
||||||
'title': 'Invalid name %s' % name,
|
|
||||||
'message': 'The name must be a valid python identifier'}
|
|
||||||
return res
|
|
||||||
|
|
||||||
def onchange_description(self, cr, uid, ids, description, name,
|
|
||||||
context=None):
|
|
||||||
""" construct name from description """
|
""" construct name from description """
|
||||||
res = {}
|
if self.description and not self.name:
|
||||||
if description and not name:
|
self.name = _python_var(self.description)
|
||||||
res = {'value': {'name': _python_var(description)}}
|
|
||||||
return res
|
|
||||||
|
|
||||||
def onchange_type(self, cr, uid, ids, kpi_type, context=None):
|
@api.onchange('type')
|
||||||
res = {}
|
def _onchange_type(self):
|
||||||
if kpi_type == 'pct':
|
# TODO: change compare_method, divider and dp for all 3 types
|
||||||
res['value'] = {'compare_method': 'diff'}
|
if self.type == 'pct':
|
||||||
elif kpi_type == 'str':
|
self.compare_method = 'diff'
|
||||||
res['value'] = {'compare_method': 'none',
|
elif self.type == 'str':
|
||||||
'divider': '',
|
self.compare_method = 'none'
|
||||||
'dp': 0}
|
self.divider = ''
|
||||||
return res
|
self.dp = 0
|
||||||
|
|
||||||
def _render(self, cr, uid, lang_id, kpi, value, context=None):
|
def render(self, lang_id, value):
|
||||||
""" render a KPI value as a unicode string, ready for display """
|
""" render a KPI value as a unicode string, ready for display """
|
||||||
|
assert len(self) == 1
|
||||||
if value is None:
|
if value is None:
|
||||||
return '#N/A'
|
return '#N/A'
|
||||||
elif kpi.type == 'num':
|
elif self.type == 'num':
|
||||||
return self._render_num(cr, uid, lang_id, value, kpi.divider,
|
return self._render_num(lang_id, value, self.divider,
|
||||||
kpi.dp, kpi.suffix, context=context)
|
self.dp, self.suffix)
|
||||||
elif kpi.type == 'pct':
|
elif self.type == 'pct':
|
||||||
return self._render_num(cr, uid, lang_id, value, 0.01,
|
return self._render_num(lang_id, value, 0.01,
|
||||||
kpi.dp, '%', context=context)
|
self.dp, '%')
|
||||||
else:
|
else:
|
||||||
return unicode(value)
|
return unicode(value)
|
||||||
|
|
||||||
def _render_comparison(self, cr, uid, lang_id, kpi, value, base_value,
|
def render_comparison(self, lang_id, value, base_value,
|
||||||
average_value, average_base_value, context=None):
|
average_value, average_base_value):
|
||||||
""" render the comparison of two KPI values, ready for display """
|
""" render the comparison of two KPI values, ready for display """
|
||||||
|
assert len(self) == 1
|
||||||
if value is None or base_value is None:
|
if value is None or base_value is None:
|
||||||
return ''
|
return ''
|
||||||
if kpi.type == 'pct':
|
if self.type == 'pct':
|
||||||
return self._render_num(
|
return self._render_num(
|
||||||
cr, uid, lang_id,
|
lang_id,
|
||||||
value - base_value,
|
value - base_value,
|
||||||
0.01, kpi.dp, _('pp'), sign='+',
|
0.01, self.dp, _('pp'), sign='+')
|
||||||
context=context)
|
elif self.type == 'num':
|
||||||
elif kpi.type == 'num':
|
|
||||||
if average_value:
|
if average_value:
|
||||||
value = value / float(average_value)
|
value = value / float(average_value)
|
||||||
if average_base_value:
|
if average_base_value:
|
||||||
base_value = base_value / float(average_base_value)
|
base_value = base_value / float(average_base_value)
|
||||||
if kpi.compare_method == 'diff':
|
if self.compare_method == 'diff':
|
||||||
return self._render_num(
|
return self._render_num(
|
||||||
cr, uid, lang_id,
|
lang_id,
|
||||||
value - base_value,
|
value - base_value,
|
||||||
kpi.divider, kpi.dp, kpi.suffix, sign='+',
|
self.divider, self.dp, self.suffix, sign='+')
|
||||||
context=context)
|
elif self.compare_method == 'pct':
|
||||||
elif kpi.compare_method == 'pct':
|
if round(base_value, self.dp) != 0:
|
||||||
if round(base_value, kpi.dp) != 0:
|
|
||||||
return self._render_num(
|
return self._render_num(
|
||||||
cr, uid, lang_id,
|
lang_id,
|
||||||
(value - base_value) / abs(base_value),
|
(value - base_value) / abs(base_value),
|
||||||
0.01, kpi.dp, '%', sign='+',
|
0.01, self.dp, '%', sign='+')
|
||||||
context=context)
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _render_num(self, cr, uid, lang_id, value, divider,
|
def _render_num(self, lang_id, value, divider,
|
||||||
dp, suffix, sign='-', context=None):
|
dp, suffix, sign='-'):
|
||||||
divider_label = _get_selection_label(
|
divider_label = _get_selection_label(
|
||||||
self._columns['divider'].selection, divider)
|
self._columns['divider'].selection, divider)
|
||||||
if divider_label == '1':
|
if divider_label == '1':
|
||||||
divider_label = ''
|
divider_label = ''
|
||||||
# format number following user language
|
# format number following user language
|
||||||
value = round(value / float(divider or 1), dp) or 0
|
value = round(value / float(divider or 1), dp) or 0
|
||||||
value = self.pool['res.lang'].format(
|
value = self.env.registry['res.lang'].format(
|
||||||
cr, uid, lang_id,
|
self.env.cr, self.env.uid, [lang_id],
|
||||||
'%%%s.%df' % (sign, dp),
|
'%%%s.%df' % (sign, dp),
|
||||||
value,
|
value,
|
||||||
grouping=True,
|
grouping=True,
|
||||||
context=context)
|
context=self.env.context)
|
||||||
value = u'%s\N{NO-BREAK SPACE}%s%s' % \
|
value = u'%s\N{NO-BREAK SPACE}%s%s' % \
|
||||||
(value, divider_label, suffix or '')
|
(value, divider_label, suffix or '')
|
||||||
value = value.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
value = value.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class mis_report_query(orm.Model):
|
class MisReportQuery(models.Model):
|
||||||
""" A query to fetch arbitrary data for a MIS report.
|
""" A query to fetch arbitrary data for a MIS report.
|
||||||
|
|
||||||
A query works on a model and has a domain and list of fields to fetch.
|
A query works on a model and has a domain and list of fields to fetch.
|
||||||
|
@ -237,66 +219,42 @@ class mis_report_query(orm.Model):
|
||||||
|
|
||||||
_name = 'mis.report.query'
|
_name = 'mis.report.query'
|
||||||
|
|
||||||
def _get_field_names(self, cr, uid, ids, name, args, context=None):
|
@api.one
|
||||||
res = {}
|
@api.depends('field_ids')
|
||||||
for query in self.browse(cr, uid, ids, context=context):
|
def _compute_field_names(self):
|
||||||
field_names = []
|
field_names = [field.name for field in self.field_ids]
|
||||||
for field in query.field_ids:
|
self.field_names = ', '.join(field_names)
|
||||||
field_names.append(field.name)
|
|
||||||
res[query.id] = ', '.join(field_names)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def onchange_field_ids(self, cr, uid, ids, field_ids, context=None):
|
name = fields.Char(size=32, required=True,
|
||||||
# compute field_names
|
string='Name')
|
||||||
field_names = []
|
model_id = fields.Many2one('ir.model', required=True,
|
||||||
for field in self.pool.get('ir.model.fields').read(
|
string='Model')
|
||||||
cr, uid,
|
field_ids = fields.Many2many('ir.model.fields', required=True,
|
||||||
field_ids[0][2],
|
string='Fields to fetch')
|
||||||
['name'],
|
field_names = fields.Char(compute='_compute_field_names',
|
||||||
context=context):
|
string='Fetched fields name')
|
||||||
field_names.append(field['name'])
|
aggregate = fields.Selection([('sum', _('Sum')),
|
||||||
return {'value': {'field_names': ', '.join(field_names)}}
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char(size=32, required=True,
|
|
||||||
string='Name'),
|
|
||||||
'model_id': fields.many2one('ir.model', required=True,
|
|
||||||
string='Model'),
|
|
||||||
'field_ids': fields.many2many('ir.model.fields', required=True,
|
|
||||||
string='Fields to fetch'),
|
|
||||||
'field_names': fields.function(_get_field_names, type='char',
|
|
||||||
string='Fetched fields name',
|
|
||||||
store={'mis.report.query':
|
|
||||||
(lambda self, cr, uid, ids, c={}:
|
|
||||||
ids, ['field_ids'], 20), }),
|
|
||||||
'aggregate': fields.selection([('sum', _('Sum')),
|
|
||||||
('avg', _('Average')),
|
('avg', _('Average')),
|
||||||
('min', _('Min')),
|
('min', _('Min')),
|
||||||
('max', _('Max'))],
|
('max', _('Max'))],
|
||||||
string='Aggregate'),
|
string='Aggregate')
|
||||||
'date_field': fields.many2one('ir.model.fields', required=True,
|
date_field = fields.Many2one('ir.model.fields', required=True,
|
||||||
string='Date field',
|
string='Date field',
|
||||||
domain=[('ttype', 'in',
|
domain=[('ttype', 'in',
|
||||||
('date', 'datetime'))]),
|
('date', 'datetime'))])
|
||||||
'domain': fields.char(string='Domain'),
|
domain = fields.Char(string='Domain')
|
||||||
'report_id': fields.many2one('mis.report', string='Report',
|
report_id = fields.Many2one('mis.report', string='Report',
|
||||||
ondelete='cascade'),
|
ondelete='cascade')
|
||||||
}
|
|
||||||
|
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
|
|
||||||
def _check_name(self, cr, uid, ids, context=None):
|
@api.one
|
||||||
for record_name in self.read(cr, uid, ids, ['name']):
|
@api.constrains('name')
|
||||||
if not _is_valid_python_var(record_name['name']):
|
def _check_name(self):
|
||||||
return False
|
return _is_valid_python_var(self.name)
|
||||||
return True
|
|
||||||
|
|
||||||
_constraints = [
|
|
||||||
(_check_name, 'The name must be a valid python identifier', ['name']),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class mis_report(orm.Model):
|
class MisReport(models.Model):
|
||||||
""" A MIS report template (without period information)
|
""" A MIS report template (without period information)
|
||||||
|
|
||||||
The MIS report holds:
|
The MIS report holds:
|
||||||
|
@ -312,21 +270,19 @@ class mis_report(orm.Model):
|
||||||
|
|
||||||
_name = 'mis.report'
|
_name = 'mis.report'
|
||||||
|
|
||||||
_columns = {
|
name = fields.Char(required=True,
|
||||||
'name': fields.char(required=True,
|
string='Name', translate=True)
|
||||||
string='Name', translate=True),
|
description = fields.Char(required=False,
|
||||||
'description': fields.char(required=False,
|
string='Description', translate=True)
|
||||||
string='Description', translate=True),
|
query_ids = fields.One2many('mis.report.query', 'report_id',
|
||||||
'query_ids': fields.one2many('mis.report.query', 'report_id',
|
string='Queries')
|
||||||
string='Queries'),
|
kpi_ids = fields.One2many('mis.report.kpi', 'report_id',
|
||||||
'kpi_ids': fields.one2many('mis.report.kpi', 'report_id',
|
string='KPI\'s')
|
||||||
string='KPI\'s'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: kpi name cannot be start with query name
|
# TODO: kpi name cannot be start with query name
|
||||||
|
|
||||||
|
|
||||||
class mis_report_instance_period(orm.Model):
|
class MisReportInstancePeriod(models.Model):
|
||||||
""" A MIS report instance has the logic to compute
|
""" A MIS report instance has the logic to compute
|
||||||
a report template for a given date period.
|
a report template for a given date period.
|
||||||
|
|
||||||
|
@ -334,122 +290,93 @@ class mis_report_instance_period(orm.Model):
|
||||||
are defined as an offset relative to a pivot date.
|
are defined as an offset relative to a pivot date.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _get_dates(self, cr, uid, ids, field_names, arg, context=None):
|
@api.one
|
||||||
if isinstance(ids, (int, long)):
|
@api.depends('report_instance_id.pivot_date', 'type', 'offset', 'duration')
|
||||||
ids = [ids]
|
def _compute_dates(self):
|
||||||
res = {}
|
self.date_from = False
|
||||||
for c in self.browse(cr, uid, ids, context=context):
|
self.date_to = False
|
||||||
date_from = False
|
self.period_from = False
|
||||||
date_to = False
|
self.period_to = False
|
||||||
period_ids = None
|
self.valid = False
|
||||||
valid = False
|
d = fields.Date.from_string(self.report_instance_id.pivot_date)
|
||||||
d = parser.parse(c.report_instance_id.pivot_date)
|
if self.type == 'd':
|
||||||
if c.type == 'd':
|
date_from = d + timedelta(days=self.offset)
|
||||||
date_from = d + timedelta(days=c.offset)
|
date_to = date_from + timedelta(days=self.duration - 1)
|
||||||
date_to = date_from + timedelta(days=c.duration - 1)
|
self.date_from = fields.Date.to_string(date_from)
|
||||||
date_from = date_from.strftime(
|
self.date_to = fields.Date.to_string(date_to)
|
||||||
tools.DEFAULT_SERVER_DATE_FORMAT)
|
self.valid = True
|
||||||
date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
elif self.type == 'w':
|
||||||
valid = True
|
|
||||||
elif c.type == 'w':
|
|
||||||
date_from = d - timedelta(d.weekday())
|
date_from = d - timedelta(d.weekday())
|
||||||
date_from = date_from + timedelta(days=c.offset * 7)
|
date_from = date_from + timedelta(days=self.offset * 7)
|
||||||
date_to = date_from + timedelta(days=(7 * c.duration) - 1)
|
date_to = date_from + timedelta(days=(7 * self.duration) - 1)
|
||||||
date_from = date_from.strftime(
|
self.date_from = fields.Date.to_string(date_from)
|
||||||
tools.DEFAULT_SERVER_DATE_FORMAT)
|
self.date_to = fields.Date.to_string(date_to)
|
||||||
date_to = date_to.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
self.valid = True
|
||||||
valid = True
|
elif self.type == 'fp':
|
||||||
elif c.type == 'fp':
|
current_periods = self.env['account.period'].search(
|
||||||
period_obj = self.pool['account.period']
|
|
||||||
current_period_ids = period_obj.search(
|
|
||||||
cr, uid,
|
|
||||||
[('special', '=', False),
|
[('special', '=', False),
|
||||||
('date_start', '<=', d),
|
('date_start', '<=', d),
|
||||||
('date_stop', '>=', d),
|
('date_stop', '>=', d),
|
||||||
('company_id', '=', c.company_id.id)],
|
('company_id', '=',
|
||||||
context=context)
|
self.report_instance_id.company_id.id)])
|
||||||
if current_period_ids:
|
if current_periods:
|
||||||
all_period_ids = period_obj.search(
|
all_periods = self.env['account.period'].search(
|
||||||
cr, uid,
|
|
||||||
[('special', '=', False),
|
[('special', '=', False),
|
||||||
('company_id', '=', c.company_id.id)],
|
('company_id', '=',
|
||||||
order='date_start',
|
self.report_instance_id.company_id.id)],
|
||||||
context=context)
|
order='date_start')
|
||||||
p = all_period_ids.index(current_period_ids[0]) + c.offset
|
all_period_ids = [p.id for p in all_periods]
|
||||||
if p >= 0 and p + c.duration <= len(all_period_ids):
|
p = all_period_ids.index(current_periods[0].id) + self.offset
|
||||||
period_ids = all_period_ids[p:p + c.duration]
|
if p >= 0 and p + self.duration <= len(all_period_ids):
|
||||||
periods = period_obj.browse(cr, uid, period_ids,
|
periods = all_periods[p:p + self.duration]
|
||||||
context=context)
|
self.date_from = periods[0].date_start
|
||||||
date_from = periods[0].date_start
|
self.date_to = periods[-1].date_stop
|
||||||
date_to = periods[-1].date_stop
|
self.period_from = periods[0]
|
||||||
valid = True
|
self.period_to = periods[-1]
|
||||||
|
self.valid = True
|
||||||
res[c.id] = {
|
|
||||||
'date_from': date_from,
|
|
||||||
'date_to': date_to,
|
|
||||||
'period_from': period_ids and period_ids[0] or False,
|
|
||||||
'period_to': period_ids and period_ids[-1] or False,
|
|
||||||
'valid': valid,
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
|
|
||||||
_name = 'mis.report.instance.period'
|
_name = 'mis.report.instance.period'
|
||||||
|
|
||||||
_columns = {
|
name = fields.Char(size=32, required=True,
|
||||||
'name': fields.char(size=32, required=True,
|
string='Description', translate=True)
|
||||||
string='Description', translate=True),
|
type = fields.Selection([('d', _('Day')),
|
||||||
'type': fields.selection([('d', _('Day')),
|
|
||||||
('w', _('Week')),
|
('w', _('Week')),
|
||||||
('fp', _('Fiscal Period')),
|
('fp', _('Fiscal Period')),
|
||||||
# ('fy', _('Fiscal Year'))
|
# ('fy', _('Fiscal Year'))
|
||||||
],
|
],
|
||||||
required=True,
|
required=True,
|
||||||
string='Period type'),
|
string='Period type')
|
||||||
'offset': fields.integer(string='Offset',
|
offset = fields.Integer(string='Offset',
|
||||||
help='Offset from current period'),
|
help='Offset from current period',
|
||||||
'duration': fields.integer(string='Duration',
|
default=-1)
|
||||||
help='Number of periods'),
|
duration = fields.Integer(string='Duration',
|
||||||
'date_from': fields.function(_get_dates,
|
help='Number of periods',
|
||||||
type='date',
|
default=1)
|
||||||
multi="dates",
|
date_from = fields.Date(compute='_compute_dates', string="From")
|
||||||
string="From"),
|
date_to = fields.Date(compute='_compute_dates', string="To")
|
||||||
'date_to': fields.function(_get_dates,
|
period_from = fields.Many2one(compute='_compute_dates',
|
||||||
type='date',
|
comodel_name='account.period',
|
||||||
multi="dates",
|
string="From period")
|
||||||
string="To"),
|
period_to = fields.Many2one(compute='_compute_dates',
|
||||||
'period_from': fields.function(_get_dates,
|
comodel_name='account.period',
|
||||||
type='many2one', obj='account.period',
|
string="To period")
|
||||||
multi="dates", string="From period"),
|
valid = fields.Boolean(compute='_compute_dates',
|
||||||
'period_to': fields.function(_get_dates,
|
|
||||||
type='many2one', obj='account.period',
|
|
||||||
multi="dates", string="To period"),
|
|
||||||
'valid': fields.function(_get_dates,
|
|
||||||
type='boolean',
|
type='boolean',
|
||||||
multi='dates', string='Valid'),
|
string='Valid')
|
||||||
'sequence': fields.integer(string='Sequence'),
|
sequence = fields.Integer(string='Sequence', default=100)
|
||||||
'report_instance_id': fields.many2one('mis.report.instance',
|
report_instance_id = fields.Many2one('mis.report.instance',
|
||||||
string='Report Instance',
|
string='Report Instance',
|
||||||
ondelete='cascade'),
|
ondelete='cascade')
|
||||||
'comparison_column_ids': fields.many2many(
|
comparison_column_ids = fields.Many2many(
|
||||||
'mis.report.instance.period',
|
comodel_name='mis.report.instance.period',
|
||||||
'mis_report_instance_period_rel',
|
relation='mis_report_instance_period_rel',
|
||||||
'period_id',
|
column1='period_id',
|
||||||
'compare_period_id',
|
column2='compare_period_id',
|
||||||
string='Compare with'),
|
string='Compare with')
|
||||||
'company_id': fields.related('report_instance_id', 'company_id',
|
normalize_factor = fields.Integer(
|
||||||
type="many2one", relation="res.company",
|
|
||||||
string="Company", readonly=True),
|
|
||||||
'normalize_factor': fields.integer(
|
|
||||||
string='Factor',
|
string='Factor',
|
||||||
help='Factor to use to normalize the period (used in comparison'),
|
help='Factor to use to normalize the period (used in comparison',
|
||||||
}
|
default=1)
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'offset': -1,
|
|
||||||
'duration': 1,
|
|
||||||
'sequence': 100,
|
|
||||||
'normalize_factor': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
_order = 'sequence, id'
|
_order = 'sequence, id'
|
||||||
|
|
||||||
|
@ -462,21 +389,20 @@ class mis_report_instance_period(orm.Model):
|
||||||
'Period name should be unique by report'),
|
'Period name should be unique by report'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def drilldown(self, cr, uid, _id, expr, context=None):
|
@api.multi
|
||||||
if context is None:
|
def drilldown(self, expr):
|
||||||
context = {}
|
assert len(self) == 1
|
||||||
this = self.browse(cr, uid, _id, context=context)[0]
|
|
||||||
if AEP.has_account_var(expr):
|
if AEP.has_account_var(expr):
|
||||||
env = Environment(cr, uid, context)
|
aep = AEP(self.env)
|
||||||
aep = AEP(env)
|
|
||||||
aep.parse_expr(expr)
|
aep.parse_expr(expr)
|
||||||
aep.done_parsing(this.report_instance_id.root_account)
|
aep.done_parsing(self.report_instance_id.root_account)
|
||||||
domain = aep.get_aml_domain_for_expr(
|
domain = aep.get_aml_domain_for_expr(
|
||||||
expr, this.date_from, this.date_to,
|
expr,
|
||||||
this.period_from, this.period_to,
|
self.date_from, self.date_to,
|
||||||
this.report_instance_id.target_move)
|
self.period_from, self.period_to,
|
||||||
|
self.report_instance_id.target_move)
|
||||||
return {
|
return {
|
||||||
'name': expr + ' - ' + this.name,
|
'name': expr + ' - ' + self.name,
|
||||||
'domain': domain,
|
'domain': domain,
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'res_model': 'account.move.line',
|
'res_model': 'account.move.line',
|
||||||
|
@ -488,44 +414,42 @@ class mis_report_instance_period(orm.Model):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _fetch_queries(self, cr, uid, c, context):
|
def _fetch_queries(self):
|
||||||
|
assert len(self) == 1
|
||||||
res = {}
|
res = {}
|
||||||
report = c.report_instance_id.report_id
|
for query in self.report_instance_id.report_id.query_ids:
|
||||||
for query in report.query_ids:
|
model = self.env[query.model_id.model]
|
||||||
obj = self.pool[query.model_id.model]
|
|
||||||
domain = query.domain and safe_eval(query.domain) or []
|
domain = query.domain and safe_eval(query.domain) or []
|
||||||
if query.date_field.ttype == 'date':
|
if query.date_field.ttype == 'date':
|
||||||
domain.extend([(query.date_field.name, '>=', c.date_from),
|
domain.extend([(query.date_field.name, '>=', self.date_from),
|
||||||
(query.date_field.name, '<=', c.date_to)])
|
(query.date_field.name, '<=', self.date_to)])
|
||||||
else:
|
else:
|
||||||
datetime_from = _utc_midnight(
|
datetime_from = _utc_midnight(
|
||||||
c.date_from, context.get('tz', 'UTC'))
|
self.date_from, self._context.get('tz', 'UTC'))
|
||||||
datetime_to = _utc_midnight(
|
datetime_to = _utc_midnight(
|
||||||
c.date_to, context.get('tz', 'UTC'), add_day=1)
|
self.date_to, self._context.get('tz', 'UTC'), add_day=1)
|
||||||
domain.extend([(query.date_field.name, '>=', datetime_from),
|
domain.extend([(query.date_field.name, '>=', datetime_from),
|
||||||
(query.date_field.name, '<', datetime_to)])
|
(query.date_field.name, '<', datetime_to)])
|
||||||
if obj._columns.get('company_id'):
|
# TODO: we probably don't need company_id here
|
||||||
|
if model._columns.get('company_id'):
|
||||||
domain.extend(['|', ('company_id', '=', False),
|
domain.extend(['|', ('company_id', '=', False),
|
||||||
('company_id', '=', c.company_id.id)])
|
('company_id', '=',
|
||||||
|
self.report_instance_id.company_id.id)])
|
||||||
field_names = [f.name for f in query.field_ids]
|
field_names = [f.name for f in query.field_ids]
|
||||||
if not query.aggregate:
|
if not query.aggregate:
|
||||||
obj_ids = obj.search(cr, uid, domain, context=context)
|
data = model.search_read(domain, field_names)
|
||||||
obj_datas = obj.read(
|
res[query.name] = [AutoStruct(**d) for d in data]
|
||||||
cr, uid, obj_ids, field_names, context=context)
|
|
||||||
res[query.name] = [AutoStruct(**d) for d in obj_datas]
|
|
||||||
elif query.aggregate == 'sum':
|
elif query.aggregate == 'sum':
|
||||||
obj_datas = obj.read_group(
|
data = model.read_group(
|
||||||
cr, uid, domain, field_names, [], context=context)
|
domain, field_names, [])
|
||||||
s = AutoStruct(count=obj_datas[0]['__count'])
|
s = AutoStruct(count=data[0]['__count'])
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
v = obj_datas[0][field_name]
|
v = data[0][field_name]
|
||||||
setattr(s, field_name, v)
|
setattr(s, field_name, v)
|
||||||
res[query.name] = s
|
res[query.name] = s
|
||||||
else:
|
else:
|
||||||
obj_ids = obj.search(cr, uid, domain, context=context)
|
data = model.search_read(domain, field_names)
|
||||||
obj_datas = obj.read(
|
s = AutoStruct(count=len(data))
|
||||||
cr, uid, obj_ids, field_names, context=context)
|
|
||||||
s = AutoStruct(count=len(obj_datas))
|
|
||||||
if query.aggregate == 'min':
|
if query.aggregate == 'min':
|
||||||
agg = min
|
agg = min
|
||||||
elif query.aggregate == 'max':
|
elif query.aggregate == 'max':
|
||||||
|
@ -534,16 +458,11 @@ class mis_report_instance_period(orm.Model):
|
||||||
agg = lambda l: sum(l) / float(len(l))
|
agg = lambda l: sum(l) / float(len(l))
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
setattr(s, field_name,
|
setattr(s, field_name,
|
||||||
agg([d[field_name] for d in obj_datas]))
|
agg([d[field_name] for d in data]))
|
||||||
res[query.name] = s
|
res[query.name] = s
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _compute(self, cr, uid, lang_id, c, aep, context=None):
|
def _compute(self, lang_id, aep):
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
|
|
||||||
kpi_obj = self.pool['mis.report.kpi']
|
|
||||||
|
|
||||||
res = {}
|
res = {}
|
||||||
|
|
||||||
localdict = {
|
localdict = {
|
||||||
|
@ -554,14 +473,14 @@ class mis_report_instance_period(orm.Model):
|
||||||
'len': len,
|
'len': len,
|
||||||
'avg': lambda l: sum(l) / float(len(l)),
|
'avg': lambda l: sum(l) / float(len(l)),
|
||||||
}
|
}
|
||||||
localdict.update(self._fetch_queries(cr, uid, c,
|
|
||||||
context=context))
|
|
||||||
|
|
||||||
aep.do_queries(c.date_from, c.date_to,
|
localdict.update(self._fetch_queries())
|
||||||
c.period_from, c.period_to,
|
|
||||||
c.report_instance_id.target_move)
|
|
||||||
|
|
||||||
compute_queue = c.report_instance_id.report_id.kpi_ids
|
aep.do_queries(self.date_from, self.date_to,
|
||||||
|
self.period_from, self.period_to,
|
||||||
|
self.report_instance_id.target_move)
|
||||||
|
|
||||||
|
compute_queue = self.report_instance_id.report_id.kpi_ids
|
||||||
recompute_queue = []
|
recompute_queue = []
|
||||||
while True:
|
while True:
|
||||||
for kpi in compute_queue:
|
for kpi in compute_queue:
|
||||||
|
@ -583,8 +502,7 @@ class mis_report_instance_period(orm.Model):
|
||||||
kpi_val_rendered = '#ERR'
|
kpi_val_rendered = '#ERR'
|
||||||
kpi_val_comment += '\n\n%s' % (traceback.format_exc(),)
|
kpi_val_comment += '\n\n%s' % (traceback.format_exc(),)
|
||||||
else:
|
else:
|
||||||
kpi_val_rendered = kpi_obj._render(
|
kpi_val_rendered = kpi.render(lang_id, kpi_val)
|
||||||
cr, uid, lang_id, kpi, kpi_val, context=context)
|
|
||||||
|
|
||||||
localdict[kpi.name] = kpi_val
|
localdict[kpi.name] = kpi_val
|
||||||
try:
|
try:
|
||||||
|
@ -592,6 +510,7 @@ class mis_report_instance_period(orm.Model):
|
||||||
if kpi.css_style:
|
if kpi.css_style:
|
||||||
kpi_style = safe_eval(kpi.css_style, localdict)
|
kpi_style = safe_eval(kpi.css_style, localdict)
|
||||||
except:
|
except:
|
||||||
|
# TODO: log warning
|
||||||
kpi_style = None
|
kpi_style = None
|
||||||
|
|
||||||
drilldown = (kpi_val is not None and
|
drilldown = (kpi_val is not None and
|
||||||
|
@ -605,7 +524,7 @@ class mis_report_instance_period(orm.Model):
|
||||||
'suffix': kpi.suffix,
|
'suffix': kpi.suffix,
|
||||||
'dp': kpi.dp,
|
'dp': kpi.dp,
|
||||||
'is_percentage': kpi.type == 'pct',
|
'is_percentage': kpi.type == 'pct',
|
||||||
'period_id': c.id,
|
'period_id': self.id,
|
||||||
'expr': kpi.expression,
|
'expr': kpi.expression,
|
||||||
'drilldown': drilldown,
|
'drilldown': drilldown,
|
||||||
}
|
}
|
||||||
|
@ -625,127 +544,101 @@ class mis_report_instance_period(orm.Model):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class mis_report_instance(orm.Model):
|
class MisReportInstance(models.Model):
|
||||||
"""The MIS report instance combines everything to compute
|
"""The MIS report instance combines everything to compute
|
||||||
a MIS report template for a set of periods."""
|
a MIS report template for a set of periods."""
|
||||||
|
|
||||||
def _get_pivot_date(self, cr, uid, ids, field_name, arg, context=None):
|
@api.one
|
||||||
res = {}
|
@api.depends('date')
|
||||||
for r in self.browse(cr, uid, ids, context=context):
|
def _compute_pivot_date(self):
|
||||||
if r.date:
|
if self.date:
|
||||||
res[r.id] = r.date
|
self.pivot_date = self.date
|
||||||
else:
|
else:
|
||||||
res[r.id] = fields.date.context_today(self, cr, uid,
|
self.pivot_date = fields.Date.context_today(self)
|
||||||
context=context)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_root_account(self, cr, uid, ids, field_name, arg, context=None):
|
@api.one
|
||||||
res = {}
|
@api.depends('company_id')
|
||||||
account_obj = self.pool['account.account']
|
def _compute_root_account(self):
|
||||||
for r in self.browse(cr, uid, ids, context=context):
|
self.root_account = False
|
||||||
account_ids = account_obj.search(
|
accounts = self.env['account.account'].search(
|
||||||
cr, uid,
|
|
||||||
[('parent_id', '=', False),
|
[('parent_id', '=', False),
|
||||||
('company_id', '=', r.company_id.id)],
|
('company_id', '=', self.company_id.id)])
|
||||||
context=context)
|
if len(accounts) == 1:
|
||||||
if len(account_ids) == 1:
|
self.root_account = accounts[0]
|
||||||
res[r.id] = account_ids[0]
|
|
||||||
return res
|
|
||||||
|
|
||||||
_name = 'mis.report.instance'
|
_name = 'mis.report.instance'
|
||||||
|
|
||||||
_columns = {
|
name = fields.Char(required=True,
|
||||||
'name': fields.char(required=True,
|
string='Name', translate=True)
|
||||||
string='Name', translate=True),
|
description = fields.Char(required=False,
|
||||||
'description': fields.char(required=False,
|
string='Description', translate=True)
|
||||||
string='Description', translate=True),
|
date = fields.Date(string='Base date',
|
||||||
'date': fields.date(string='Base date',
|
|
||||||
help='Report base date '
|
help='Report base date '
|
||||||
'(leave empty to use current date)'),
|
'(leave empty to use current date)')
|
||||||
'pivot_date': fields.function(_get_pivot_date,
|
pivot_date = fields.Date(compute='_compute_pivot_date',
|
||||||
type='date',
|
string="Pivot date")
|
||||||
string="Pivot date"),
|
report_id = fields.Many2one('mis.report',
|
||||||
'report_id': fields.many2one('mis.report',
|
|
||||||
required=True,
|
required=True,
|
||||||
string='Report'),
|
string='Report')
|
||||||
'period_ids': fields.one2many('mis.report.instance.period',
|
period_ids = fields.One2many('mis.report.instance.period',
|
||||||
'report_instance_id',
|
'report_instance_id',
|
||||||
required=True,
|
required=True,
|
||||||
string='Periods'),
|
string='Periods')
|
||||||
'target_move': fields.selection([('posted', 'All Posted Entries'),
|
target_move = fields.Selection([('posted', 'All Posted Entries'),
|
||||||
('all', 'All Entries'),
|
('all', 'All Entries')],
|
||||||
], 'Target Moves', required=True),
|
string='Target Moves', required=True,
|
||||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
default='posted')
|
||||||
'root_account': fields.function(_get_root_account,
|
company_id = fields.Many2one('res.company', 'Company', required=True,
|
||||||
type='many2one', obj='account.account',
|
default=lambda self: self.env['res.company'].
|
||||||
string="Account chart"),
|
_company_default_get('mis.report.instance'))
|
||||||
'landscape_pdf': fields.boolean(string='Landscape PDF'),
|
root_account = fields.Many2one(compute='_compute_root_account',
|
||||||
}
|
comodel_name='account.account',
|
||||||
|
string="Account chart")
|
||||||
|
landscape_pdf = fields.Boolean(string='Landscape PDF')
|
||||||
|
|
||||||
_defaults = {
|
def _format_date(self, lang_id, date):
|
||||||
'target_move': 'posted',
|
|
||||||
'company_id': lambda s, cr, uid, c:
|
|
||||||
s.pool.get('res.company')._company_default_get(
|
|
||||||
cr, uid,
|
|
||||||
'mis.report.instance',
|
|
||||||
context=c)
|
|
||||||
}
|
|
||||||
|
|
||||||
def _format_date(self, cr, uid, lang_id, date, context=None):
|
|
||||||
# format date following user language
|
# format date following user language
|
||||||
tformat = self.pool['res.lang'].read(
|
date_format = self.env['res.lang'].browse(lang_id).date_format
|
||||||
cr, uid, lang_id, ['date_format'])[0]['date_format']
|
return datetime.strftime(fields.Date.from_string(date), date_format)
|
||||||
return datetime.strftime(datetime.strptime(
|
|
||||||
date,
|
|
||||||
tools.DEFAULT_SERVER_DATE_FORMAT),
|
|
||||||
tformat)
|
|
||||||
|
|
||||||
def preview(self, cr, uid, _id, context=None):
|
@api.multi
|
||||||
view_id = self.pool['ir.model.data'].get_object_reference(
|
def preview(self):
|
||||||
cr, uid, 'mis_builder', 'mis_report_instance_result_view_form')[1]
|
assert len(self) == 1
|
||||||
|
view_id = self.env.ref('mis_builder.'
|
||||||
|
'mis_report_instance_result_view_form')
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'res_model': 'mis.report.instance',
|
'res_model': 'mis.report.instance',
|
||||||
'res_id': _id[0],
|
'res_id': self.id,
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'view_type': 'form',
|
'view_type': 'form',
|
||||||
'view_id': view_id,
|
'view_id': view_id.id,
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.cr_uid_id_context
|
@api.multi
|
||||||
def compute(self, cr, uid, _id, context=None):
|
def compute(self):
|
||||||
assert isinstance(_id, (int, long))
|
assert len(self) == 1
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
|
|
||||||
this = self.browse(cr, uid, _id, context=context)
|
|
||||||
|
|
||||||
kpi_obj = self.pool['mis.report.kpi']
|
|
||||||
report_instance_period_obj = self.pool['mis.report.instance.period']
|
|
||||||
|
|
||||||
# prepare AccountingExpressionProcessor
|
# prepare AccountingExpressionProcessor
|
||||||
env = Environment(cr, uid, context)
|
aep = AEP(self.env)
|
||||||
aep = AEP(env)
|
for kpi in self.report_id.kpi_ids:
|
||||||
for kpi in this.report_id.kpi_ids:
|
|
||||||
aep.parse_expr(kpi.expression)
|
aep.parse_expr(kpi.expression)
|
||||||
aep.done_parsing(this.root_account)
|
aep.done_parsing(self.root_account)
|
||||||
|
|
||||||
# fetch user language only once (TODO: is it necessary?)
|
# fetch user language only once
|
||||||
lang = self.pool['res.users'].read(
|
# TODO: is this necessary?
|
||||||
cr, uid, uid, ['lang'], context=context)['lang']
|
lang = self.env.user.lang
|
||||||
if not lang:
|
if not lang:
|
||||||
lang = 'en_US'
|
lang = 'en_US'
|
||||||
lang_id = self.pool['res.lang'].search(
|
lang_id = self.env['res.lang'].search([('code', '=', lang)]).id
|
||||||
cr, uid, [('code', '=', lang)], context=context)
|
|
||||||
|
|
||||||
# compute kpi values for each period
|
# compute kpi values for each period
|
||||||
kpi_values_by_period_ids = {}
|
kpi_values_by_period_ids = {}
|
||||||
for period in this.period_ids:
|
for period in self.period_ids:
|
||||||
if not period.valid:
|
if not period.valid:
|
||||||
continue
|
continue
|
||||||
kpi_values = report_instance_period_obj._compute(
|
kpi_values = period._compute(lang_id, aep)
|
||||||
cr, uid, lang_id, period, aep, context=context)
|
|
||||||
kpi_values_by_period_ids[period.id] = kpi_values
|
kpi_values_by_period_ids[period.id] = kpi_values
|
||||||
|
|
||||||
# prepare header and content
|
# prepare header and content
|
||||||
|
@ -756,7 +649,7 @@ class mis_report_instance(orm.Model):
|
||||||
})
|
})
|
||||||
content = []
|
content = []
|
||||||
rows_by_kpi_name = {}
|
rows_by_kpi_name = {}
|
||||||
for kpi in this.report_id.kpi_ids:
|
for kpi in self.report_id.kpi_ids:
|
||||||
rows_by_kpi_name[kpi.name] = {
|
rows_by_kpi_name[kpi.name] = {
|
||||||
'kpi_name': kpi.description,
|
'kpi_name': kpi.description,
|
||||||
'cols': [],
|
'cols': [],
|
||||||
|
@ -765,20 +658,19 @@ class mis_report_instance(orm.Model):
|
||||||
content.append(rows_by_kpi_name[kpi.name])
|
content.append(rows_by_kpi_name[kpi.name])
|
||||||
|
|
||||||
# populate header and content
|
# populate header and content
|
||||||
for period in this.period_ids:
|
for period in self.period_ids:
|
||||||
if not period.valid:
|
if not period.valid:
|
||||||
continue
|
continue
|
||||||
# add the column header
|
# add the column header
|
||||||
|
# TODO: format period.date_from
|
||||||
header[0]['cols'].append(dict(
|
header[0]['cols'].append(dict(
|
||||||
name=period.name,
|
name=period.name,
|
||||||
date=(period.duration > 1 or period.type == 'w') and
|
date=(period.duration > 1 or period.type == 'w') and
|
||||||
_('from %s to %s' %
|
_('from %s to %s' %
|
||||||
(period.period_from and period.period_from.name
|
(period.period_from and period.period_from.name
|
||||||
or self._format_date(cr, uid, lang_id, period.date_from,
|
or self._format_date(lang_id, period.date_from),
|
||||||
context=context),
|
|
||||||
period.period_to and period.period_to.name
|
period.period_to and period.period_to.name
|
||||||
or self._format_date(cr, uid, lang_id, period.date_to,
|
or self._format_date(lang_id, period.date_to)))
|
||||||
context=context)))
|
|
||||||
or period.period_from and period.period_from.name or
|
or period.period_from and period.period_from.name or
|
||||||
period.date_from))
|
period.date_from))
|
||||||
# add kpi values
|
# add kpi values
|
||||||
|
@ -792,22 +684,20 @@ class mis_report_instance(orm.Model):
|
||||||
kpi_values_by_period_ids.get(compare_col.id)
|
kpi_values_by_period_ids.get(compare_col.id)
|
||||||
if compare_kpi_values:
|
if compare_kpi_values:
|
||||||
# add the comparison column header
|
# add the comparison column header
|
||||||
|
# TODO: make 'vs' translatable
|
||||||
header[0]['cols'].append(
|
header[0]['cols'].append(
|
||||||
dict(name='%s vs %s' % (period.name, compare_col.name),
|
dict(name='%s vs %s' % (period.name, compare_col.name),
|
||||||
date=''))
|
date=''))
|
||||||
# add comparison values
|
# add comparison values
|
||||||
for kpi in this.report_id.kpi_ids:
|
for kpi in self.report_id.kpi_ids:
|
||||||
rows_by_kpi_name[kpi.name]['cols'].append(
|
rows_by_kpi_name[kpi.name]['cols'].append({
|
||||||
{'val_r': kpi_obj._render_comparison(
|
'val_r': kpi.render_comparison(
|
||||||
cr,
|
|
||||||
uid,
|
|
||||||
lang_id,
|
lang_id,
|
||||||
kpi,
|
|
||||||
kpi_values[kpi.name]['val'],
|
kpi_values[kpi.name]['val'],
|
||||||
compare_kpi_values[kpi.name]['val'],
|
compare_kpi_values[kpi.name]['val'],
|
||||||
period.normalize_factor,
|
period.normalize_factor,
|
||||||
compare_col.normalize_factor,
|
compare_col.normalize_factor)
|
||||||
context=context)})
|
})
|
||||||
|
|
||||||
return {'header': header,
|
return {'header': header,
|
||||||
'content': content}
|
'content': content}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ReportMisReportInstance(models.AbstractModel):
|
||||||
docs = self.env['mis.report.instance'].browse(self._ids)
|
docs = self.env['mis.report.instance'].browse(self._ids)
|
||||||
docs_computed = {}
|
docs_computed = {}
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
docs_computed[doc.id] = doc.compute()[0]
|
docs_computed[doc.id] = doc.compute()
|
||||||
docargs = {
|
docargs = {
|
||||||
'doc_ids': self._ids,
|
'doc_ids': self._ids,
|
||||||
'doc_model': 'mis.report.instance',
|
'doc_model': 'mis.report.instance',
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
<tree string="Queries" editable="bottom">
|
<tree string="Queries" editable="bottom">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="model_id"/>
|
<field name="model_id"/>
|
||||||
<field name="field_ids" domain="[('model_id', '=', model_id)]" widget="many2many_tags"
|
<field name="field_ids" domain="[('model_id', '=', model_id)]" widget="many2many_tags"/>
|
||||||
on_change="onchange_field_ids(field_ids, context)"/>
|
|
||||||
<field name="field_names"/>
|
<field name="field_names"/>
|
||||||
<field name="aggregate"/>
|
<field name="aggregate"/>
|
||||||
<field name="date_field" domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"/>
|
<field name="date_field" domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"/>
|
||||||
|
@ -44,10 +43,10 @@
|
||||||
<field name="kpi_ids">
|
<field name="kpi_ids">
|
||||||
<tree string="KPI's" editable="bottom">
|
<tree string="KPI's" editable="bottom">
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="description" on_change="onchange_description(description, name, context)"/>
|
<field name="description"/>
|
||||||
<field name="name" on_change="onchange_name(name, context)"/>
|
<field name="name"/>
|
||||||
<field name="expression"/>
|
<field name="expression"/>
|
||||||
<field name="type" on_change="onchange_type(type, context)"/>
|
<field name="type"/>
|
||||||
<field name="dp" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
<field name="dp" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
||||||
<field name="divider" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
<field name="divider" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
||||||
<field name="suffix"/>
|
<field name="suffix"/>
|
||||||
|
@ -190,7 +189,6 @@
|
||||||
<field name="report_instance_id" invisible="1"/>
|
<field name="report_instance_id" invisible="1"/>
|
||||||
<field name="id" invisible="1"/>
|
<field name="id" invisible="1"/>
|
||||||
<field name="comparison_column_ids" domain="[('report_instance_id', '=', report_instance_id), ('id', '!=', id)]" widget="many2many_tags"/>
|
<field name="comparison_column_ids" domain="[('report_instance_id', '=', report_instance_id), ('id', '!=', id)]" widget="many2many_tags"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
|
|
Loading…
Reference in New Issue