[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 (?)
|
||||
.. ~~~~~~~~~~
|
||||
..
|
||||
.. *
|
||||
..
|
||||
.. *
|
||||
|
||||
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
|
||||
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
|
||||
rounding, as the KPI rounding does not make sense in this case
|
||||
* [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
|
||||
the % are added by the user in the expressions
|
||||
* [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
|
||||
flexible alternative to fiscal periods
|
||||
* v9 migration: fiscal periods are removed, account charts are removed,
|
||||
|
|
|
@ -40,6 +40,14 @@ class KpiMatrixRow(object):
|
|||
self.account_id = account_id
|
||||
self.comment = ''
|
||||
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
|
||||
def description(self):
|
||||
|
@ -48,13 +56,6 @@ class KpiMatrixRow(object):
|
|||
else:
|
||||
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
|
||||
def row_id(self):
|
||||
if not self.account_id:
|
||||
|
@ -134,13 +135,14 @@ class KpiMatrixCell(object):
|
|||
|
||||
def __init__(self, row, subcol,
|
||||
val, val_rendered, val_comment,
|
||||
style=None, drilldown_arg=None):
|
||||
style_props,
|
||||
drilldown_arg):
|
||||
self.row = row
|
||||
self.subcol = subcol
|
||||
self.val = val
|
||||
self.val_rendered = val_rendered
|
||||
self.val_comment = val_comment
|
||||
self.style = style
|
||||
self.style_props = style_props
|
||||
self.drilldown_arg = drilldown_arg
|
||||
|
||||
|
||||
|
@ -151,6 +153,7 @@ class KpiMatrix(object):
|
|||
lang_model = env['res.lang']
|
||||
lang_id = lang_model._lang_get(env.user.lang)
|
||||
self.lang = lang_model.browse(lang_id)
|
||||
self._style_model = env['mis.report.style']
|
||||
self._account_model = env['account.account']
|
||||
# data structures
|
||||
# { kpi: KpiMatrixRow }
|
||||
|
@ -224,7 +227,7 @@ class KpiMatrix(object):
|
|||
val_rendered = val.name
|
||||
val_comment = val.msg
|
||||
else:
|
||||
val_rendered = kpi.render(self.lang, val)
|
||||
val_rendered = kpi.render(self.lang, row.style_props, val)
|
||||
if subcol.subkpi:
|
||||
val_comment = u'{}.{} = {}'.format(
|
||||
row.kpi.name,
|
||||
|
@ -234,9 +237,9 @@ class KpiMatrix(object):
|
|||
val_comment = u'{} = {}'.format(
|
||||
row.kpi.name,
|
||||
row.kpi.expression)
|
||||
# TODO style
|
||||
# TODO FIXME style expression
|
||||
cell = KpiMatrixCell(row, subcol, val, val_rendered, val_comment,
|
||||
None, drilldown_arg)
|
||||
row.style_props, drilldown_arg)
|
||||
cell_tuple.append(cell)
|
||||
col._set_cell_tuple(row, cell_tuple)
|
||||
|
||||
|
@ -279,10 +282,11 @@ class KpiMatrix(object):
|
|||
base_vals,
|
||||
comparison_col.iter_subcols()):
|
||||
# TODO FIXME average factors
|
||||
delta, delta_r = row.kpi.compare_and_render(
|
||||
self.lang, val, base_val, 1, 1)
|
||||
delta, delta_r, style_r = row.kpi.compare_and_render(
|
||||
self.lang, row.style_props, val, base_val, 1, 1)
|
||||
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)
|
||||
self._comparison_cols[pos_period_key].append(comparison_col)
|
||||
|
||||
|
@ -355,11 +359,13 @@ class KpiMatrix(object):
|
|||
row.parent_row.row_id or None),
|
||||
'description': row.description,
|
||||
'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': []
|
||||
}
|
||||
for cell in row.iter_cells():
|
||||
if cell is None:
|
||||
# TODO use subcol style here
|
||||
row_data['cols'].append({})
|
||||
else:
|
||||
col_data = {
|
||||
|
@ -367,7 +373,8 @@ class KpiMatrix(object):
|
|||
if cell.val is not AccountingNone else None),
|
||||
'val_r': cell.val_rendered,
|
||||
'val_c': cell.val_comment,
|
||||
# TODO FIXME style
|
||||
'style': self._style_model.to_css_style(
|
||||
cell.style_props),
|
||||
}
|
||||
if 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):
|
||||
d = fields.Datetime.from_string(d) + datetime.timedelta(days=add_day)
|
||||
utc_tz = pytz.timezone('UTC')
|
||||
|
@ -427,13 +427,13 @@ class MisReportKpi(models.Model):
|
|||
inverse='_inverse_expression')
|
||||
expression_ids = fields.One2many('mis.report.kpi.expression', 'kpi_id')
|
||||
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",
|
||||
comodel_name="mis.report.style",
|
||||
required=False
|
||||
)
|
||||
style = fields.Many2one(
|
||||
string="Row style",
|
||||
style_id = fields.Many2one(
|
||||
string="Style",
|
||||
comodel_name="mis.report.style",
|
||||
required=False
|
||||
)
|
||||
|
@ -445,18 +445,8 @@ class MisReportKpi(models.Model):
|
|||
('pct', _('Percentage')),
|
||||
('str', _('String'))],
|
||||
required=True,
|
||||
string='Type',
|
||||
string='Value type',
|
||||
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')),
|
||||
('pct', _('Percentage')),
|
||||
('none', _('None'))],
|
||||
|
@ -544,57 +534,57 @@ class MisReportKpi(models.Model):
|
|||
def _onchange_type(self):
|
||||
if self.type == 'num':
|
||||
self.compare_method = 'pct'
|
||||
self.divider = '1'
|
||||
self.dp = 0
|
||||
elif self.type == 'pct':
|
||||
self.compare_method = 'diff'
|
||||
self.divider = '1'
|
||||
self.dp = 0
|
||||
elif self.type == 'str':
|
||||
self.compare_method = 'none'
|
||||
self.divider = ''
|
||||
self.dp = 0
|
||||
|
||||
def get_expression_for_subkpi(self, subkpi):
|
||||
for expression in self.expression_ids:
|
||||
if expression.subkpi_id == subkpi:
|
||||
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 """
|
||||
assert len(self) == 1
|
||||
if value is None or value is AccountingNone:
|
||||
return ''
|
||||
elif self.type == 'num':
|
||||
return self._render_num(lang, value, self.divider,
|
||||
self.dp, self.prefix, self.suffix)
|
||||
self.ensure_one()
|
||||
style_obj = self.env['mis.report.style']
|
||||
if self.type == 'num':
|
||||
return style_obj.render_num(lang, value, style_props.divider,
|
||||
style_props.dp,
|
||||
style_props.prefix, style_props.suffix)
|
||||
elif self.type == 'pct':
|
||||
return self._render_num(lang, value, 0.01,
|
||||
self.dp, '', '%')
|
||||
return style_obj.render_pct(lang, value, style_props.dp)
|
||||
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):
|
||||
""" 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.
|
||||
"""
|
||||
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:
|
||||
value = AccountingNone
|
||||
if base_value is None:
|
||||
base_value = AccountingNone
|
||||
if self.type == 'pct':
|
||||
delta = value - base_value
|
||||
if delta and round(delta, self.dp + 2) != 0:
|
||||
return delta, self._render_num(
|
||||
lang,
|
||||
delta,
|
||||
0.01, self.dp, '', _('pp'),
|
||||
sign='+')
|
||||
if delta and round(delta, (style_props.dp or 0) + 2) != 0:
|
||||
style_r.update(dict(
|
||||
divider=0.01, prefix='', suffix=_('pp')))
|
||||
else:
|
||||
delta = AccountingNone
|
||||
elif self.type == 'num':
|
||||
if value and average_value:
|
||||
value = value / float(average_value)
|
||||
|
@ -602,43 +592,27 @@ class MisReportKpi(models.Model):
|
|||
base_value = base_value / float(average_base_value)
|
||||
if self.compare_method == 'diff':
|
||||
delta = value - base_value
|
||||
if delta and round(delta, self.dp) != 0:
|
||||
return delta, self._render_num(
|
||||
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='+')
|
||||
if delta and round(delta, style_props.dp or 0) != 0:
|
||||
pass
|
||||
else:
|
||||
return AccountingNone, ''
|
||||
return 0, ''
|
||||
|
||||
def _render_num(self, lang, value, divider,
|
||||
dp, prefix, suffix, sign='-'):
|
||||
# format number following user language
|
||||
value = round(value / float(divider or 1), dp) or 0
|
||||
value = lang.format(
|
||||
'%%%s.%df' % (sign, dp),
|
||||
value,
|
||||
grouping=True)
|
||||
value = value.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
||||
if prefix:
|
||||
prefix = prefix + u'\N{NO-BREAK SPACE}'
|
||||
delta = AccountingNone
|
||||
elif self.compare_method == 'pct':
|
||||
if base_value and round(base_value, style_props.dp or 0) != 0:
|
||||
delta = (value - base_value) / abs(base_value)
|
||||
if delta and round(delta, 1) != 0:
|
||||
style_r.update(dict(
|
||||
divider=0.01, dp=1, prefix='', suffix='%'))
|
||||
else:
|
||||
delta = AccountingNone
|
||||
if delta is not AccountingNone:
|
||||
delta_r = style_obj.render_num(
|
||||
lang, delta,
|
||||
style_r.divider, style_r.dp,
|
||||
style_r.prefix, style_r.suffix,
|
||||
sign='+')
|
||||
return delta, delta_r, style_r
|
||||
else:
|
||||
prefix = ''
|
||||
if suffix:
|
||||
suffix = u'\N{NO-BREAK SPACE}' + suffix
|
||||
else:
|
||||
suffix = ''
|
||||
return prefix + value + suffix
|
||||
return AccountingNone, '', style_r
|
||||
|
||||
|
||||
class MisReportSubkpi(models.Model):
|
||||
|
@ -773,6 +747,8 @@ class MisReport(models.Model):
|
|||
string='Name', translate=True)
|
||||
description = fields.Char(required=False,
|
||||
string='Description', translate=True)
|
||||
style_id = fields.Many2one(string="Style",
|
||||
comodel_name="mis.report.style")
|
||||
query_ids = fields.One2many('mis.report.query', 'report_id',
|
||||
string='Queries',
|
||||
copy=True)
|
||||
|
|
|
@ -3,16 +3,45 @@
|
|||
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from openerp import api, fields, models
|
||||
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):
|
||||
|
||||
_name = 'mis.report.style'
|
||||
|
||||
@api.depends('indent_level')
|
||||
@api.one
|
||||
@api.constrains('indent_level')
|
||||
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 = [
|
||||
('normal', 'Normal'),
|
||||
|
@ -25,7 +54,7 @@ class MisReportKpiStyle(models.Model):
|
|||
]
|
||||
|
||||
_font_size_selection = [
|
||||
('medium', ''),
|
||||
('medium', 'medium'),
|
||||
('xx-small', 'xx-small'),
|
||||
('x-small', 'x-small'),
|
||||
('small', 'small'),
|
||||
|
@ -34,7 +63,7 @@ class MisReportKpiStyle(models.Model):
|
|||
('xx-large', 'xx-large'),
|
||||
]
|
||||
|
||||
_font_size_to_size = {
|
||||
_font_size_to_xlsx_size = {
|
||||
'medium': 11,
|
||||
'xx-small': 5,
|
||||
'x-small': 7,
|
||||
|
@ -44,57 +73,129 @@ class MisReportKpiStyle(models.Model):
|
|||
'xx-large': 17
|
||||
}
|
||||
|
||||
# style name
|
||||
# TODO enforce uniqueness
|
||||
name = fields.Char(string='Style name', required=True)
|
||||
|
||||
# color
|
||||
color_inherit = fields.Boolean(default=True)
|
||||
color = fields.Char(
|
||||
string='Text color',
|
||||
help='Text color in valid RGB code (from #000000 to #FFFFFF)',
|
||||
)
|
||||
background_color_inherit = fields.Boolean(default=True)
|
||||
background_color = fields.Char(
|
||||
help='Background color in valid RGB code (from #000000 to #FFFFFF)'
|
||||
)
|
||||
# font
|
||||
font_style_inherit = fields.Boolean(default=True)
|
||||
font_style = fields.Selection(
|
||||
selection=_font_style_selection,
|
||||
)
|
||||
font_weight_inherit = fields.Boolean(default=True)
|
||||
font_weight = fields.Selection(
|
||||
selection=_font_weight_selection
|
||||
)
|
||||
font_size_inherit = fields.Boolean(default=True)
|
||||
font_size = fields.Selection(
|
||||
selection=_font_size_selection
|
||||
)
|
||||
# indent
|
||||
indent_level_inherit = fields.Boolean(default=True)
|
||||
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
|
||||
def font_size_to_size(self):
|
||||
self.ensure_one()
|
||||
return self._font_size_to_size.get(self.font_size, 11)
|
||||
@api.model
|
||||
def merge(self, styles):
|
||||
r = PropertyDict()
|
||||
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
|
||||
def to_xlsx_format_properties(self):
|
||||
self.ensure_one()
|
||||
props = {
|
||||
'italic': self.font_style == 'italic',
|
||||
'bold': self.font_weight == 'bold',
|
||||
'font_color': self.color,
|
||||
'bg_color': self.background_color,
|
||||
'indent': self.indent_level,
|
||||
'size': self.font_size_to_size()
|
||||
}
|
||||
return props
|
||||
@api.model
|
||||
def render_num(self, lang, value,
|
||||
divider=1.0, dp=0, prefix=None, suffix=None, sign='-'):
|
||||
# format number following user language
|
||||
if value is None or value is AccountingNone:
|
||||
return u''
|
||||
value = round(value / float(divider or 1), dp or 0) or 0
|
||||
r = lang.format('%%%s.%df' % (sign, dp or 0), value, grouping=True)
|
||||
r = r.replace('-', u'\N{NON-BREAKING HYPHEN}')
|
||||
if prefix:
|
||||
r = prefix + u'\N{NO-BREAK SPACE}' + r
|
||||
if suffix:
|
||||
r = r + u'\N{NO-BREAK SPACE}' + suffix
|
||||
return r
|
||||
|
||||
@api.multi
|
||||
def to_css_style(self):
|
||||
self.ensure_one()
|
||||
@api.model
|
||||
def render_pct(self, lang, value, dp=1, sign='-'):
|
||||
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 = [
|
||||
('font-style', self.font_style),
|
||||
('font-weight', self.font_weight),
|
||||
('font-size', self.font_size),
|
||||
('color', self.color),
|
||||
('background-color', self.background_color),
|
||||
('indent-level', self.indent_level)
|
||||
('font-style', props.font_style),
|
||||
('font-weight', props.font_weight),
|
||||
('font-size', props.font_size),
|
||||
('color', props.color),
|
||||
('background-color', props.background_color),
|
||||
('indent-level', props.indent_level)
|
||||
]
|
||||
|
||||
css_list = [
|
||||
'%s:%s' % x for x in css_attributes if x[1]
|
||||
]
|
||||
return ';'.join(css_item for css_item in css_list)
|
||||
return '; '.join(['%s: %s' % a for a in css_attributes
|
||||
if a[1] is not None]) or None
|
||||
|
|
|
@ -17,11 +17,18 @@
|
|||
</xpath>
|
||||
</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">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="report.internal_layout">
|
||||
<t t-set="matrix" t-value="o._compute_matrix()"/>
|
||||
<t t-set="style_obj" t-value="o.env['mis.report.style']"/>
|
||||
<div class="page">
|
||||
<h2><span t-field="o.name" /> - <span t-field="o.company_id.name" /></h2>
|
||||
<div class="mis_table">
|
||||
|
@ -57,7 +64,7 @@
|
|||
</div>
|
||||
<div class="mis_tbody">
|
||||
<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-if="row.comment">
|
||||
<br/>
|
||||
|
@ -65,16 +72,9 @@
|
|||
</t>
|
||||
</div>
|
||||
<t t-foreach="row.iter_cells()" t-as="cell">
|
||||
<t t-if="cell">
|
||||
<div t-att-style="cell.row.style and cell.row.style.to_css_style() or ''" class="mis_cell mis_amount">
|
||||
<div t-att-style="cell.style and cell.style.to_css_style() or ''">
|
||||
<t t-esc="cell.val_rendered"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="not cell">
|
||||
<div class="mis_cell mis_amount"></div>
|
||||
</t>
|
||||
<div t-att-style="cell and style_obj.to_css_style(cell.style_props) or ''" class="mis_cell mis_amount">
|
||||
<t t-esc="cell and cell.val_rendered or ''"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ _logger = logging.getLogger(__name__)
|
|||
try:
|
||||
from openerp.addons.report_xlsx.report.report_xlsx import ReportXlsx
|
||||
except ImportError:
|
||||
_logger.debug("report_xslx not installed, Excel export non functional")
|
||||
_logger.debug("report_xlsx not installed, Excel export non functional")
|
||||
|
||||
class ReportXslx:
|
||||
pass
|
||||
|
@ -33,23 +33,11 @@ class MisBuilderXslx(ReportXlsx):
|
|||
super(MisBuilderXslx, self).__init__(
|
||||
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):
|
||||
|
||||
# get the computed result of the report
|
||||
matrix = objects._compute_matrix()
|
||||
style_obj = self.env['mis.report.style']
|
||||
|
||||
# create worksheet
|
||||
report_name = '{} - {}'.format(
|
||||
|
@ -107,10 +95,7 @@ class MisBuilderXslx(ReportXlsx):
|
|||
|
||||
# rows
|
||||
for row in matrix.iter_rows():
|
||||
if row.style:
|
||||
row_xlsx_style = row.style.to_xlsx_format_properties()
|
||||
else:
|
||||
row_xlsx_style = {}
|
||||
row_xlsx_style = style_obj.to_xlsx_style(row.style_props)
|
||||
row_format = workbook.add_format(row_xlsx_style)
|
||||
col_pos = 0
|
||||
sheet.write(row_pos, col_pos, row.description, row_format)
|
||||
|
@ -118,19 +103,13 @@ class MisBuilderXslx(ReportXlsx):
|
|||
for cell in row.iter_cells():
|
||||
col_pos += 1
|
||||
if not cell or cell.val is AccountingNone:
|
||||
# TODO col/subcol format
|
||||
sheet.write(row_pos, col_pos, '', row_format)
|
||||
continue
|
||||
kpi_xlsx_style = dict(row_xlsx_style)
|
||||
kpi_xlsx_style.update({
|
||||
'num_format': self.make_number_format(row.kpi),
|
||||
'align': 'right'
|
||||
})
|
||||
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
|
||||
cell_xlsx_style = style_obj.to_xlsx_style(cell.style_props)
|
||||
cell_xlsx_style['align'] = 'right'
|
||||
kpi_format = workbook.add_format(cell_xlsx_style)
|
||||
val = cell.val / float(cell.style_props.get('divider', 1))
|
||||
sheet.write(row_pos, col_pos, val, kpi_format)
|
||||
col_width[col_pos] = max(col_width[col_pos],
|
||||
len(cell.val_rendered or ''))
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"id","compare_method","description","expression","divider","name","dp","sequence","type","suffix"
|
||||
"mis_report_kpi_test","Percentage","total test","len(test)","","total_test","","1","Numeric",""
|
||||
"id","description","expression","name"
|
||||
"mis_report_kpi_test","total test","len(test)","total_test"
|
||||
|
|
|
|
@ -23,6 +23,7 @@ class TestFetchQuery(common.TransactionCase):
|
|||
'cols': [{'val': 0,
|
||||
'val_r': u'0',
|
||||
'val_c': u'total_test = len(test)',
|
||||
'style': None,
|
||||
}]
|
||||
}],
|
||||
'header':
|
||||
|
|
|
@ -11,132 +11,147 @@ class TestRendering(common.TransactionCase):
|
|||
|
||||
def setUp(self):
|
||||
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',
|
||||
description='Test KPI',
|
||||
description='test kpi',
|
||||
type='num',
|
||||
dp=0,
|
||||
style_id=self.style.id,
|
||||
))
|
||||
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):
|
||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1))
|
||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1.1))
|
||||
self.assertEquals(u'2', self.kpi.render(self.lang, 1.6))
|
||||
self.kpi.dp = 2
|
||||
self.assertEquals(u'1.00', self.kpi.render(self.lang, 1))
|
||||
self.assertEquals(u'1.10', self.kpi.render(self.lang, 1.1))
|
||||
self.assertEquals(u'1.60', self.kpi.render(self.lang, 1.6))
|
||||
self.assertEquals(u'1.61', self.kpi.render(self.lang, 1.606))
|
||||
self.assertEquals(u'12,345.67', self.kpi.render(self.lang, 12345.67))
|
||||
self.assertEquals(u'1', self._render(1))
|
||||
self.assertEquals(u'1', self._render(1.1))
|
||||
self.assertEquals(u'2', self._render(1.6))
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.assertEquals(u'1.00', self._render(1))
|
||||
self.assertEquals(u'1.10', self._render(1.1))
|
||||
self.assertEquals(u'1.60', self._render(1.6))
|
||||
self.assertEquals(u'1.61', self._render(1.606))
|
||||
self.assertEquals(u'12,345.67', self._render(12345.67))
|
||||
|
||||
def test_render_negative(self):
|
||||
# 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):
|
||||
self.assertEquals(u'0', self.kpi.render(self.lang, 0))
|
||||
self.assertEquals(u'', self.kpi.render(self.lang, None))
|
||||
self.assertEquals(u'', self.kpi.render(self.lang, AccountingNone))
|
||||
self.assertEquals(u'0', self._render(0))
|
||||
self.assertEquals(u'', self._render(None))
|
||||
self.assertEquals(u'', self._render(AccountingNone))
|
||||
|
||||
def test_render_suffix(self):
|
||||
self.kpi.suffix = u'€'
|
||||
self.assertEquals(u'1\xa0€', self.kpi.render(self.lang, 1))
|
||||
self.kpi.suffix = u'k€'
|
||||
self.kpi.divider = '1e3'
|
||||
self.assertEquals(u'1\xa0k€', self.kpi.render(self.lang, 1000))
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = u'€'
|
||||
self.assertEquals(u'1\xa0€', self._render(1))
|
||||
self.style.suffix = u'k€'
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = '1e3'
|
||||
self.assertEquals(u'1\xa0k€', self._render(1000))
|
||||
|
||||
def test_render_prefix(self):
|
||||
self.kpi.prefix = u'$'
|
||||
self.assertEquals(u'$\xa01', self.kpi.render(self.lang, 1))
|
||||
self.kpi.prefix = u'k$'
|
||||
self.kpi.divider = '1e3'
|
||||
self.assertEquals(u'k$\xa01', self.kpi.render(self.lang, 1000))
|
||||
self.style.prefix_inherit = False
|
||||
self.style.prefix = u'$'
|
||||
self.assertEquals(u'$\xa01', self._render(1))
|
||||
self.style.prefix = u'k$'
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = '1e3'
|
||||
self.assertEquals(u'k$\xa01', self._render(1000))
|
||||
|
||||
def test_render_divider(self):
|
||||
self.kpi.divider = '1e3'
|
||||
self.kpi.dp = 0
|
||||
self.assertEquals(u'1', self.kpi.render(self.lang, 1000))
|
||||
self.kpi.divider = '1e6'
|
||||
self.kpi.dp = 3
|
||||
self.assertEquals(u'0.001', self.kpi.render(self.lang, 1000))
|
||||
self.kpi.divider = '1e-3'
|
||||
self.kpi.dp = 0
|
||||
self.assertEquals(u'1,000', self.kpi.render(self.lang, 1))
|
||||
self.kpi.divider = '1e-6'
|
||||
self.kpi.dp = 0
|
||||
self.assertEquals(u'1,000,000', self.kpi.render(self.lang, 1))
|
||||
self.style.divider_inherit = False
|
||||
self.style.divider = '1e3'
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 0
|
||||
self.assertEquals(u'1', self._render(1000))
|
||||
self.style.divider = '1e6'
|
||||
self.style.dp = 3
|
||||
self.assertEquals(u'0.001', self._render(1000))
|
||||
self.style.divider = '1e-3'
|
||||
self.style.dp = 0
|
||||
self.assertEquals(u'1,000', self._render(1))
|
||||
self.style.divider = '1e-6'
|
||||
self.style.dp = 0
|
||||
self.assertEquals(u'1,000,000', self._render(1))
|
||||
|
||||
def test_render_pct(self):
|
||||
self.kpi.type = 'pct'
|
||||
self.assertEquals(u'100\xa0%', self.kpi.render(self.lang, 1))
|
||||
self.assertEquals(u'50\xa0%', self.kpi.render(self.lang, 0.5))
|
||||
self.kpi.dp = 2
|
||||
self.assertEquals(u'51.23\xa0%', self.kpi.render(self.lang, 0.5123))
|
||||
self.assertEquals(u'100\xa0%', self._render(1))
|
||||
self.assertEquals(u'50\xa0%', self._render(0.5))
|
||||
self.style.dp_inherit = False
|
||||
self.style.dp = 2
|
||||
self.assertEquals(u'51.23\xa0%', self._render(0.5123))
|
||||
|
||||
def test_render_string(self):
|
||||
self.kpi.type = 'str'
|
||||
self.assertEquals(u'', self.kpi.render(self.lang, ''))
|
||||
self.assertEquals(u'', self.kpi.render(self.lang, None))
|
||||
self.assertEquals(u'abcdé', self.kpi.render(self.lang, u'abcdé'))
|
||||
self.assertEquals(u'', self._render(''))
|
||||
self.assertEquals(u'', self._render(None))
|
||||
self.assertEquals(u'abcdé', self._render(u'abcdé'))
|
||||
|
||||
def test_compare_num_pct(self):
|
||||
self.assertEquals('pct', self.kpi.compare_method)
|
||||
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.kpi.compare_and_render(self.lang, 75, 50))
|
||||
self._compare_and_render(75, 50))
|
||||
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.kpi.compare_and_render(self.lang, 0, -50))
|
||||
self._compare_and_render(0, -50))
|
||||
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.kpi.compare_and_render(self.lang, 25, 50))
|
||||
self._compare_and_render(25, 50))
|
||||
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.kpi.compare_and_render(self.lang, -50, 50))
|
||||
self._compare_and_render(-50, 50))
|
||||
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.kpi.compare_and_render(
|
||||
self.lang, 50, AccountingNone))
|
||||
self._compare_and_render(50, AccountingNone))
|
||||
self.assertEquals((AccountingNone, u''),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, 50, None))
|
||||
self._compare_and_render(50, None))
|
||||
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, AccountingNone, 50))
|
||||
self._compare_and_render(AccountingNone, 50))
|
||||
self.assertEquals((-1.0, u'\u2011100.0\xa0%'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, None, 50))
|
||||
self._compare_and_render(None, 50))
|
||||
|
||||
def test_compare_num_diff(self):
|
||||
self.kpi.compare_method = 'diff'
|
||||
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.kpi.compare_and_render(self.lang, 25, 50))
|
||||
self.kpi.suffix = u'€'
|
||||
self._compare_and_render(25, 50))
|
||||
self.style.suffix_inherit = False
|
||||
self.style.suffix = u'€'
|
||||
self.assertEquals((-25, u'\u201125\xa0€'),
|
||||
self.kpi.compare_and_render(self.lang, 25, 50))
|
||||
self.kpi.suffix = u''
|
||||
self._compare_and_render(25, 50))
|
||||
self.style.suffix = u''
|
||||
self.assertEquals((50.0, u'+50'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, 50, AccountingNone))
|
||||
self._compare_and_render(50, AccountingNone))
|
||||
self.assertEquals((50.0, u'+50'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, 50, None))
|
||||
self._compare_and_render(50, None))
|
||||
self.assertEquals((-50.0, u'\u201150'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, AccountingNone, 50))
|
||||
self._compare_and_render(AccountingNone, 50))
|
||||
self.assertEquals((-50.0, u'\u201150'),
|
||||
self.kpi.compare_and_render(
|
||||
self.lang, None, 50))
|
||||
self._compare_and_render(None, 50))
|
||||
|
||||
def test_compare_pct(self):
|
||||
self.kpi.type = 'pct'
|
||||
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">
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="style_id"/>
|
||||
</group>
|
||||
<group string="Sub KPI's">
|
||||
<field name="subkpi_ids" nolabel="1" colspan="2">
|
||||
|
@ -51,14 +52,10 @@
|
|||
<field name="sequence" widget="handle"/>
|
||||
<field name="description"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="compare_method" attrs="{'invisible': [('type', '=', 'str')]}"/>
|
||||
<field name="multi"/>
|
||||
<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>
|
||||
</field>
|
||||
</group>
|
||||
|
@ -103,15 +100,8 @@
|
|||
<field name="description"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="dp"
|
||||
attrs="{'invisible': [('type', '=', 'str')]}"/>
|
||||
<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="compare_method"/>
|
||||
<field name="style_id"/>
|
||||
<field name="style_expression"/>
|
||||
<!--<field name="sequence" />-->
|
||||
</group>
|
||||
|
@ -131,7 +121,7 @@
|
|||
</group>
|
||||
<group col="4" string="Auto expand">
|
||||
<field name="auto_expand_accounts"/>
|
||||
<field name="auto_expand_accounts_style"
|
||||
<field name="auto_expand_accounts_style_id"
|
||||
attrs="{'invisible': [('auto_expand_accounts', '!=', True)]}"/>
|
||||
</group>
|
||||
<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