You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

280 lines
9.2 KiB

# -*- coding: utf-8 -*-
# © 2016 Therp BV (<http://therp.nl>)
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from .accounting_none import AccountingNone
from .data_error import DataError
class PropertyDict(dict):
def __getattr__(self, name):
return self.get(name)
def copy(self): # pylint: disable=copy-wo-api-one,method-required-super
return PropertyDict(self)
PROPS = [
'color',
'background_color',
'font_style',
'font_weight',
'font_size',
'indent_level',
'prefix',
'suffix',
'dp',
'divider',
]
TYPE_NUM = 'num'
TYPE_PCT = 'pct'
TYPE_STR = 'str'
CMP_DIFF = 'diff'
CMP_PCT = 'pct'
CMP_NONE = 'none'
class MisReportKpiStyle(models.Model):
_name = 'mis.report.style'
@api.constrains('indent_level')
def check_positive_val(self):
for record in self:
if record.indent_level < 0:
raise UserError(_('Indent level must be greater than '
'or equal to 0'))
_font_style_selection = [
('normal', 'Normal'),
('italic', 'Italic'),
]
_font_weight_selection = [
('nornal', 'Normal'),
('bold', 'Bold'),
]
_font_size_selection = [
('medium', 'medium'),
('xx-small', 'xx-small'),
('x-small', 'x-small'),
('small', 'small'),
('large', 'large'),
('x-large', 'x-large'),
('xx-large', 'xx-large'),
]
_font_size_to_xlsx_size = {
'medium': 11,
'xx-small': 5,
'x-small': 7,
'small': 9,
'large': 13,
'x-large': 15,
'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)',
default='#000000',
)
background_color_inherit = fields.Boolean(default=True)
background_color = fields.Char(
help='Background color in valid RGB code (from #000000 to #FFFFFF)',
default='#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.model
def merge(self, styles):
""" Merge several styles, giving priority to the last.
Returns a PropertyDict of style properties.
"""
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.model
def render(self, lang, style_props, type, value):
if type == 'num':
return self.render_num(lang, value, style_props.divider,
style_props.dp,
style_props.prefix, style_props.suffix)
elif type == 'pct':
return self.render_pct(lang, value, style_props.dp)
else:
return self.render_str(lang, value)
@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.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 compare_and_render(self, lang, style_props, type, compare_method,
value, base_value,
average_value=1, average_base_value=1):
delta = AccountingNone
style_r = style_props.copy()
if isinstance(value, DataError) or isinstance(base_value, DataError):
return AccountingNone, '', style_r
if value is None:
value = AccountingNone
if base_value is None:
base_value = AccountingNone
if type == TYPE_PCT:
delta = value - base_value
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 type == TYPE_NUM:
if value and average_value:
value = value / float(average_value)
if base_value and average_base_value:
base_value = base_value / float(average_base_value)
if compare_method == CMP_DIFF:
delta = value - base_value
if delta and round(delta, style_props.dp or 0) != 0:
pass
else:
delta = AccountingNone
elif compare_method == CMP_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 = self.render_num(
lang, delta,
style_r.divider, style_r.dp,
style_r.prefix, style_r.suffix,
sign='+')
return delta, delta_r, style_r
else:
return AccountingNone, '', style_r
@api.model
def to_xlsx_style(self, props, no_indent=False):
num_format = '0'
if props.dp:
num_format += '.'
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),
('num_format', num_format),
]
if props.indent_level is not None and not no_indent:
xlsx_attributes.append(
('indent', props.indent_level))
return dict([a for a in xlsx_attributes
if a[1] is not None])
@api.model
def to_css_style(self, props, no_indent=False):
css_attributes = [
('font-style', props.font_style),
('font-weight', props.font_weight),
('font-size', props.font_size),
('color', props.color),
('background-color', props.background_color),
]
if props.indent_level is not None and not no_indent:
css_attributes.append(
('text-indent', '{}em'.format(props.indent_level)))
return '; '.join(['%s: %s' % a for a in css_attributes
if a[1] is not None]) or None