[IMP] mis_builder: number format are now part of styles
Plus a default style at the report level. Plus correct number rendering for comparisons in Excel export.pull/189/head
parent
5fc18628e4
commit
e8aa6dd2c8
|
@ -3,8 +3,8 @@ Changelog
|
||||||
|
|
||||||
.. Future (?)
|
.. Future (?)
|
||||||
.. ~~~~~~~~~~
|
.. ~~~~~~~~~~
|
||||||
..
|
..
|
||||||
.. *
|
.. *
|
||||||
|
|
||||||
9.0.1.0.0 (2016-??-??)
|
9.0.1.0.0 (2016-??-??)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -13,6 +13,9 @@ Part of the work for this release has been done at the Sorrento sprint
|
||||||
April 26-29, 2016. The rest (ie a major refactoring) has been done in
|
April 26-29, 2016. The rest (ie a major refactoring) has been done in
|
||||||
the weeks after.
|
the weeks after.
|
||||||
|
|
||||||
|
* [IMP] There is now a default style at the report level
|
||||||
|
* [CHG] Number display properties (rounding, prefix, suffix, factor) are
|
||||||
|
now defined in styles
|
||||||
* [CHG] Percentage difference are rounded to 1 digit instead of the kpi's
|
* [CHG] Percentage difference are rounded to 1 digit instead of the kpi's
|
||||||
rounding, as the KPI rounding does not make sense in this case
|
rounding, as the KPI rounding does not make sense in this case
|
||||||
* [CHG] The divider suffix (k, M, etc) is not inserted automatically anymore
|
* [CHG] The divider suffix (k, M, etc) is not inserted automatically anymore
|
||||||
|
@ -42,7 +45,7 @@ the weeks after.
|
||||||
* [FIX] use =like instead of like to search for accounts, because
|
* [FIX] use =like instead of like to search for accounts, because
|
||||||
the % are added by the user in the expressions
|
the % are added by the user in the expressions
|
||||||
* [FIX] Correctly compute the initial balance of income and expense account
|
* [FIX] Correctly compute the initial balance of income and expense account
|
||||||
based on the start of the fiscal year
|
based on the start of the fiscal year
|
||||||
* [IMP] Support date ranges (from OCA/server-tools/date_range) as a more
|
* [IMP] Support date ranges (from OCA/server-tools/date_range) as a more
|
||||||
flexible alternative to fiscal periods
|
flexible alternative to fiscal periods
|
||||||
* v9 migration: fiscal periods are removed, account charts are removed,
|
* v9 migration: fiscal periods are removed, account charts are removed,
|
||||||
|
|
|
@ -40,6 +40,14 @@ class KpiMatrixRow(object):
|
||||||
self.account_id = account_id
|
self.account_id = account_id
|
||||||
self.comment = ''
|
self.comment = ''
|
||||||
self.parent_row = parent_row
|
self.parent_row = parent_row
|
||||||
|
if not self.account_id:
|
||||||
|
self.style_props = self._matrix._style_model.merge([
|
||||||
|
self.kpi.report_id.style_id,
|
||||||
|
self.kpi.style_id])
|
||||||
|
else:
|
||||||
|
self.style_props = self._matrix._style_model.merge([
|
||||||
|
self.kpi.report_id.style_id,
|
||||||
|
self.kpi.auto_expand_accounts_style_id])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
|
@ -48,13 +56,6 @@ class KpiMatrixRow(object):
|
||||||
else:
|
else:
|
||||||
return self._matrix.get_account_name(self.account_id)
|
return self._matrix.get_account_name(self.account_id)
|
||||||
|
|
||||||
@property
|
|
||||||
def style(self):
|
|
||||||
if not self.account_id:
|
|
||||||
return self.kpi.style
|
|
||||||
else:
|
|
||||||
return self.kpi.auto_expand_accounts_style
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def row_id(self):
|
def row_id(self):
|
||||||
if not self.account_id:
|
if not self.account_id:
|
||||||
|
@ -134,13 +135,14 @@ class KpiMatrixCell(object):
|
||||||
|
|
||||||
def __init__(self, row, subcol,
|
def __init__(self, row, subcol,
|
||||||
val, val_rendered, val_comment,
|
val, val_rendered, val_comment,
|
||||||
style=None, drilldown_arg=None):
|
style_props,
|
||||||
|
drilldown_arg):
|
||||||
self.row = row
|
self.row = row
|
||||||
self.subcol = subcol
|
self.subcol = subcol
|
||||||
self.val = val
|
self.val = val
|
||||||
self.val_rendered = val_rendered
|
self.val_rendered = val_rendered
|
||||||
self.val_comment = val_comment
|
self.val_comment = val_comment
|
||||||
self.style = style
|
self.style_props = style_props
|
||||||
self.drilldown_arg = drilldown_arg
|
self.drilldown_arg = drilldown_arg
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,6 +153,7 @@ class KpiMatrix(object):
|
||||||
lang_model = env['res.lang']
|
lang_model = env['res.lang']
|
||||||
lang_id = lang_model._lang_get(env.user.lang)
|
lang_id = lang_model._lang_get(env.user.lang)
|
||||||
self.lang = lang_model.browse(lang_id)
|
self.lang = lang_model.browse(lang_id)
|
||||||
|
self._style_model = env['mis.report.style']
|
||||||
self._account_model = env['account.account']
|
self._account_model = env['account.account']
|
||||||
# data structures
|
# data structures
|
||||||
# { kpi: KpiMatrixRow }
|
# { kpi: KpiMatrixRow }
|
||||||
|
@ -224,7 +227,7 @@ class KpiMatrix(object):
|
||||||
val_rendered = val.name
|
val_rendered = val.name
|
||||||
val_comment = val.msg
|
val_comment = val.msg
|
||||||
else:
|
else:
|
||||||
val_rendered = kpi.render(self.lang, val)
|
val_rendered = kpi.render(self.lang, row.style_props, val)
|
||||||
if subcol.subkpi:
|
if subcol.subkpi:
|
||||||
val_comment = u'{}.{} = {}'.format(
|
val_comment = u'{}.{} = {}'.format(
|
||||||
row.kpi.name,
|
row.kpi.name,
|
||||||
|
@ -234,9 +237,9 @@ class KpiMatrix(object):
|
||||||
val_comment = u'{} = {}'.format(
|
val_comment = u'{} = {}'.format(
|
||||||
row.kpi.name,
|
row.kpi.name,
|
||||||
row.kpi.expression)
|
row.kpi.expression)
|
||||||
# TODO style
|
# TODO FIXME style expression
|
||||||
cell = KpiMatrixCell(row, subcol, val, val_rendered, val_comment,
|
cell = KpiMatrixCell(row, subcol, val, val_rendered, val_comment,
|
||||||
None, drilldown_arg)
|
row.style_props, drilldown_arg)
|
||||||
cell_tuple.append(cell)
|
cell_tuple.append(cell)
|
||||||
col._set_cell_tuple(row, cell_tuple)
|
col._set_cell_tuple(row, cell_tuple)
|
||||||
|
|
||||||
|
@ -279,10 +282,11 @@ class KpiMatrix(object):
|
||||||
base_vals,
|
base_vals,
|
||||||
comparison_col.iter_subcols()):
|
comparison_col.iter_subcols()):
|
||||||
# TODO FIXME average factors
|
# TODO FIXME average factors
|
||||||
delta, delta_r = row.kpi.compare_and_render(
|
delta, delta_r, style_r = row.kpi.compare_and_render(
|
||||||
self.lang, val, base_val, 1, 1)
|
self.lang, row.style_props, val, base_val, 1, 1)
|
||||||
comparison_cell_tuple.append(KpiMatrixCell(
|
comparison_cell_tuple.append(KpiMatrixCell(
|
||||||
row, comparison_subcol, delta, delta_r, None))
|
row, comparison_subcol, delta, delta_r, None,
|
||||||
|
style_r, None))
|
||||||
comparison_col._set_cell_tuple(row, comparison_cell_tuple)
|
comparison_col._set_cell_tuple(row, comparison_cell_tuple)
|
||||||
self._comparison_cols[pos_period_key].append(comparison_col)
|
self._comparison_cols[pos_period_key].append(comparison_col)
|
||||||
|
|
||||||
|
@ -355,11 +359,13 @@ class KpiMatrix(object):
|
||||||
row.parent_row.row_id or None),
|
row.parent_row.row_id or None),
|
||||||
'description': row.description,
|
'description': row.description,
|
||||||
'comment': row.comment,
|
'comment': row.comment,
|
||||||
'style': row.style and row.style.to_css_style() or None,
|
'style': self._style_model.to_css_style(
|
||||||
|
row.style_props),
|
||||||
'cols': []
|
'cols': []
|
||||||
}
|
}
|
||||||
for cell in row.iter_cells():
|
for cell in row.iter_cells():
|
||||||
if cell is None:
|
if cell is None:
|
||||||
|
# TODO use subcol style here
|
||||||
row_data['cols'].append({})
|
row_data['cols'].append({})
|
||||||
else:
|
else:
|
||||||
col_data = {
|
col_data = {
|
||||||
|
@ -367,7 +373,8 @@ class KpiMatrix(object):
|
||||||
if cell.val is not AccountingNone else None),
|
if cell.val is not AccountingNone else None),
|
||||||
'val_r': cell.val_rendered,
|
'val_r': cell.val_rendered,
|
||||||
'val_c': cell.val_comment,
|
'val_c': cell.val_comment,
|
||||||
# TODO FIXME style
|
'style': self._style_model.to_css_style(
|
||||||
|
cell.style_props),
|
||||||
}
|
}
|
||||||
if cell.drilldown_arg:
|
if cell.drilldown_arg:
|
||||||
col_data['drilldown_arg'] = cell.drilldown_arg
|
col_data['drilldown_arg'] = cell.drilldown_arg
|
||||||
|
@ -380,13 +387,6 @@ class KpiMatrix(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_selection_label(selection, value):
|
|
||||||
for v, l in selection:
|
|
||||||
if v == value:
|
|
||||||
return l
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def _utc_midnight(d, tz_name, add_day=0):
|
def _utc_midnight(d, tz_name, add_day=0):
|
||||||
d = fields.Datetime.from_string(d) + datetime.timedelta(days=add_day)
|
d = fields.Datetime.from_string(d) + datetime.timedelta(days=add_day)
|
||||||
utc_tz = pytz.timezone('UTC')
|
utc_tz = pytz.timezone('UTC')
|
||||||
|
@ -427,13 +427,13 @@ class MisReportKpi(models.Model):
|
||||||
inverse='_inverse_expression')
|
inverse='_inverse_expression')
|
||||||
expression_ids = fields.One2many('mis.report.kpi.expression', 'kpi_id')
|
expression_ids = fields.One2many('mis.report.kpi.expression', 'kpi_id')
|
||||||
auto_expand_accounts = fields.Boolean(string='Display details by account')
|
auto_expand_accounts = fields.Boolean(string='Display details by account')
|
||||||
auto_expand_accounts_style = fields.Many2one(
|
auto_expand_accounts_style_id = fields.Many2one(
|
||||||
string="Style for account detail rows",
|
string="Style for account detail rows",
|
||||||
comodel_name="mis.report.style",
|
comodel_name="mis.report.style",
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
style = fields.Many2one(
|
style_id = fields.Many2one(
|
||||||
string="Row style",
|
string="Style",
|
||||||
comodel_name="mis.report.style",
|
comodel_name="mis.report.style",
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
@ -445,18 +445,8 @@ class MisReportKpi(models.Model):
|
||||||
('pct', _('Percentage')),
|
('pct', _('Percentage')),
|
||||||
('str', _('String'))],
|
('str', _('String'))],
|
||||||
required=True,
|
required=True,
|
||||||
string='Type',
|
string='Value type',
|
||||||
default='num')
|
default='num')
|
||||||
divider = fields.Selection([('1e-6', _('µ')),
|
|
||||||
('1e-3', _('m')),
|
|
||||||
('1', _('1')),
|
|
||||||
('1e3', _('k')),
|
|
||||||
('1e6', _('M'))],
|
|
||||||
string='Factor',
|
|
||||||
default='1')
|
|
||||||
dp = fields.Integer(string='Rounding', default=0)
|
|
||||||
prefix = fields.Char(size=16, string='Prefix')
|
|
||||||
suffix = fields.Char(size=16, string='Suffix')
|
|
||||||
compare_method = fields.Selection([('diff', _('Difference')),
|
compare_method = fields.Selection([('diff', _('Difference')),
|
||||||
('pct', _('Percentage')),
|
('pct', _('Percentage')),
|
||||||
('none', _('None'))],
|
('none', _('None'))],
|
||||||
|
@ -544,57 +534,57 @@ class MisReportKpi(models.Model):
|
||||||
def _onchange_type(self):
|
def _onchange_type(self):
|
||||||
if self.type == 'num':
|
if self.type == 'num':
|
||||||
self.compare_method = 'pct'
|
self.compare_method = 'pct'
|
||||||
self.divider = '1'
|
|
||||||
self.dp = 0
|
|
||||||
elif self.type == 'pct':
|
elif self.type == 'pct':
|
||||||
self.compare_method = 'diff'
|
self.compare_method = 'diff'
|
||||||
self.divider = '1'
|
|
||||||
self.dp = 0
|
|
||||||
elif self.type == 'str':
|
elif self.type == 'str':
|
||||||
self.compare_method = 'none'
|
self.compare_method = 'none'
|
||||||
self.divider = ''
|
|
||||||
self.dp = 0
|
|
||||||
|
|
||||||
def get_expression_for_subkpi(self, subkpi):
|
def get_expression_for_subkpi(self, subkpi):
|
||||||
for expression in self.expression_ids:
|
for expression in self.expression_ids:
|
||||||
if expression.subkpi_id == subkpi:
|
if expression.subkpi_id == subkpi:
|
||||||
return expression.name
|
return expression.name
|
||||||
|
|
||||||
def render(self, lang, value):
|
@api.multi
|
||||||
|
def render(self, lang, style_props, 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
|
self.ensure_one()
|
||||||
if value is None or value is AccountingNone:
|
style_obj = self.env['mis.report.style']
|
||||||
return ''
|
if self.type == 'num':
|
||||||
elif self.type == 'num':
|
return style_obj.render_num(lang, value, style_props.divider,
|
||||||
return self._render_num(lang, value, self.divider,
|
style_props.dp,
|
||||||
self.dp, self.prefix, self.suffix)
|
style_props.prefix, style_props.suffix)
|
||||||
elif self.type == 'pct':
|
elif self.type == 'pct':
|
||||||
return self._render_num(lang, value, 0.01,
|
return style_obj.render_pct(lang, value, style_props.dp)
|
||||||
self.dp, '', '%')
|
|
||||||
else:
|
else:
|
||||||
return unicode(value)
|
return style_obj.render_str(lang, value)
|
||||||
|
|
||||||
def compare_and_render(self, lang, value, base_value,
|
@api.multi
|
||||||
|
def compare_and_render(self, lang, style_props, value, base_value,
|
||||||
average_value=1, average_base_value=1):
|
average_value=1, average_base_value=1):
|
||||||
""" render the comparison of two KPI values, ready for display
|
""" render the comparison of two KPI values, ready for display
|
||||||
|
|
||||||
Returns a tuple, with the numeric comparison and its string rendering.
|
Returns a triple, with
|
||||||
|
* the numeric comparison
|
||||||
|
* its string rendering
|
||||||
|
* the update style properties
|
||||||
|
|
||||||
If the difference is 0, an empty string is returned.
|
If the difference is 0, an empty string is returned.
|
||||||
"""
|
"""
|
||||||
assert len(self) == 1
|
self.ensure_one()
|
||||||
|
style_obj = self.env['mis.report.style']
|
||||||
|
delta = AccountingNone
|
||||||
|
style_r = style_props.copy()
|
||||||
if value is None:
|
if value is None:
|
||||||
value = AccountingNone
|
value = AccountingNone
|
||||||
if base_value is None:
|
if base_value is None:
|
||||||
base_value = AccountingNone
|
base_value = AccountingNone
|
||||||
if self.type == 'pct':
|
if self.type == 'pct':
|
||||||
delta = value - base_value
|
delta = value - base_value
|
||||||
if delta and round(delta, self.dp + 2) != 0:
|
if delta and round(delta, (style_props.dp or 0) + 2) != 0:
|
||||||
return delta, self._render_num(
|
style_r.update(dict(
|
||||||
lang,
|
divider=0.01, prefix='', suffix=_('pp')))
|
||||||
delta,
|
else:
|
||||||
0.01, self.dp, '', _('pp'),
|
delta = AccountingNone
|
||||||
sign='+')
|
|
||||||
elif self.type == 'num':
|
elif self.type == 'num':
|
||||||
if value and average_value:
|
if value and average_value:
|
||||||
value = value / float(average_value)
|
value = value / float(average_value)
|
||||||
|
@ -602,43 +592,27 @@ class MisReportKpi(models.Model):
|
||||||
base_value = base_value / float(average_base_value)
|
base_value = base_value / float(average_base_value)
|
||||||
if self.compare_method == 'diff':
|
if self.compare_method == 'diff':
|
||||||
delta = value - base_value
|
delta = value - base_value
|
||||||
if delta and round(delta, self.dp) != 0:
|
if delta and round(delta, style_props.dp or 0) != 0:
|
||||||
return delta, self._render_num(
|
pass
|
||||||
lang,
|
|
||||||
delta,
|
|
||||||
self.divider, self.dp, self.prefix, self.suffix,
|
|
||||||
sign='+')
|
|
||||||
elif self.compare_method == 'pct':
|
|
||||||
if base_value and round(base_value, self.dp) != 0:
|
|
||||||
delta = (value - base_value) / abs(base_value)
|
|
||||||
if delta and round(delta, self.dp) != 0:
|
|
||||||
return delta, self._render_num(
|
|
||||||
lang,
|
|
||||||
delta,
|
|
||||||
0.01, 1, '', '%',
|
|
||||||
sign='+')
|
|
||||||
else:
|
else:
|
||||||
return AccountingNone, ''
|
delta = AccountingNone
|
||||||
return 0, ''
|
elif self.compare_method == 'pct':
|
||||||
|
if base_value and round(base_value, style_props.dp or 0) != 0:
|
||||||
def _render_num(self, lang, value, divider,
|
delta = (value - base_value) / abs(base_value)
|
||||||
dp, prefix, suffix, sign='-'):
|
if delta and round(delta, 1) != 0:
|
||||||
# format number following user language
|
style_r.update(dict(
|
||||||
value = round(value / float(divider or 1), dp) or 0
|
divider=0.01, dp=1, prefix='', suffix='%'))
|
||||||
value = lang.format(
|
else:
|
||||||
'%%%s.%df' % (sign, dp),
|
delta = AccountingNone
|
||||||
value,
|
if delta is not AccountingNone:
|
||||||
grouping=True)
|
delta_r = style_obj.render_num(
|
||||||
value = value.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
lang, delta,
|
||||||
if prefix:
|
style_r.divider, style_r.dp,
|
||||||
prefix = prefix + u'\N{NO-BREAK SPACE}'
|
style_r.prefix, style_r.suffix,
|
||||||
|
sign='+')
|
||||||
|
return delta, delta_r, style_r
|
||||||
else:
|
else:
|
||||||
prefix = ''
|
return AccountingNone, '', style_r
|
||||||
if suffix:
|
|
||||||
suffix = u'\N{NO-BREAK SPACE}' + suffix
|
|
||||||
else:
|
|
||||||
suffix = ''
|
|
||||||
return prefix + value + suffix
|
|
||||||
|
|
||||||
|
|
||||||
class MisReportSubkpi(models.Model):
|
class MisReportSubkpi(models.Model):
|
||||||
|
@ -773,6 +747,8 @@ class MisReport(models.Model):
|
||||||
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)
|
||||||
|
style_id = fields.Many2one(string="Style",
|
||||||
|
comodel_name="mis.report.style")
|
||||||
query_ids = fields.One2many('mis.report.query', 'report_id',
|
query_ids = fields.One2many('mis.report.query', 'report_id',
|
||||||
string='Queries',
|
string='Queries',
|
||||||
copy=True)
|
copy=True)
|
||||||
|
|
|
@ -3,16 +3,45 @@
|
||||||
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
|
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from openerp import api, fields, models
|
from openerp import api, fields, models, _
|
||||||
|
from openerp.exceptions import UserError
|
||||||
|
|
||||||
|
from .accounting_none import AccountingNone
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyDict(dict):
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return self.get(name)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return PropertyDict(self)
|
||||||
|
|
||||||
|
|
||||||
|
PROPS = [
|
||||||
|
'color',
|
||||||
|
'background_color',
|
||||||
|
'font_style',
|
||||||
|
'font_weight',
|
||||||
|
'font_size',
|
||||||
|
'indent_level',
|
||||||
|
'prefix',
|
||||||
|
'suffix',
|
||||||
|
'dp',
|
||||||
|
'divider',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class MisReportKpiStyle(models.Model):
|
class MisReportKpiStyle(models.Model):
|
||||||
|
|
||||||
_name = 'mis.report.style'
|
_name = 'mis.report.style'
|
||||||
|
|
||||||
@api.depends('indent_level')
|
@api.one
|
||||||
|
@api.constrains('indent_level')
|
||||||
def check_positive_val(self):
|
def check_positive_val(self):
|
||||||
return self.indent_level > 0
|
if self.indent_level < 0:
|
||||||
|
raise UserError(_('Indent level must be greater than '
|
||||||
|
'or equal to 0'))
|
||||||
|
|
||||||
_font_style_selection = [
|
_font_style_selection = [
|
||||||
('normal', 'Normal'),
|
('normal', 'Normal'),
|
||||||
|
@ -25,7 +54,7 @@ class MisReportKpiStyle(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
_font_size_selection = [
|
_font_size_selection = [
|
||||||
('medium', ''),
|
('medium', 'medium'),
|
||||||
('xx-small', 'xx-small'),
|
('xx-small', 'xx-small'),
|
||||||
('x-small', 'x-small'),
|
('x-small', 'x-small'),
|
||||||
('small', 'small'),
|
('small', 'small'),
|
||||||
|
@ -34,7 +63,7 @@ class MisReportKpiStyle(models.Model):
|
||||||
('xx-large', 'xx-large'),
|
('xx-large', 'xx-large'),
|
||||||
]
|
]
|
||||||
|
|
||||||
_font_size_to_size = {
|
_font_size_to_xlsx_size = {
|
||||||
'medium': 11,
|
'medium': 11,
|
||||||
'xx-small': 5,
|
'xx-small': 5,
|
||||||
'x-small': 7,
|
'x-small': 7,
|
||||||
|
@ -44,57 +73,129 @@ class MisReportKpiStyle(models.Model):
|
||||||
'xx-large': 17
|
'xx-large': 17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# style name
|
||||||
|
# TODO enforce uniqueness
|
||||||
name = fields.Char(string='Style name', required=True)
|
name = fields.Char(string='Style name', required=True)
|
||||||
|
|
||||||
|
# color
|
||||||
|
color_inherit = fields.Boolean(default=True)
|
||||||
color = fields.Char(
|
color = fields.Char(
|
||||||
string='Text color',
|
string='Text color',
|
||||||
help='Text color in valid RGB code (from #000000 to #FFFFFF)',
|
help='Text color in valid RGB code (from #000000 to #FFFFFF)',
|
||||||
)
|
)
|
||||||
|
background_color_inherit = fields.Boolean(default=True)
|
||||||
background_color = fields.Char(
|
background_color = fields.Char(
|
||||||
help='Background color in valid RGB code (from #000000 to #FFFFFF)'
|
help='Background color in valid RGB code (from #000000 to #FFFFFF)'
|
||||||
)
|
)
|
||||||
|
# font
|
||||||
|
font_style_inherit = fields.Boolean(default=True)
|
||||||
font_style = fields.Selection(
|
font_style = fields.Selection(
|
||||||
selection=_font_style_selection,
|
selection=_font_style_selection,
|
||||||
)
|
)
|
||||||
|
font_weight_inherit = fields.Boolean(default=True)
|
||||||
font_weight = fields.Selection(
|
font_weight = fields.Selection(
|
||||||
selection=_font_weight_selection
|
selection=_font_weight_selection
|
||||||
)
|
)
|
||||||
|
font_size_inherit = fields.Boolean(default=True)
|
||||||
font_size = fields.Selection(
|
font_size = fields.Selection(
|
||||||
selection=_font_size_selection
|
selection=_font_size_selection
|
||||||
)
|
)
|
||||||
|
# indent
|
||||||
|
indent_level_inherit = fields.Boolean(default=True)
|
||||||
indent_level = fields.Integer()
|
indent_level = fields.Integer()
|
||||||
|
# number format
|
||||||
|
prefix_inherit = fields.Boolean(default=True)
|
||||||
|
prefix = fields.Char(size=16, string='Prefix')
|
||||||
|
suffix_inherit = fields.Boolean(default=True)
|
||||||
|
suffix = fields.Char(size=16, string='Suffix')
|
||||||
|
dp_inherit = fields.Boolean(default=True)
|
||||||
|
dp = fields.Integer(string='Rounding', default=0)
|
||||||
|
divider_inherit = fields.Boolean(default=True)
|
||||||
|
divider = fields.Selection([('1e-6', _('µ')),
|
||||||
|
('1e-3', _('m')),
|
||||||
|
('1', _('1')),
|
||||||
|
('1e3', _('k')),
|
||||||
|
('1e6', _('M'))],
|
||||||
|
string='Factor',
|
||||||
|
default='1')
|
||||||
|
|
||||||
@api.multi
|
@api.model
|
||||||
def font_size_to_size(self):
|
def merge(self, styles):
|
||||||
self.ensure_one()
|
r = PropertyDict()
|
||||||
return self._font_size_to_size.get(self.font_size, 11)
|
for style in styles:
|
||||||
|
if not style:
|
||||||
|
continue
|
||||||
|
if isinstance(style, dict):
|
||||||
|
r.update(style)
|
||||||
|
else:
|
||||||
|
for prop in PROPS:
|
||||||
|
inherit = getattr(style, prop + '_inherit', None)
|
||||||
|
if inherit is None:
|
||||||
|
value = getattr(style, prop)
|
||||||
|
if value:
|
||||||
|
r[prop] = value
|
||||||
|
elif not inherit:
|
||||||
|
value = getattr(style, prop)
|
||||||
|
r[prop] = value
|
||||||
|
return r
|
||||||
|
|
||||||
@api.multi
|
@api.model
|
||||||
def to_xlsx_format_properties(self):
|
def render_num(self, lang, value,
|
||||||
self.ensure_one()
|
divider=1.0, dp=0, prefix=None, suffix=None, sign='-'):
|
||||||
props = {
|
# format number following user language
|
||||||
'italic': self.font_style == 'italic',
|
if value is None or value is AccountingNone:
|
||||||
'bold': self.font_weight == 'bold',
|
return u''
|
||||||
'font_color': self.color,
|
value = round(value / float(divider or 1), dp or 0) or 0
|
||||||
'bg_color': self.background_color,
|
r = lang.format('%%%s.%df' % (sign, dp or 0), value, grouping=True)
|
||||||
'indent': self.indent_level,
|
r = r.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
||||||
'size': self.font_size_to_size()
|
if prefix:
|
||||||
}
|
r = prefix + u'\N{NO-BREAK SPACE}' + r
|
||||||
return props
|
if suffix:
|
||||||
|
r = r + u'\N{NO-BREAK SPACE}' + suffix
|
||||||
|
return r
|
||||||
|
|
||||||
@api.multi
|
@api.model
|
||||||
def to_css_style(self):
|
def render_pct(self, lang, value, dp=1, sign='-'):
|
||||||
self.ensure_one()
|
return self.render_num(lang, value, divider=0.01,
|
||||||
|
dp=dp, suffix='%', sign=sign)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def render_str(self, lang, value):
|
||||||
|
if value is None or value is AccountingNone:
|
||||||
|
return u''
|
||||||
|
return unicode(value)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def to_xlsx_style(self, props):
|
||||||
|
num_format = '0.'
|
||||||
|
if props.dp:
|
||||||
|
num_format += '0' * props.dp
|
||||||
|
if props.prefix:
|
||||||
|
num_format = u'"{} "{}'.format(props.prefix, num_format)
|
||||||
|
if props.suffix:
|
||||||
|
num_format = u'{}" {}"'.format(num_format, props.suffix)
|
||||||
|
|
||||||
|
xlsx_attributes = [
|
||||||
|
('italic', props.font_style == 'italic'),
|
||||||
|
('bold', props.font_weight == 'bold'),
|
||||||
|
('size', self._font_size_to_xlsx_size.get(props.font_size, 11)),
|
||||||
|
('font_color', props.color),
|
||||||
|
('bg_color', props.background_color),
|
||||||
|
('indent', props.indent_level),
|
||||||
|
('num_format', num_format),
|
||||||
|
]
|
||||||
|
return dict([a for a in xlsx_attributes
|
||||||
|
if a[1] is not None])
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def to_css_style(self, props):
|
||||||
css_attributes = [
|
css_attributes = [
|
||||||
('font-style', self.font_style),
|
('font-style', props.font_style),
|
||||||
('font-weight', self.font_weight),
|
('font-weight', props.font_weight),
|
||||||
('font-size', self.font_size),
|
('font-size', props.font_size),
|
||||||
('color', self.color),
|
('color', props.color),
|
||||||
('background-color', self.background_color),
|
('background-color', props.background_color),
|
||||||
('indent-level', self.indent_level)
|
('indent-level', props.indent_level)
|
||||||
]
|
]
|
||||||
|
return '; '.join(['%s: %s' % a for a in css_attributes
|
||||||
css_list = [
|
if a[1] is not None]) or None
|
||||||
'%s:%s' % x for x in css_attributes if x[1]
|
|
||||||
]
|
|
||||||
return ';'.join(css_item for css_item in css_list)
|
|
||||||
|
|
|
@ -17,11 +17,18 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
TODO we use divs with css table layout, but this has drawbacks:
|
||||||
|
(bad layout of first column, no colspan for first header row),
|
||||||
|
consider getting back to a plain HTML table.
|
||||||
|
-->
|
||||||
|
|
||||||
<template id="report_mis_report_instance">
|
<template id="report_mis_report_instance">
|
||||||
<t t-call="report.html_container">
|
<t t-call="report.html_container">
|
||||||
<t t-foreach="docs" t-as="o">
|
<t t-foreach="docs" t-as="o">
|
||||||
<t t-call="report.internal_layout">
|
<t t-call="report.internal_layout">
|
||||||
<t t-set="matrix" t-value="o._compute_matrix()"/>
|
<t t-set="matrix" t-value="o._compute_matrix()"/>
|
||||||
|
<t t-set="style_obj" t-value="o.env['mis.report.style']"/>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h2><span t-field="o.name" /> - <span t-field="o.company_id.name" /></h2>
|
<h2><span t-field="o.name" /> - <span t-field="o.company_id.name" /></h2>
|
||||||
<div class="mis_table">
|
<div class="mis_table">
|
||||||
|
@ -57,7 +64,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mis_tbody">
|
<div class="mis_tbody">
|
||||||
<div t-foreach="matrix.iter_rows()" t-as="row" class="mis_row">
|
<div t-foreach="matrix.iter_rows()" t-as="row" class="mis_row">
|
||||||
<div t-att-style="row.style and row.style.to_css_style() or ''" class="mis_cell mis_rowlabel">
|
<div t-att-style="style_obj.to_css_style(row.style_props)" class="mis_cell mis_rowlabel">
|
||||||
<t t-esc="row.description"/>
|
<t t-esc="row.description"/>
|
||||||
<t t-if="row.comment">
|
<t t-if="row.comment">
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -65,16 +72,9 @@
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
<t t-foreach="row.iter_cells()" t-as="cell">
|
<t t-foreach="row.iter_cells()" t-as="cell">
|
||||||
<t t-if="cell">
|
<div t-att-style="cell and style_obj.to_css_style(cell.style_props) or ''" class="mis_cell mis_amount">
|
||||||
<div t-att-style="cell.row.style and cell.row.style.to_css_style() or ''" class="mis_cell mis_amount">
|
<t t-esc="cell and cell.val_rendered or ''"/>
|
||||||
<div t-att-style="cell.style and cell.style.to_css_style() or ''">
|
</div>
|
||||||
<t t-esc="cell.val_rendered"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-if="not cell">
|
|
||||||
<div class="mis_cell mis_amount"></div>
|
|
||||||
</t>
|
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,7 +14,7 @@ _logger = logging.getLogger(__name__)
|
||||||
try:
|
try:
|
||||||
from openerp.addons.report_xlsx.report.report_xlsx import ReportXlsx
|
from openerp.addons.report_xlsx.report.report_xlsx import ReportXlsx
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_logger.debug("report_xslx not installed, Excel export non functional")
|
_logger.debug("report_xlsx not installed, Excel export non functional")
|
||||||
|
|
||||||
class ReportXslx:
|
class ReportXslx:
|
||||||
pass
|
pass
|
||||||
|
@ -33,23 +33,11 @@ class MisBuilderXslx(ReportXlsx):
|
||||||
super(MisBuilderXslx, self).__init__(
|
super(MisBuilderXslx, self).__init__(
|
||||||
name, table, rml, parser, header, store)
|
name, table, rml, parser, header, store)
|
||||||
|
|
||||||
def make_number_format(self, kpi, comparison=False):
|
|
||||||
# TODO FIXME comparison
|
|
||||||
number_format = '#'
|
|
||||||
if kpi.dp:
|
|
||||||
number_format += '.'
|
|
||||||
number_format += '0' * kpi.dp
|
|
||||||
# TODO FIXME factor
|
|
||||||
if kpi.prefix:
|
|
||||||
number_format = u'"{} "{}'.format(kpi.prefix, number_format)
|
|
||||||
if kpi.suffix:
|
|
||||||
number_format = u'{}" {}"'.format(number_format, kpi.suffix)
|
|
||||||
return number_format
|
|
||||||
|
|
||||||
def generate_xlsx_report(self, workbook, data, objects):
|
def generate_xlsx_report(self, workbook, data, objects):
|
||||||
|
|
||||||
# get the computed result of the report
|
# get the computed result of the report
|
||||||
matrix = objects._compute_matrix()
|
matrix = objects._compute_matrix()
|
||||||
|
style_obj = self.env['mis.report.style']
|
||||||
|
|
||||||
# create worksheet
|
# create worksheet
|
||||||
report_name = '{} - {}'.format(
|
report_name = '{} - {}'.format(
|
||||||
|
@ -107,10 +95,7 @@ class MisBuilderXslx(ReportXlsx):
|
||||||
|
|
||||||
# rows
|
# rows
|
||||||
for row in matrix.iter_rows():
|
for row in matrix.iter_rows():
|
||||||
if row.style:
|
row_xlsx_style = style_obj.to_xlsx_style(row.style_props)
|
||||||
row_xlsx_style = row.style.to_xlsx_format_properties()
|
|
||||||
else:
|
|
||||||
row_xlsx_style = {}
|
|
||||||
row_format = workbook.add_format(row_xlsx_style)
|
row_format = workbook.add_format(row_xlsx_style)
|
||||||
col_pos = 0
|
col_pos = 0
|
||||||
sheet.write(row_pos, col_pos, row.description, row_format)
|
sheet.write(row_pos, col_pos, row.description, row_format)
|
||||||
|
@ -118,19 +103,13 @@ class MisBuilderXslx(ReportXlsx):
|
||||||
for cell in row.iter_cells():
|
for cell in row.iter_cells():
|
||||||
col_pos += 1
|
col_pos += 1
|
||||||
if not cell or cell.val is AccountingNone:
|
if not cell or cell.val is AccountingNone:
|
||||||
|
# TODO col/subcol format
|
||||||
sheet.write(row_pos, col_pos, '', row_format)
|
sheet.write(row_pos, col_pos, '', row_format)
|
||||||
continue
|
continue
|
||||||
kpi_xlsx_style = dict(row_xlsx_style)
|
cell_xlsx_style = style_obj.to_xlsx_style(cell.style_props)
|
||||||
kpi_xlsx_style.update({
|
cell_xlsx_style['align'] = 'right'
|
||||||
'num_format': self.make_number_format(row.kpi),
|
kpi_format = workbook.add_format(cell_xlsx_style)
|
||||||
'align': 'right'
|
val = cell.val / float(cell.style_props.get('divider', 1))
|
||||||
})
|
|
||||||
kpi_format = workbook.add_format(kpi_xlsx_style)
|
|
||||||
# TODO FIXME kpi computed style
|
|
||||||
# TODO FIXME pct in comparision columns
|
|
||||||
val = cell.val
|
|
||||||
if row.kpi.type == 'pct':
|
|
||||||
val = val / 0.01
|
|
||||||
sheet.write(row_pos, col_pos, val, kpi_format)
|
sheet.write(row_pos, col_pos, val, kpi_format)
|
||||||
col_width[col_pos] = max(col_width[col_pos],
|
col_width[col_pos] = max(col_width[col_pos],
|
||||||
len(cell.val_rendered or ''))
|
len(cell.val_rendered or ''))
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
"id","compare_method","description","expression","divider","name","dp","sequence","type","suffix"
|
"id","description","expression","name"
|
||||||
"mis_report_kpi_test","Percentage","total test","len(test)","","total_test","","1","Numeric",""
|
"mis_report_kpi_test","total test","len(test)","total_test"
|
||||||
|
|
|
|
@ -23,6 +23,7 @@ class TestFetchQuery(common.TransactionCase):
|
||||||
'cols': [{'val': 0,
|
'cols': [{'val': 0,
|
||||||
'val_r': u'0',
|
'val_r': u'0',
|
||||||
'val_c': u'total_test = len(test)',
|
'val_c': u'total_test = len(test)',
|
||||||
|
'style': None,
|
||||||
}]
|
}]
|
||||||
}],
|
}],
|
||||||
'header':
|
'header':
|
||||||
|
|
|
@ -11,132 +11,147 @@ class TestRendering(common.TransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestRendering, self).setUp()
|
super(TestRendering, self).setUp()
|
||||||
self.kpi = self.env['mis.report.kpi'].create(dict(
|
self.style_obj = self.env['mis.report.style']
|
||||||
|
self.kpi_obj = self.env['mis.report.kpi']
|
||||||
|
self.style = self.style_obj.create(dict(
|
||||||
|
name='teststyle',
|
||||||
|
))
|
||||||
|
self.kpi = self.kpi_obj.create(dict(
|
||||||
name='testkpi',
|
name='testkpi',
|
||||||
description='Test KPI',
|
description='test kpi',
|
||||||
type='num',
|
type='num',
|
||||||
dp=0,
|
style_id=self.style.id,
|
||||||
))
|
))
|
||||||
self.lang = self.env['res.lang'].search([('code', '=', 'en_US')])[0]
|
self.lang = self.env['res.lang'].search([('code', '=', 'en_US')])[0]
|
||||||
|
|
||||||
|
def _render(self, value):
|
||||||
|
style_props = self.style_obj.merge([self.style])
|
||||||
|
return self.kpi.render(self.lang, style_props, value)
|
||||||
|
|
||||||
|
def _compare_and_render(self, value, base_value):
|
||||||
|
style_props = self.style_obj.merge([self.style])
|
||||||
|
return self.kpi.compare_and_render(self.lang, style_props,
|
||||||
|
value, base_value)[:2]
|
||||||
|
|
||||||
def test_render(self):
|
def test_render(self):
|
||||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1))
|
self.assertEquals(u'1', self._render(1))
|
||||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1.1))
|
self.assertEquals(u'1', self._render(1.1))
|
||||||
self.assertEquals(u'2', self.kpi.render(self.lang, 1.6))
|
self.assertEquals(u'2', self._render(1.6))
|
||||||
self.kpi.dp = 2
|
self.style.dp_inherit = False
|
||||||
self.assertEquals(u'1.00', self.kpi.render(self.lang, 1))
|
self.style.dp = 2
|
||||||
self.assertEquals(u'1.10', self.kpi.render(self.lang, 1.1))
|
self.assertEquals(u'1.00', self._render(1))
|
||||||
self.assertEquals(u'1.60', self.kpi.render(self.lang, 1.6))
|
self.assertEquals(u'1.10', self._render(1.1))
|
||||||
self.assertEquals(u'1.61', self.kpi.render(self.lang, 1.606))
|
self.assertEquals(u'1.60', self._render(1.6))
|
||||||
self.assertEquals(u'12,345.67', self.kpi.render(self.lang, 12345.67))
|
self.assertEquals(u'1.61', self._render(1.606))
|
||||||
|
self.assertEquals(u'12,345.67', self._render(12345.67))
|
||||||
|
|
||||||
def test_render_negative(self):
|
def test_render_negative(self):
|
||||||
# non breaking hyphen
|
# non breaking hyphen
|
||||||
self.assertEquals(u'\u20111', self.kpi.render(self.lang, -1))
|
self.assertEquals(u'\u20111', self._render(-1))
|
||||||
|
|
||||||
def test_render_zero(self):
|
def test_render_zero(self):
|
||||||
self.assertEquals(u'0', self.kpi.render(self.lang, 0))
|
self.assertEquals(u'0', self._render(0))
|
||||||
self.assertEquals(u'', self.kpi.render(self.lang, None))
|
self.assertEquals(u'', self._render(None))
|
||||||
self.assertEquals(u'', self.kpi.render(self.lang, AccountingNone))
|
self.assertEquals(u'', self._render(AccountingNone))
|
||||||
|
|
||||||
def test_render_suffix(self):
|
def test_render_suffix(self):
|
||||||
self.kpi.suffix = u'€'
|
self.style.suffix_inherit = False
|
||||||
self.assertEquals(u'1\xa0€', self.kpi.render(self.lang, 1))
|
self.style.suffix = u'€'
|
||||||
self.kpi.suffix = u'k€'
|
self.assertEquals(u'1\xa0€', self._render(1))
|
||||||
self.kpi.divider = '1e3'
|
self.style.suffix = u'k€'
|
||||||
self.assertEquals(u'1\xa0k€', self.kpi.render(self.lang, 1000))
|
self.style.divider_inherit = False
|
||||||
|
self.style.divider = '1e3'
|
||||||
|
self.assertEquals(u'1\xa0k€', self._render(1000))
|
||||||
|
|
||||||
def test_render_prefix(self):
|
def test_render_prefix(self):
|
||||||
self.kpi.prefix = u'$'
|
self.style.prefix_inherit = False
|
||||||
self.assertEquals(u'$\xa01', self.kpi.render(self.lang, 1))
|
self.style.prefix = u'$'
|
||||||
self.kpi.prefix = u'k$'
|
self.assertEquals(u'$\xa01', self._render(1))
|
||||||
self.kpi.divider = '1e3'
|
self.style.prefix = u'k$'
|
||||||
self.assertEquals(u'k$\xa01', self.kpi.render(self.lang, 1000))
|
self.style.divider_inherit = False
|
||||||
|
self.style.divider = '1e3'
|
||||||
|
self.assertEquals(u'k$\xa01', self._render(1000))
|
||||||
|
|
||||||
def test_render_divider(self):
|
def test_render_divider(self):
|
||||||
self.kpi.divider = '1e3'
|
self.style.divider_inherit = False
|
||||||
self.kpi.dp = 0
|
self.style.divider = '1e3'
|
||||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1000))
|
self.style.dp_inherit = False
|
||||||
self.kpi.divider = '1e6'
|
self.style.dp = 0
|
||||||
self.kpi.dp = 3
|
self.assertEquals(u'1', self._render(1000))
|
||||||
self.assertEquals(u'0.001', self.kpi.render(self.lang, 1000))
|
self.style.divider = '1e6'
|
||||||
self.kpi.divider = '1e-3'
|
self.style.dp = 3
|
||||||
self.kpi.dp = 0
|
self.assertEquals(u'0.001', self._render(1000))
|
||||||
self.assertEquals(u'1,000', self.kpi.render(self.lang, 1))
|
self.style.divider = '1e-3'
|
||||||
self.kpi.divider = '1e-6'
|
self.style.dp = 0
|
||||||
self.kpi.dp = 0
|
self.assertEquals(u'1,000', self._render(1))
|
||||||
self.assertEquals(u'1,000,000', self.kpi.render(self.lang, 1))
|
self.style.divider = '1e-6'
|
||||||
|
self.style.dp = 0
|
||||||
|
self.assertEquals(u'1,000,000', self._render(1))
|
||||||
|
|
||||||
def test_render_pct(self):
|
def test_render_pct(self):
|
||||||
self.kpi.type = 'pct'
|
self.kpi.type = 'pct'
|
||||||
self.assertEquals(u'100\xa0%', self.kpi.render(self.lang, 1))
|
self.assertEquals(u'100\xa0%', self._render(1))
|
||||||
self.assertEquals(u'50\xa0%', self.kpi.render(self.lang, 0.5))
|
self.assertEquals(u'50\xa0%', self._render(0.5))
|
||||||
self.kpi.dp = 2
|
self.style.dp_inherit = False
|
||||||
self.assertEquals(u'51.23\xa0%', self.kpi.render(self.lang, 0.5123))
|
self.style.dp = 2
|
||||||
|
self.assertEquals(u'51.23\xa0%', self._render(0.5123))
|
||||||
|
|
||||||
def test_render_string(self):
|
def test_render_string(self):
|
||||||
self.kpi.type = 'str'
|
self.kpi.type = 'str'
|
||||||
self.assertEquals(u'', self.kpi.render(self.lang, ''))
|
self.assertEquals(u'', self._render(''))
|
||||||
self.assertEquals(u'', self.kpi.render(self.lang, None))
|
self.assertEquals(u'', self._render(None))
|
||||||
self.assertEquals(u'abcdé', self.kpi.render(self.lang, u'abcdé'))
|
self.assertEquals(u'abcdé', self._render(u'abcdé'))
|
||||||
|
|
||||||
def test_compare_num_pct(self):
|
def test_compare_num_pct(self):
|
||||||
self.assertEquals('pct', self.kpi.compare_method)
|
self.assertEquals('pct', self.kpi.compare_method)
|
||||||
self.assertEquals((1.0, u'+100.0\xa0%'),
|
self.assertEquals((1.0, u'+100.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 100, 50))
|
self._compare_and_render(100, 50))
|
||||||
self.assertEquals((0.5, u'+50.0\xa0%'),
|
self.assertEquals((0.5, u'+50.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 75, 50))
|
self._compare_and_render(75, 50))
|
||||||
self.assertEquals((0.5, u'+50.0\xa0%'),
|
self.assertEquals((0.5, u'+50.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, -25, -50))
|
self._compare_and_render(-25, -50))
|
||||||
self.assertEquals((1.0, u'+100.0\xa0%'),
|
self.assertEquals((1.0, u'+100.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 0, -50))
|
self._compare_and_render(0, -50))
|
||||||
self.assertEquals((2.0, u'+200.0\xa0%'),
|
self.assertEquals((2.0, u'+200.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 50, -50))
|
self._compare_and_render(50, -50))
|
||||||
self.assertEquals((-0.5, u'\u201150.0\xa0%'),
|
self.assertEquals((-0.5, u'\u201150.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 25, 50))
|
self._compare_and_render(25, 50))
|
||||||
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, 0, 50))
|
self._compare_and_render(0, 50))
|
||||||
self.assertEquals((-2.0, u'\u2011200.0\xa0%'),
|
self.assertEquals((-2.0, u'\u2011200.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, -50, 50))
|
self._compare_and_render(-50, 50))
|
||||||
self.assertEquals((-0.5, u'\u201150.0\xa0%'),
|
self.assertEquals((-0.5, u'\u201150.0\xa0%'),
|
||||||
self.kpi.compare_and_render(self.lang, -75, -50))
|
self._compare_and_render(-75, -50))
|
||||||
self.assertEquals((AccountingNone, u''),
|
self.assertEquals((AccountingNone, u''),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(50, AccountingNone))
|
||||||
self.lang, 50, AccountingNone))
|
|
||||||
self.assertEquals((AccountingNone, u''),
|
self.assertEquals((AccountingNone, u''),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(50, None))
|
||||||
self.lang, 50, None))
|
|
||||||
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(AccountingNone, 50))
|
||||||
self.lang, AccountingNone, 50))
|
|
||||||
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(None, 50))
|
||||||
self.lang, None, 50))
|
|
||||||
|
|
||||||
def test_compare_num_diff(self):
|
def test_compare_num_diff(self):
|
||||||
self.kpi.compare_method = 'diff'
|
self.kpi.compare_method = 'diff'
|
||||||
self.assertEquals((25, u'+25'),
|
self.assertEquals((25, u'+25'),
|
||||||
self.kpi.compare_and_render(self.lang, 75, 50))
|
self._compare_and_render(75, 50))
|
||||||
self.assertEquals((-25, u'\u201125'),
|
self.assertEquals((-25, u'\u201125'),
|
||||||
self.kpi.compare_and_render(self.lang, 25, 50))
|
self._compare_and_render(25, 50))
|
||||||
self.kpi.suffix = u'€'
|
self.style.suffix_inherit = False
|
||||||
|
self.style.suffix = u'€'
|
||||||
self.assertEquals((-25, u'\u201125\xa0€'),
|
self.assertEquals((-25, u'\u201125\xa0€'),
|
||||||
self.kpi.compare_and_render(self.lang, 25, 50))
|
self._compare_and_render(25, 50))
|
||||||
self.kpi.suffix = u''
|
self.style.suffix = u''
|
||||||
self.assertEquals((50.0, u'+50'),
|
self.assertEquals((50.0, u'+50'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(50, AccountingNone))
|
||||||
self.lang, 50, AccountingNone))
|
|
||||||
self.assertEquals((50.0, u'+50'),
|
self.assertEquals((50.0, u'+50'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(50, None))
|
||||||
self.lang, 50, None))
|
|
||||||
self.assertEquals((-50.0, u'\u201150'),
|
self.assertEquals((-50.0, u'\u201150'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(AccountingNone, 50))
|
||||||
self.lang, AccountingNone, 50))
|
|
||||||
self.assertEquals((-50.0, u'\u201150'),
|
self.assertEquals((-50.0, u'\u201150'),
|
||||||
self.kpi.compare_and_render(
|
self._compare_and_render(None, 50))
|
||||||
self.lang, None, 50))
|
|
||||||
|
|
||||||
def test_compare_pct(self):
|
def test_compare_pct(self):
|
||||||
self.kpi.type = 'pct'
|
self.kpi.type = 'pct'
|
||||||
self.assertEquals((0.25, u'+25\xa0pp'),
|
self.assertEquals((0.25, u'+25\xa0pp'),
|
||||||
self.kpi.compare_and_render(self.lang, 0.75, 0.50))
|
self._compare_and_render(0.75, 0.50))
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="mis_report_style_view_tree">
|
|
||||||
<field name="name">mis.report.style.view.tree</field>
|
|
||||||
<field name="model">mis.report.style</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="MIS Report Styles">
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="color"/>
|
|
||||||
<field name="background_color"/>
|
|
||||||
<field name="font_style"/>
|
|
||||||
<field name="font_weight"/>
|
|
||||||
<field name="font_size"/>
|
|
||||||
<field name="indent_level"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="mis_report_style_view_form" model="ir.ui.view">
|
|
||||||
<field name="name">mis.report.style.view.form</field>
|
|
||||||
<field name="model">mis.report.style</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="MIS Report Style" version="7.0">
|
|
||||||
<sheet>
|
|
||||||
<group string="Style" col="2">
|
|
||||||
<field name="name" />
|
|
||||||
</group>
|
|
||||||
<group string="Color" col="2">
|
|
||||||
<field name="color" />
|
|
||||||
<field name="background_color" />
|
|
||||||
</group>
|
|
||||||
<group string="Font" col="2">
|
|
||||||
<field name="font_style" />
|
|
||||||
<field name="font_weight" />
|
|
||||||
<field name="font_size" />
|
|
||||||
</group>
|
|
||||||
<group string="Indent">
|
|
||||||
<field name="indent_level" />
|
|
||||||
</group>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.actions.act_window" id="mis_report_style_view_action">
|
|
||||||
<field name="name">MIS Report Styles</field>
|
|
||||||
<field name="view_id" ref="mis_report_style_view_tree"/>
|
|
||||||
<field name="res_model">mis.report.style</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">tree,form</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<menuitem id="mis_report_style_view_menu" parent="account.menu_account_reports" name="MIS Report Styles" action="mis_report_style_view_action" sequence="22"/>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
|
@ -22,6 +22,7 @@
|
||||||
<group col="2">
|
<group col="2">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="description"/>
|
<field name="description"/>
|
||||||
|
<field name="style_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Sub KPI's">
|
<group string="Sub KPI's">
|
||||||
<field name="subkpi_ids" nolabel="1" colspan="2">
|
<field name="subkpi_ids" nolabel="1" colspan="2">
|
||||||
|
@ -51,14 +52,10 @@
|
||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="description"/>
|
<field name="description"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="type"/>
|
||||||
|
<field name="compare_method" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
||||||
<field name="multi"/>
|
<field name="multi"/>
|
||||||
<field name="expression"/>
|
<field name="expression"/>
|
||||||
<field name="type"/>
|
|
||||||
<field name="dp" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
|
||||||
<field name="divider" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
|
||||||
<field name="prefix"/>
|
|
||||||
<field name="suffix"/>
|
|
||||||
<field name="compare_method" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
|
@ -103,15 +100,8 @@
|
||||||
<field name="description"/>
|
<field name="description"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="type"/>
|
<field name="type"/>
|
||||||
<field name="dp"
|
<field name="compare_method"/>
|
||||||
attrs="{'invisible': [('type', '=', 'str')]}"/>
|
<field name="style_id"/>
|
||||||
<field name="divider"
|
|
||||||
attrs="{'invisible': [('type', '=', 'str')]}"/>
|
|
||||||
<field name="compare_method"
|
|
||||||
attrs="{'invisible': [('type', '=', 'str')]}"/>
|
|
||||||
<field name="prefix"/>
|
|
||||||
<field name="suffix"/>
|
|
||||||
<field name="style"/>
|
|
||||||
<field name="style_expression"/>
|
<field name="style_expression"/>
|
||||||
<!--<field name="sequence" />-->
|
<!--<field name="sequence" />-->
|
||||||
</group>
|
</group>
|
||||||
|
@ -131,7 +121,7 @@
|
||||||
</group>
|
</group>
|
||||||
<group col="4" string="Auto expand">
|
<group col="4" string="Auto expand">
|
||||||
<field name="auto_expand_accounts"/>
|
<field name="auto_expand_accounts"/>
|
||||||
<field name="auto_expand_accounts_style"
|
<field name="auto_expand_accounts_style_id"
|
||||||
attrs="{'invisible': [('auto_expand_accounts', '!=', True)]}"/>
|
attrs="{'invisible': [('auto_expand_accounts', '!=', True)]}"/>
|
||||||
</group>
|
</group>
|
||||||
<group col="2" string="Legend (for kpi expressions)">
|
<group col="2" string="Legend (for kpi expressions)">
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="mis_report_style_view_tree">
|
||||||
|
<field name="name">mis.report.style.view.tree</field>
|
||||||
|
<field name="model">mis.report.style</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="MIS Report Styles">
|
||||||
|
<field name="name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mis_report_style_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">mis.report.style.view.form</field>
|
||||||
|
<field name="model">mis.report.style</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="MIS Report Style" version="7.0">
|
||||||
|
<sheet>
|
||||||
|
<group string="Style" col="2">
|
||||||
|
<field name="name" />
|
||||||
|
</group>
|
||||||
|
<group string="Number" col="4">
|
||||||
|
<field name="dp_inherit" string="Rounding inherit"/>
|
||||||
|
<field name="dp"
|
||||||
|
attrs="{'invisible': [('dp_inherit', '=', True)]}"/>
|
||||||
|
<field name="divider_inherit" string="Factor inherit"/>
|
||||||
|
<field name="divider"
|
||||||
|
attrs="{'invisible': [('divider_inherit', '=', True)]}"/>
|
||||||
|
<field name="prefix_inherit"/>
|
||||||
|
<field name="prefix"
|
||||||
|
attrs="{'invisible': [('prefix_inherit', '=', True)]}"/>
|
||||||
|
<field name="suffix_inherit"/>
|
||||||
|
<field name="suffix"
|
||||||
|
attrs="{'invisible': [('suffix_inherit', '=', True)]}"/>
|
||||||
|
</group>
|
||||||
|
<group string="Color" col="4">
|
||||||
|
<field name="color_inherit" />
|
||||||
|
<field name="color"
|
||||||
|
attrs="{'invisible': [('color_inherit', '=', True)]}"
|
||||||
|
widget="color" />
|
||||||
|
<field name="background_color_inherit" />
|
||||||
|
<field name="background_color"
|
||||||
|
attrs="{'invisible': [('background_color_inherit', '=', True)]}"
|
||||||
|
widget="color" />
|
||||||
|
</group>
|
||||||
|
<group string="Font" col="4">
|
||||||
|
<field name="font_style_inherit" />
|
||||||
|
<field name="font_style"
|
||||||
|
attrs="{'invisible': [('font_style_inherit', '=', True)]}" />
|
||||||
|
<field name="font_weight_inherit" />
|
||||||
|
<field name="font_weight"
|
||||||
|
attrs="{'invisible': [('font_weight_inherit', '=', True)]}" />
|
||||||
|
<field name="font_size_inherit" />
|
||||||
|
<field name="font_size"
|
||||||
|
attrs="{'invisible': [('font_size_inherit', '=', True)]}" />
|
||||||
|
</group>
|
||||||
|
<group string="Indent" col="4">
|
||||||
|
<field name="indent_level_inherit" />
|
||||||
|
<field name="indent_level"
|
||||||
|
attrs="{'invisible': [('indent_level_inherit', '=', True)]}" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.actions.act_window" id="mis_report_style_view_action">
|
||||||
|
<field name="name">MIS Report Styles</field>
|
||||||
|
<field name="view_id" ref="mis_report_style_view_tree"/>
|
||||||
|
<field name="res_model">mis.report.style</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="mis_report_style_view_menu" parent="account.menu_account_reports" name="MIS Report Styles" action="mis_report_style_view_action" sequence="22"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
Loading…
Reference in New Issue