Browse Source

Merge pull request #169 from ThomasBinsfeld/8.0_add_AccountingNone

[IMP] mis_builder: display blank cells instead of 0.0 when there is no data
pull/177/head
Pedro M. Baeza 9 years ago
parent
commit
f69d436fb1
  1. 187
      mis_builder/models/accounting_none.py
  2. 6
      mis_builder/models/aep.py
  3. 46
      mis_builder/models/mis_builder.py

187
mis_builder/models/accounting_none.py

@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
# © 2016 Thomas Binsfeld
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
"""
Provides the AccountingNone singleton.
AccountingNone is a null value that dissolves in basic arithmetic operations,
as illustrated in the examples below. In comparisons, AccountingNone behaves
the same as zero.
>>> 1 + 1
2
>>> 1 + AccountingNone
1
>>> AccountingNone + 1
1
>>> AccountingNone + None
AccountingNone
>>> +AccountingNone
AccountingNone
>>> -AccountingNone
AccountingNone
>>> -(AccountingNone)
AccountingNone
>>> AccountingNone - 1
-1
>>> 1 - AccountingNone
1
>>> abs(AccountingNone)
AccountingNone
>>> AccountingNone - None
AccountingNone
>>> AccountingNone / 2
0.0
>>> 2 / AccountingNone
Traceback (most recent call last):
...
ZeroDivisionError
>>> AccountingNone / AccountingNone
AccountingNone
>>> AccountingNone // 2
0.0
>>> 2 // AccountingNone
Traceback (most recent call last):
...
ZeroDivisionError
>>> AccountingNone // AccountingNone
AccountingNone
>>> AccountingNone * 2
0.0
>>> 2 * AccountingNone
0.0
>>> AccountingNone * AccountingNone
AccountingNone
>>> AccountingNone * None
AccountingNone
>>> None * AccountingNone
AccountingNone
>>> str(AccountingNone)
''
>>> bool(AccountingNone)
False
>>> AccountingNone > 0
False
>>> AccountingNone < 0
False
>>> AccountingNone < 1
True
>>> AccountingNone > 1
False
>>> 0 < AccountingNone
False
>>> 0 > AccountingNone
False
>>> 1 < AccountingNone
False
>>> 1 > AccountingNone
True
>>> AccountingNone == 0
True
>>> AccountingNone == 0.0
True
>>> AccountingNone == None
True
"""
__all__ = ['AccountingNone']
class AccountingNoneType(object):
def __add__(self, other):
if other is None:
return AccountingNone
return other
__radd__ = __add__
def __sub__(self, other):
if other is None:
return AccountingNone
return -other
def __rsub__(self, other):
if other is None:
return AccountingNone
return other
def __iadd__(self, other):
if other is None:
return AccountingNone
return other
def __isub__(self, other):
if other is None:
return AccountingNone
return -other
def __abs__(self):
return self
def __pos__(self):
return self
def __neg__(self):
return self
def __div__(self, other):
if other is AccountingNone:
return AccountingNone
return 0.0
def __rdiv__(self, other):
raise ZeroDivisionError
def __floordiv__(self, other):
if other is AccountingNone:
return AccountingNone
return 0.0
def __rfloordiv__(self, other):
raise ZeroDivisionError
def __truediv__(self, other):
if other is AccountingNone:
return AccountingNone
return 0.0
def __rtruediv__(self, other):
raise ZeroDivisionError
def __mul__(self, other):
if other is None or other is AccountingNone:
return AccountingNone
return 0.0
__rmul__ = __mul__
def __repr__(self):
return 'AccountingNone'
def __str__(self):
return ''
def __nonzero__(self):
return False
def __bool__(self):
return False
def __eq__(self, other):
return other == 0 or other is None or other is AccountingNone
def __lt__(self, other):
return 0 < other
def __gt__(self, other):
return 0 > other
AccountingNone = AccountingNoneType()
if __name__ == '__main__':
import doctest
doctest.testmod()

6
mis_builder/models/aep.py

