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.

273 lines
8.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2016 Therp BV (<http://therp.nl>)
  3. # © 2016 ACSONE SA/NV (<http://acsone.eu>)
  4. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
  5. from openerp import api, fields, models, _
  6. from openerp.exceptions import UserError
  7. from .accounting_none import AccountingNone
  8. class PropertyDict(dict):
  9. def __getattr__(self, name):
  10. return self.get(name)
  11. def copy(self):
  12. return PropertyDict(self)
  13. PROPS = [
  14. 'color',
  15. 'background_color',
  16. 'font_style',
  17. 'font_weight',
  18. 'font_size',
  19. 'indent_level',
  20. 'prefix',
  21. 'suffix',
  22. 'dp',
  23. 'divider',
  24. ]
  25. TYPE_NUM = 'num'
  26. TYPE_PCT = 'pct'
  27. TYPE_STR = 'str'
  28. CMP_DIFF = 'diff'
  29. CMP_PCT = 'pct'
  30. CMP_NONE = 'none'
  31. class MisReportKpiStyle(models.Model):
  32. _name = 'mis.report.style'
  33. @api.one
  34. @api.constrains('indent_level')
  35. def check_positive_val(self):
  36. if self.indent_level < 0:
  37. raise UserError(_('Indent level must be greater than '
  38. 'or equal to 0'))
  39. _font_style_selection = [
  40. ('normal', 'Normal'),
  41. ('italic', 'Italic'),
  42. ]
  43. _font_weight_selection = [
  44. ('nornal', 'Normal'),
  45. ('bold', 'Bold'),
  46. ]
  47. _font_size_selection = [
  48. ('medium', 'medium'),
  49. ('xx-small', 'xx-small'),
  50. ('x-small', 'x-small'),
  51. ('small', 'small'),
  52. ('large', 'large'),
  53. ('x-large', 'x-large'),
  54. ('xx-large', 'xx-large'),
  55. ]
  56. _font_size_to_xlsx_size = {
  57. 'medium': 11,
  58. 'xx-small': 5,
  59. 'x-small': 7,
  60. 'small': 9,
  61. 'large': 13,
  62. 'x-large': 15,
  63. 'xx-large': 17
  64. }
  65. # style name
  66. # TODO enforce uniqueness
  67. name = fields.Char(string='Style name', required=True)
  68. # color
  69. color_inherit = fields.Boolean(default=True)
  70. color = fields.Char(
  71. string='Text color',
  72. help='Text color in valid RGB code (from #000000 to #FFFFFF)',
  73. default='#000000',
  74. )
  75. background_color_inherit = fields.Boolean(default=True)
  76. background_color = fields.Char(
  77. help='Background color in valid RGB code (from #000000 to #FFFFFF)',
  78. default='#FFFFFF',
  79. )
  80. # font
  81. font_style_inherit = fields.Boolean(default=True)
  82. font_style = fields.Selection(
  83. selection=_font_style_selection,
  84. )
  85. font_weight_inherit = fields.Boolean(default=True)
  86. font_weight = fields.Selection(
  87. selection=_font_weight_selection
  88. )
  89. font_size_inherit = fields.Boolean(default=True)
  90. font_size = fields.Selection(
  91. selection=_font_size_selection
  92. )
  93. # indent
  94. indent_level_inherit = fields.Boolean(default=True)
  95. indent_level = fields.Integer()
  96. # number format
  97. prefix_inherit = fields.Boolean(default=True)
  98. prefix = fields.Char(size=16, string='Prefix')
  99. suffix_inherit = fields.Boolean(default=True)
  100. suffix = fields.Char(size=16, string='Suffix')
  101. dp_inherit = fields.Boolean(default=True)
  102. dp = fields.Integer(string='Rounding', default=0)
  103. divider_inherit = fields.Boolean(default=True)
  104. divider = fields.Selection([('1e-6', _('µ')),
  105. ('1e-3', _('m')),
  106. ('1', _('1')),
  107. ('1e3', _('k')),
  108. ('1e6', _('M'))],
  109. string='Factor',
  110. default='1')
  111. @api.model
  112. def merge(self, styles):
  113. """ Merge several styles, giving priority to the last.
  114. Returns a PropertyDict of style properties.
  115. """
  116. r = PropertyDict()
  117. for style in styles:
  118. if not style:
  119. continue
  120. if isinstance(style, dict):
  121. r.update(style)
  122. else:
  123. for prop in PROPS:
  124. inherit = getattr(style, prop + '_inherit', None)
  125. if inherit is None:
  126. value = getattr(style, prop)
  127. if value:
  128. r[prop] = value
  129. elif not inherit:
  130. value = getattr(style, prop)
  131. r[prop] = value
  132. return r
  133. @api.model
  134. def render(self, lang, style_props, type, value):
  135. if type == 'num':
  136. return self.render_num(lang, value, style_props.divider,
  137. style_props.dp,
  138. style_props.prefix, style_props.suffix)
  139. elif type == 'pct':
  140. return self.render_pct(lang, value, style_props.dp)
  141. else:
  142. return self.render_str(lang, value)
  143. @api.model
  144. def render_num(self, lang, value,
  145. divider=1.0, dp=0, prefix=None, suffix=None, sign='-'):
  146. # format number following user language
  147. if value is None or value is AccountingNone:
  148. return u''
  149. value = round(value / float(divider or 1), dp or 0) or 0
  150. r = lang.format('%%%s.%df' % (sign, dp or 0), value, grouping=True)
  151. r = r.replace('-', u'\N{NON-BREAKING HYPHEN}')
  152. if prefix:
  153. r = prefix + u'\N{NO-BREAK SPACE}' + r
  154. if suffix:
  155. r = r + u'\N{NO-BREAK SPACE}' + suffix
  156. return r
  157. @api.model
  158. def render_pct(self, lang, value, dp=1, sign='-'):
  159. return self.render_num(lang, value, divider=0.01,
  160. dp=dp, suffix='%', sign=sign)
  161. @api.model
  162. def render_str(self, lang, value):
  163. if value is None or value is AccountingNone:
  164. return u''
  165. return unicode(value)
  166. @api.model
  167. def compare_and_render(self, lang, style_props, type, compare_method,
  168. value, base_value,
  169. average_value=1, average_base_value=1):
  170. delta = AccountingNone
  171. style_r = style_props.copy()
  172. if value is None:
  173. value = AccountingNone
  174. if base_value is None:
  175. base_value = AccountingNone
  176. if type == TYPE_PCT:
  177. delta = value - base_value
  178. if delta and round(delta, (style_props.dp or 0) + 2) != 0:
  179. style_r.update(dict(
  180. divider=0.01, prefix='', suffix=_('pp')))
  181. else:
  182. delta = AccountingNone
  183. elif type == TYPE_NUM:
  184. if value and average_value:
  185. value = value / float(average_value)
  186. if base_value and average_base_value:
  187. base_value = base_value / float(average_base_value)
  188. if compare_method == CMP_DIFF:
  189. delta = value - base_value
  190. if delta and round(delta, style_props.dp or 0) != 0:
  191. pass
  192. else:
  193. delta = AccountingNone
  194. elif compare_method == CMP_PCT:
  195. if base_value and round(base_value, style_props.dp or 0) != 0:
  196. delta = (value - base_value) / abs(base_value)
  197. if delta and round(delta, 1) != 0:
  198. style_r.update(dict(
  199. divider=0.01, dp=1, prefix='', suffix='%'))
  200. else:
  201. delta = AccountingNone
  202. if delta is not AccountingNone:
  203. delta_r = self.render_num(
  204. lang, delta,
  205. style_r.divider, style_r.dp,
  206. style_r.prefix, style_r.suffix,
  207. sign='+')
  208. return delta, delta_r, style_r
  209. else:
  210. return AccountingNone, '', style_r
  211. @api.model
  212. def to_xlsx_style(self, props):
  213. num_format = '0'
  214. if props.dp:
  215. num_format += '.'
  216. num_format += '0' * props.dp
  217. if props.prefix:
  218. num_format = u'"{} "{}'.format(props.prefix, num_format)
  219. if props.suffix:
  220. num_format = u'{}" {}"'.format(num_format, props.suffix)
  221. xlsx_attributes = [
  222. ('italic', props.font_style == 'italic'),
  223. ('bold', props.font_weight == 'bold'),
  224. ('size', self._font_size_to_xlsx_size.get(props.font_size, 11)),
  225. ('font_color', props.color),
  226. ('bg_color', props.background_color),
  227. ('indent', props.indent_level),
  228. ('num_format', num_format),
  229. ]
  230. return dict([a for a in xlsx_attributes
  231. if a[1] is not None])
  232. @api.model
  233. def to_css_style(self, props):
  234. css_attributes = [
  235. ('font-style', props.font_style),
  236. ('font-weight', props.font_weight),
  237. ('font-size', props.font_size),
  238. ('color', props.color),
  239. ('background-color', props.background_color),
  240. ('indent-level', props.indent_level)
  241. ]
  242. return '; '.join(['%s: %s' % a for a in css_attributes
  243. if a[1] is not None]) or None