Skip to content

Commit

Permalink
generalize auto expand
Browse files Browse the repository at this point in the history
  • Loading branch information
TeoGoddet committed Dec 21, 2021
1 parent 46eb975 commit 90702af
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 63 deletions.
2 changes: 2 additions & 0 deletions mis_builder/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
from . import aep
from . import mis_kpi_data
from . import prorata_read_group_mixin
from . import account_account
from . import account_analytic_account
14 changes: 14 additions & 0 deletions mis_builder/models/account_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2017 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv import expression
from .kpimatrix import RowDetailIdentifierInterface

class AccountAccount(models.Model):
_name = 'account.account'
_inherit = ['account.account', 'row.detail.identifier.mixin']

def get_domain(self):
return [('account_id' , '=', self.id)]
14 changes: 14 additions & 0 deletions mis_builder/models/account_analytic_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2017 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv import expression
from .kpimatrix import RowDetailIdentifierInterface

class AccountAnalyticAccount(models.Model):
_name = 'account.analytic.account'
_inherit = ['account.analytic.account', 'row.detail.identifier.mixin']

def get_domain(self):
return [('analytic_account_id' , '=', self.id)]
34 changes: 22 additions & 12 deletions mis_builder/models/aep.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,23 +170,29 @@ def _parse_match_object(self, mo):
ml_domain = tuple()
return field, mode, acc_domain, ml_domain

def parse_expr(self, expr):
def parse_expr(self, expr, row_detail_identifiers = False):
"""Parse an expression, extracting accounting variables.
Move line domains and account selectors are extracted and
stored in the map so when all expressions have been parsed,
we know which account domains to query for each move line domain
and mode.
"""
if not row_detail_identifiers:
row_detail_identifiers = [None]

for mo in self._ACC_RE.finditer(expr):
_, mode, acc_domain, ml_domain = self._parse_match_object(mo)
if mode == self.MODE_END and self.smart_end:
modes = (self.MODE_INITIAL, self.MODE_VARIATION, self.MODE_END)
else:
modes = (mode,)
for mode in modes:
key = (ml_domain, mode)
self._map_account_ids[key].add(acc_domain)
for rdi in row_detail_identifiers:
for mode in modes:
key = (ml_domain, rdi, mode)
if rdi:
print(rdi.get_label())
self._map_account_ids[key].add(acc_domain)

