Merge pull request #169 from ThomasBinsfeld/8.0_add_AccountingNone
[IMP] mis_builder: display blank cells instead of 0.0 when there is no datapull/177/head
commit
f69d436fb1
|
@ -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()
|
|
@ -9,6 +9,7 @@ from openerp.exceptions import Warning as UserError
|
|||
from openerp.models import expression
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.tools.translate import _
|
||||
from .accounting_none import AccountingNone
|
||||
|
||||
MODE_VARIATION = 'p'
|
||||
MODE_INITIAL = 'i'
|
||||
|
@ -348,12 +349,13 @@ class AccountingExpressionProcessor(object):
|
|||
field, mode, account_codes, domain = self._parse_match_object(mo)
|
||||
key = (domain, mode)
|
||||
account_ids_data = self._data[key]
|
||||
v = 0.0
|
||||
v = AccountingNone
|
||||
for account_code in account_codes:
|
||||
account_ids = self._account_ids_by_code[account_code]
|
||||
for account_id in account_ids:
|
||||
debit, credit = \
|
||||
account_ids_data.get(account_id, (0.0, 0.0))
|
||||
account_ids_data.get(account_id,
|
||||
(AccountingNone, AccountingNone))
|
||||
if field == 'bal':
|
||||
v += debit - credit
|
||||
elif field == 'deb':
|
||||
|
|
|
@ -16,6 +16,7 @@ from openerp.tools.safe_eval import safe_eval
|
|||
|
||||
from .aep import AccountingExpressionProcessor as AEP
|
||||
from .aggregate import _sum, _avg, _min, _max
|
||||
from .accounting_none import AccountingNone
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -140,8 +141,8 @@ class MisReportKpi(models.Model):
|
|||
def render(self, lang_id, value):
|
||||
""" render a KPI value as a unicode string, ready for display """
|
||||
assert len(self) == 1
|
||||
if value is None:
|
||||
return '#N/A'
|
||||
if value is None or value is AccountingNone:
|
||||
return ''
|
||||
elif self.type == 'num':
|
||||
return self._render_num(lang_id, value, self.divider,
|
||||
self.dp, self.prefix, self.suffix)
|
||||
|
@ -153,31 +154,45 @@ class MisReportKpi(models.Model):
|
|||
|
||||
def render_comparison(self, lang_id, value, 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
|
||||
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':
|
||||
return self._render_num(
|
||||
lang_id,
|
||||
value - base_value,
|
||||
0.01, self.dp, '', _('pp'), sign='+')
|
||||
elif self.type == 'num':
|
||||
if average_value:
|
||||
value = value / float(average_value)
|
||||
if average_base_value:
|
||||
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 self._render_num(
|
||||
lang_id,
|
||||
value - base_value,
|
||||
self.divider, self.dp, self.prefix, self.suffix, sign='+')
|
||||
elif self.compare_method == 'pct':
|
||||
if round(base_value, self.dp) != 0:
|
||||
delta,
|
||||
0.01, self.dp, '', _('pp'),
|
||||
sign='+')
|
||||
elif self.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 self.compare_method == 'diff':
|
||||
delta = value - base_value
|
||||
if delta and round(delta, self.dp) != 0:
|
||||
return self._render_num(
|
||||
lang_id,
|
||||
(value - base_value) / abs(base_value),
|
||||
0.01, self.dp, '', '%', sign='+')
|
||||
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 self._render_num(
|
||||
lang_id,
|
||||
delta,
|
||||
0.01, self.dp, '', '%',
|
||||
sign='+')
|
||||
return ''
|
||||
|
||||
def _render_num(self, lang_id, value, divider,
|
||||
|
@ -497,6 +512,7 @@ class MisReportInstancePeriod(models.Model):
|
|||
'max': _max,
|
||||
'len': len,
|
||||
'avg': _avg,
|
||||
'AccountingNone': AccountingNone,
|
||||
}
|
||||
|
||||
localdict.update(self._fetch_queries())
|
||||
|
@ -544,7 +560,7 @@ class MisReportInstancePeriod(models.Model):
|
|||
AEP.has_account_var(kpi.expression))
|
||||
|
||||
res[kpi.name] = {
|
||||
'val': kpi_val,
|
||||
'val': None if kpi_val is AccountingNone else kpi_val,
|
||||
'val_r': kpi_val_rendered,
|
||||
'val_c': kpi_val_comment,
|
||||
'style': kpi_style,
|
||||
|
|
Loading…
Reference in New Issue