diff --git a/mis_builder/__openerp__.py b/mis_builder/__openerp__.py old mode 100644 new mode 100755 diff --git a/mis_builder/models/accounting_none.py b/mis_builder/models/accounting_none.py new file mode 100755 index 00000000..bcf15ecb --- /dev/null +++ b/mis_builder/models/accounting_none.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + + +""" +Provides the AccountingNone singleton + +AccountingNone is a null value that dissolves in basic arithmetic operations, +as illustrated in the examples below + +>>> 1 + 1 +2 +>>> 1 + AccountingNone +1 +>>> AccountingNone + 1 +1 +>>> AccountingNone + None +AccountingNone +>>> +AccountingNone +AccountingNone +>>> -AccountingNone +AccountingNone +>>> -(AccountingNone) +AccountingNone +>>> AccountingNone - 1 +-1 +>>> 1 - AccountingNone +1 +>>> 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 +""" + + +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 __pos__(self): + return self + + def __neg__(self): + return self + + def __floordiv__(self, other): + """ + Overload of the // operator + """ + if other is AccountingNone: + return AccountingNone + return 0.0 + + def __rfloordiv__(self, other): + raise ZeroDivisionError + + def __truediv__(self, other): + """ + Overload of the / operator + """ + 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 + + def __rmul__(self, other): + if other is None or other is AccountingNone: + return AccountingNone + return 0.0 + + def __repr__(self): + return 'AccountingNone' + + def __unicode__(self): + return '' + + +AccountingNone = AccountingNoneType() + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/mis_builder/models/aep.py b/mis_builder/models/aep.py old mode 100644 new mode 100755 index 4267c296..cbf63fea --- a/mis_builder/models/aep.py +++ b/mis_builder/models/aep.py @@ -8,6 +8,8 @@ from collections import defaultdict 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' @@ -228,12 +230,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': diff --git a/mis_builder/models/mis_builder.py b/mis_builder/models/mis_builder.py old mode 100644 new mode 100755 index 17bc7aee..30106461 --- a/mis_builder/models/mis_builder.py +++ b/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 .aggregate import _sum, _avg, _min, _max +from .accounting_none import AccountingNone _logger = logging.getLogger(__name__) @@ -142,8 +143,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) @@ -469,6 +470,7 @@ class MisReportInstancePeriod(models.Model): 'max': _max, 'len': len, 'avg': _avg, + 'AccountingNone': AccountingNone, } localdict.update(self._fetch_queries()) @@ -515,7 +517,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,