def done_parsing(self):
""" Replace account domains by account ids in map """
Expand Down Expand Up @@ -328,19 +334,23 @@ def do_queries(
domain_by_mode = {}
ends = []
for key in self._map_account_ids:
domain, mode = key
(domain, row_detail_identifier, mode) = key
if mode == self.MODE_END and self.smart_end:
# postpone computation of ending balance
ends.append((domain, mode))
ends.append((domain, row_detail_identifier, mode))
continue
if mode not in domain_by_mode:
domain_by_mode[mode] = self.get_aml_domain_for_dates(
date_from, date_to, mode, target_move
)
domain = list(domain) + domain_by_mode[mode]
domain = list(domain) + domain_by_mode[mode]
domain.append(("account_id", "in", self._map_account_ids[key]))
if additional_move_line_filter:
domain.extend(additional_move_line_filter)

if row_detail_identifier:
domain = list(domain) + row_detail_identifier.get_domain()

# fetch sum of debit/credit, grouped by account_id
accs = aml_model.read_group(
domain,
Expand All @@ -360,9 +370,9 @@ def do_queries(
self._data[key][acc["account_id"][0]] = (debit * rate, credit * rate)
# compute ending balances by summing initial and variation
for key in ends:
domain, mode = key
initial_data = self._data[(domain, self.MODE_INITIAL)]
variation_data = self._data[(domain, self.MODE_VARIATION)]
domain, row_detail_identifier, mode = key
initial_data = self._data[(domain, row_detail_identifier, self.MODE_INITIAL)]
variation_data = self._data[(domain, row_detail_identifier, self.MODE_VARIATION)]
account_ids = set(initial_data.keys()) | set(variation_data.keys())
for account_id in account_ids:
di, ci = initial_data.get(account_id, (AccountingNone, AccountingNone))
Expand All @@ -371,7 +381,7 @@ def do_queries(
)
self._data[key][account_id] = (di + dv, ci + cv)

def replace_expr(self, expr):
def replace_expr(self, expr, row_detail_identifier = None):
"""Replace accounting variables in an expression by their amount.
Returns a new expression string.
Expand All @@ -381,7 +391,7 @@ def replace_expr(self, expr):

def f(mo):
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
key = (ml_domain, mode)
key = (ml_domain, row_detail_identifier, mode)
account_ids_data = self._data[key]
v = AccountingNone
account_ids = self._account_ids_by_acc_domain[acc_domain]
Expand Down
7 changes: 4 additions & 3 deletions mis_builder/models/expression_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,27 @@ def aep_do_queries(self):
)
self._aep_queries_done = True

def eval_expressions(self, expressions, locals_dict):
def eval_expressions(self, expressions, locals_dict, row_detail_identifier = None):
vals = []
drilldown_args = []
name_error = False
for expression in expressions:
expr = expression and expression.name or "AccountingNone"
if self.aep:
replaced_expr = self.aep.replace_expr(expr)
replaced_expr = self.aep.replace_expr(expr, row_detail_identifier)
else:
replaced_expr = expr
val = mis_safe_eval(replaced_expr, locals_dict)
vals.append(val)
if isinstance(val, NameDataError):
name_error = True
if replaced_expr != expr:
drilldown_args.append({"expr": expr})
drilldown_args.append({"expr": expr, "row_detail_identifier": row_detail_identifier.get_domain()} if row_detail_identifier else {"expr": expr})
else:
drilldown_args.append(None)
return vals, drilldown_args, name_error

# TODO maybe depreciated
def eval_expressions_by_account(self, expressions, locals_dict):
if not self.aep:
return
Expand Down
73 changes: 55 additions & 18 deletions mis_builder/models/kpimatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
from collections import OrderedDict, defaultdict

from odoo import _
from odoo import _, models
from odoo.exceptions import UserError

from .accounting_none import AccountingNone
Expand All @@ -21,41 +21,70 @@
_logger = logging.getLogger(__name__)


class RowDetailIdentifierInterface(models.Model):
_name = "row.detail.identifier.mixin"

def get_id(self):
""" return a python compatible row id """
from .mis_report import _python_var
return _python_var(self.get_label())

def get_label(self):
""" return the row label """
return self.name_get()[0][1]

def iter_parents(self): # TODO assess utility# TODO assess
""" yield all parent RowDetailIdentifierInterface
(eg if self references an account, yield parent groups from bottom to top)
This is important so detail rows can be added in any order and the KpiMatrix
can create placeholders for parent rows.
"""
pass

def get_domain(self):
return [('account_id', '=', self.id)]

# def __hash__(self):
# pass

def __lt__(self): # TODO assess utility
pass

class KpiMatrixRow(object):

# TODO: ultimately, the kpi matrix will become ignorant of KPI's and
# accounts and know about rows, columns, sub columns and styles only.
# It is already ignorant of period and only knowns about columns.
# This will require a correct abstraction for expanding row details.

def __init__(self, matrix, kpi, account_id=None, parent_row=None):
def __init__(self, matrix, kpi, row_detail_identifier=None, parent_row=None):
self._matrix = matrix
self.kpi = kpi
self.account_id = account_id
self.row_detail_identifier = row_detail_identifier
self.description = ""
self.parent_row = parent_row
if not self.account_id:
if not self.row_detail_identifier:
self.style_props = self._matrix._style_model.merge(
[self.kpi.report_id.style_id, self.kpi.style_id]
)
else:
self.style_props = self._matrix._style_model.merge(
[self.kpi.report_id.style_id, self.kpi.auto_expand_accounts_style_id]
[self.kpi.report_id.style_id, self.kpi.row_details_style_id]
)

@property
def label(self):
if not self.account_id:
if not self.row_detail_identifier:
return self.kpi.description
else:
return self._matrix.get_account_name(self.account_id)
return self.row_detail_identifier.get_label()

@property
def row_id(self):
if not self.account_id:
if not self.row_detail_identifier:
return self.kpi.name
else:
return "{}:{}".format(self.kpi.name, self.account_id)
return "{}:{}".format(self.kpi.name, self.row_detail_identifier.get_id())

def iter_cell_tuples(self, cols=None):
if cols is None:
Expand Down Expand Up @@ -217,26 +246,34 @@ def set_values(self, kpi, col_key, vals, drilldown_args, tooltips=True):
Invoke this after declaring the kpi and the column.
"""
self.set_values_detail_account(
self.set_values_detail(
kpi, col_key, None, vals, drilldown_args, tooltips
)

def set_values_detail_account(
self, kpi, col_key, account_id, vals, drilldown_args, tooltips=True
):
"""Compatibility function for old account_id drilldown api
"""
# TODO : if needed : put the account_id in a RowDetailIdentifier class and call set_values_detail
pass

def set_values_detail(
self, kpi, col_key, row_detail_identifier, vals, drilldown_args, tooltips=True
):
"""Set values for a kpi and a column and a detail account.
Invoke this after declaring the kpi and the column.
"""
if not account_id:
if not row_detail_identifier:
row = self._kpi_rows[kpi]
else:
kpi_row = self._kpi_rows[kpi]
if account_id in self._detail_rows[kpi]:
row = self._detail_rows[kpi][account_id]
if row_detail_identifier.get_id() in self._detail_rows[kpi]:
row = self._detail_rows[kpi][row_detail_identifier.get_id()] # TODO need to check if need a change
else:
row = KpiMatrixRow(self, kpi, account_id, parent_row=kpi_row)
self._detail_rows[kpi][account_id] = row
row = KpiMatrixRow(self, kpi, row_detail_identifier, parent_row=kpi_row)
self._detail_rows[kpi][row_detail_identifier.get_id()] = row
col = self._cols[col_key]
cell_tuple = []
assert len(vals) == col.colspan
Expand Down Expand Up @@ -412,7 +449,7 @@ def compute_sums(self):
for row in self.iter_rows():
acc = SimpleArray([AccountingNone] * (len(common_subkpis) or 1))
if row.kpi.accumulation_method == ACC_SUM and not (
row.account_id and not sum_accdet
row.row_detail_identifier and not sum_accdet
):
for sign, col_to_sum in col_to_sum_keys:
cell_tuple = self._cols[col_to_sum].get_cell_tuple_for_row(row)
Expand All @@ -429,10 +466,10 @@ def compute_sums(self):
acc += SimpleArray(vals)
else:
acc -= SimpleArray(vals)
self.set_values_detail_account(
self.set_values_detail(
row.kpi,
sumcol_key,
row.account_id,
row.row_detail_identifier,
acc,
[None] * (len(common_subkpis) or 1),
tooltips=False,
Expand Down
Loading

0 comments on commit 90702af

Please sign in to comment.