@ -9,6 +9,7 @@ from openerp.exceptions import Warning as UserError
from openerp.models import expression from openerp.models import expression
from openerp.tools.safe_eval import safe_eval from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _ from openerp.tools.translate import _
from .accounting_none import AccountingNone
MODE_VARIATION = 'p' MODE_VARIATION = 'p'
MODE_INITIAL = 'i' MODE_INITIAL = 'i'
@ -348,12 +349,13 @@ class AccountingExpressionProcessor(object):
field, mode, account_codes, domain = self._parse_match_object(mo) field, mode, account_codes, domain = self._parse_match_object(mo)
key = (domain, mode) key = (domain, mode)
account_ids_data = self._data[key] account_ids_data = self._data[key]
v = 0.0
v = AccountingNone
for account_code in account_codes: for account_code in account_codes:
account_ids = self._account_ids_by_code[account_code] account_ids = self._account_ids_by_code[account_code]
for account_id in account_ids: for account_id in account_ids:
debit, credit = \ debit, credit = \
account_ids_data.get(account_id, (0.0, 0.0))
account_ids_data.get(account_id,
(AccountingNone, AccountingNone))
if field == 'bal': if field == 'bal':
v += debit - credit v += debit - credit
elif field == 'deb': elif field == 'deb':

46
mis_builder/models/mis_builder.py

@ -16,6 +16,7 @@ from openerp.tools.safe_eval import safe_eval
from .aep import AccountingExpressionProcessor as AEP from .aep import AccountingExpressionProcessor as AEP
from .aggregate import _sum, _avg, _min, _max from .aggregate import _sum, _avg, _min, _max
from .accounting_none import AccountingNone
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -140,8 +141,8 @@ class MisReportKpi(models.Model):
def render(self, lang_id, value): def render(self, lang_id, value):
""" render a KPI value as a unicode string, ready for display """ """ render a KPI value as a unicode string, ready for display """
assert len(self) == 1 assert len(self) == 1
if value is None:
return '#N/A'
if value is None or value is AccountingNone:
return ''
elif self.type == 'num': elif self.type == 'num':
return self._render_num(lang_id, value, self.divider, return self._render_num(lang_id, value, self.divider,
self.dp, self.prefix, self.suffix) self.dp, self.prefix, self.suffix)
@ -153,31 +154,45 @@ class MisReportKpi(models.Model):
def render_comparison(self, lang_id, value, base_value, def render_comparison(self, lang_id, value, base_value,
average_value, average_base_value): average_value, average_base_value):
""" render the comparison of two KPI values, ready for display """
""" render the comparison of two KPI values, ready for display
If the difference is 0, an empty string is returned.
"""
assert len(self) == 1 assert len(self) == 1
if value is None or base_value is None:
return ''
if value is None:
value = AccountingNone
if base_value is None:
base_value = AccountingNone
if self.type == 'pct': if self.type == 'pct':
delta = value - base_value
if delta and round(delta, self.dp) != 0:
return self._render_num( return self._render_num(
lang_id, lang_id,
value - base_value,
0.01, self.dp, '', _('pp'), sign='+')
delta,
0.01, self.dp, '', _('pp'),
sign='+')
elif self.type == 'num': elif self.type == 'num':
if average_value:
if value and average_value:
value = value / float(average_value) value = value / float(average_value)
if average_base_value:
if base_value and average_base_value:
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
if delta and round(delta, self.dp) != 0:
return self._render_num( return self._render_num(
lang_id, lang_id,
value - base_value,
self.divider, self.dp, self.prefix, self.suffix, sign='+')
delta,
self.divider, self.dp, self.prefix, self.suffix,
sign='+')
elif self.compare_method == 'pct': elif self.compare_method == 'pct':
if round(base_value, self.dp) != 0:
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 self._render_num( return self._render_num(
lang_id, lang_id,
(value - base_value) / abs(base_value),
0.01, self.dp, '', '%', sign='+')
delta,
0.01, self.dp, '', '%',
sign='+')
return '' return ''
def _render_num(self, lang_id, value, divider, def _render_num(self, lang_id, value, divider,
@ -497,6 +512,7 @@ class MisReportInstancePeriod(models.Model):
'max': _max, 'max': _max,
'len': len, 'len': len,
'avg': _avg, 'avg': _avg,
'AccountingNone': AccountingNone,
} }
localdict.update(self._fetch_queries()) localdict.update(self._fetch_queries())
@ -544,7 +560,7 @@ class MisReportInstancePeriod(models.Model):
AEP.has_account_var(kpi.expression)) AEP.has_account_var(kpi.expression))
res[kpi.name] = { res[kpi.name] = {
'val': kpi_val,
'val': None if kpi_val is AccountingNone else kpi_val,
'val_r': kpi_val_rendered, 'val_r': kpi_val_rendered,
'val_c': kpi_val_comment, 'val_c': kpi_val_comment,
'style': kpi_style, 'style': kpi_style,

Loading…
Cancel
Save