diff --git a/mis_builder/models/aep.py b/mis_builder/models/aep.py
index d2b57bd02..8bcf6ed30 100644
--- a/mis_builder/models/aep.py
+++ b/mis_builder/models/aep.py
@@ -14,6 +14,8 @@
_DOMAIN_START_RE = re.compile(r"\(|(['\"])[!&|]\1")
+UNCLASSIFIED_ROW_DETAIL = "other"
+
def _is_domain(s):
"""Test if a string looks like an Odoo domain"""
@@ -300,16 +302,14 @@ def do_queries(
date_to,
additional_move_line_filter=None,
aml_model=None,
+ auto_expand_col_name=None,
):
"""Query sums of debit and credit for all accounts and domains
used in expressions.
This method must be executed after done_parsing().
"""
- if not aml_model:
- aml_model = self.env["account.move.line"]
- else:
- aml_model = self.env[aml_model]
+ aml_model = self.env[aml_model or "account.move.line"]
aml_model = aml_model.with_context(active_test=False)
company_rates = self._get_company_rates(date_to)
# {(domain, mode): {account_id: (debit, credit)}}
@@ -330,13 +330,16 @@ def do_queries(
domain.append(("account_id", "in", self._map_account_ids[key]))
if additional_move_line_filter:
domain.extend(additional_move_line_filter)
+
+ get_fields = ["debit", "credit", "account_id", "company_id"]
+ group_by_fields = ["account_id", "company_id"]
+ if auto_expand_col_name:
+ get_fields = [auto_expand_col_name] + get_fields
+ group_by_fields = [auto_expand_col_name] + group_by_fields
+
# fetch sum of debit/credit, grouped by account_id
- accs = aml_model.read_group(
- domain,
- ["debit", "credit", "account_id", "company_id"],
- ["account_id", "company_id"],
- lazy=False,
- )
+ accs = aml_model.read_group(domain, get_fields, group_by_fields, lazy=False)
+
for acc in accs:
rate, dp = company_rates[acc["company_id"][0]]
debit = acc["debit"] or 0.0
@@ -346,19 +349,45 @@ def do_queries(
):
# in initial mode, ignore accounts with 0 balance
continue
- self._data[key][acc["account_id"][0]] = (debit * rate, credit * rate)
+ if (
+ auto_expand_col_name
+ and auto_expand_col_name in acc
+ and acc[auto_expand_col_name]
+ ):
+ rdi_id = acc[auto_expand_col_name][0]
+ else:
+ rdi_id = UNCLASSIFIED_ROW_DETAIL
+ if not self._data[key].get(rdi_id, False):
+ self._data[key][rdi_id] = defaultdict(dict)
+ self._data[key][rdi_id][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)]
- 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))
- dv, cv = variation_data.get(
- account_id, (AccountingNone, AccountingNone)
+ rdis = set(initial_data.keys()) | set(variation_data.keys())
+ for rdi in rdis:
+ if not initial_data.get(rdi, False):
+ initial_data[rdi] = defaultdict(dict)
+ if not variation_data.get(rdi, False):
+ variation_data[rdi] = defaultdict(dict)
+ if not self._data[key].get(rdi, False):
+ self._data[key][rdi] = defaultdict(dict)
+
+ account_ids = set(initial_data[rdi].keys()) | set(
+ variation_data[rdi].keys()
)
- self._data[key][account_id] = (di + dv, ci + cv)
+ for account_id in account_ids:
+ di, ci = initial_data[rdi].get(
+ account_id, (AccountingNone, AccountingNone)
+ )
+ dv, cv = variation_data[rdi].get(
+ account_id, (AccountingNone, AccountingNone)
+ )
+ self._data[key][rdi][account_id] = (di + dv, ci + cv)
def replace_expr(self, expr):
"""Replace accounting variables in an expression by their amount.
@@ -371,23 +400,25 @@ def replace_expr(self, expr):
def f(mo):
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
key = (ml_domain, mode)
- account_ids_data = self._data[key]
+ rdi_ids_data = self._data[key]
v = AccountingNone
account_ids = self._account_ids_by_acc_domain[acc_domain]
- for account_id in account_ids:
- debit, credit = account_ids_data.get(
- account_id, (AccountingNone, AccountingNone)
- )
- if field == "bal":
- v += debit - credit
- elif field == "pbal" and debit >= credit:
- v += debit - credit
- elif field == "nbal" and debit < credit:
- v += debit - credit
- elif field == "deb":
- v += debit
- elif field == "crd":
- v += credit
+ for rdi in rdi_ids_data:
+ account_ids_data = self._data[key][rdi]
+ for account_id in account_ids:
+ debit, credit = account_ids_data.get(
+ account_id, (AccountingNone, AccountingNone)
+ )
+ if field == "bal":
+ v += debit - credit
+ elif field == "pbal" and debit >= credit:
+ v += debit - credit
+ elif field == "nbal" and debit < credit:
+ v += debit - credit
+ elif field == "deb":
+ v += debit
+ elif field == "crd":
+ v += credit
# in initial balance mode, assume 0 is None
# as it does not make sense to distinguish 0 from "no data"
if (
@@ -401,11 +432,11 @@ def f(mo):
return self._ACC_RE.sub(f, expr)
def replace_exprs_by_account_id(self, exprs):
- """Replace accounting variables in a list of expression
- by their amount, iterating by accounts involved in the expression.
+ """This method is depreciated and replaced by replace_exprs_by_row_detail.
+ Replace accounting variables in a list of expression
+ by their amount, iterating by accounts involved in the expression.
yields account_id, replaced_expr
-
This method must be executed after do_queries().
"""
@@ -417,7 +448,7 @@ def f(mo):
if account_id not in self._account_ids_by_acc_domain[acc_domain]:
return "(AccountingNone)"
# here we know account_id is involved in acc_domain
- account_ids_data = self._data[key]
+ account_ids_data = self._data[key][UNCLASSIFIED_ROW_DETAIL]
debit, credit = account_ids_data.get(
account_id, (AccountingNone, AccountingNone)
)
@@ -452,7 +483,7 @@ def f(mo):
for mo in self._ACC_RE.finditer(expr):
field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
key = (ml_domain, mode)
- account_ids_data = self._data[key]
+ account_ids_data = self._data[key][UNCLASSIFIED_ROW_DETAIL]
for account_id in self._account_ids_by_acc_domain[acc_domain]:
if account_id in account_ids_data:
account_ids.add(account_id)
@@ -460,6 +491,58 @@ def f(mo):
for account_id in account_ids:
yield account_id, [self._ACC_RE.sub(f, expr) for expr in exprs]
+ def replace_exprs_by_row_detail(self, exprs):
+ """Replace accounting variables in a list of expression
+ by their amount, iterating by accounts involved in the expression.
+
+ yields account_id, replaced_expr
+
+ This method must be executed after do_queries().
+ """
+
+ def f(mo):
+ field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
+ key = (ml_domain, mode)
+ v = AccountingNone
+ account_ids_data = self._data[key][rdi_id]
+ account_ids = self._account_ids_by_acc_domain[acc_domain]
+
+ for account_id in account_ids:
+ debit, credit = account_ids_data.get(
+ account_id, (AccountingNone, AccountingNone)
+ )
+ if field == "bal":
+ v += debit - credit
+ elif field == "pbal" and debit >= credit:
+ v += debit - credit
+ elif field == "nbal" and debit < credit:
+ v += debit - credit
+ elif field == "deb":
+ v += debit
+ elif field == "crd":
+ v += credit
+ # in initial balance mode, assume 0 is None
+ # as it does not make sense to distinguish 0 from "no data"
+ if (
+ v is not AccountingNone
+ and mode in (self.MODE_INITIAL, self.MODE_UNALLOCATED)
+ and float_is_zero(v, precision_digits=self.dp)
+ ):
+ v = AccountingNone
+ return "(" + repr(v) + ")"
+
+ rdi_ids = set()
+ for expr in exprs:
+ for mo in self._ACC_RE.finditer(expr):
+ field, mode, acc_domain, ml_domain = self._parse_match_object(mo)
+ key = (ml_domain, mode)
+ rdis_data = self._data[key]
+ for rdi_id in rdis_data.keys():
+ rdi_ids.add(rdi_id)
+
+ for rdi_id in rdi_ids:
+ yield rdi_id, [self._ACC_RE.sub(f, expr) for expr in exprs]
+
@classmethod
def _get_balances(cls, mode, companies, date_from, date_to):
expr = "deb{mode}[], crd{mode}[]".format(mode=mode)
@@ -470,7 +553,10 @@ def _get_balances(cls, mode, companies, date_from, date_to):
aep.parse_expr(expr)
aep.done_parsing()
aep.do_queries(date_from, date_to)
- return aep._data[((), mode)]
+
+ return aep._data[((), mode)].get(UNCLASSIFIED_ROW_DETAIL, {})
+ # to keep compatibility, we give the UNCLASSIFIED_ROW_DETAIL
+ # (expecting that auto_expand_col_names=None was given to do_queries )
@classmethod
def get_balances_initial(cls, companies, date):
diff --git a/mis_builder/models/expression_evaluator.py b/mis_builder/models/expression_evaluator.py
index 32705f55d..5faa231b3 100644
--- a/mis_builder/models/expression_evaluator.py
+++ b/mis_builder/models/expression_evaluator.py
@@ -20,13 +20,14 @@ def __init__(
self.aml_model = aml_model
self._aep_queries_done = False
- def aep_do_queries(self):
+ def aep_do_queries(self, auto_expand_col_name=None):
if self.aep and not self._aep_queries_done:
self.aep.do_queries(
self.date_from,
self.date_to,
self.additional_move_line_filter,
self.aml_model,
+ auto_expand_col_name,
)
self._aep_queries_done = True
@@ -50,6 +51,7 @@ def eval_expressions(self, expressions, locals_dict):
drilldown_args.append(None)
return vals, drilldown_args, name_error
+ # we keep it for backward compatibility
def eval_expressions_by_account(self, expressions, locals_dict):
if not self.aep:
return
@@ -66,3 +68,20 @@ def eval_expressions_by_account(self, expressions, locals_dict):
else:
drilldown_args.append(None)
yield account_id, vals, drilldown_args, name_error
+
+ def eval_expressions_by_row_detail(self, expressions, locals_dict):
+ if not self.aep:
+ return
+ exprs = [e and e.name or "AccountingNone" for e in expressions]
+ for rdi_id, replaced_exprs in self.aep.replace_exprs_by_row_detail(exprs):
+ vals = []
+ drilldown_args = []
+ name_error = False
+ for expr, replaced_expr in zip(exprs, replaced_exprs):
+ val = mis_safe_eval(replaced_expr, locals_dict)
+ vals.append(val)
+ if replaced_expr != expr:
+ drilldown_args.append({"expr": expr, "row_detail": rdi_id})
+ else:
+ drilldown_args.append(None)
+ yield rdi_id, vals, drilldown_args, name_error
diff --git a/mis_builder/models/kpimatrix.py b/mis_builder/models/kpimatrix.py
index 7aaf43d44..c47546078 100644
--- a/mis_builder/models/kpimatrix.py
+++ b/mis_builder/models/kpimatrix.py
@@ -8,6 +8,7 @@
from odoo.exceptions import UserError
from .accounting_none import AccountingNone
+from .aep import UNCLASSIFIED_ROW_DETAIL
from .mis_kpi_data import ACC_SUM
from .mis_safe_eval import DataError, mis_safe_eval
from .simple_array import SimpleArray
@@ -22,13 +23,13 @@ class KpiMatrixRow(object):
# 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]
)
@@ -39,17 +40,17 @@ def __init__(self, matrix, kpi, account_id=None, parent_row=None):
@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._matrix.get_rdi_name(self.row_detail_identifier)
@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)
def iter_cell_tuples(self, cols=None):
if cols is None:
@@ -147,12 +148,12 @@ def __init__(
class KpiMatrix(object):
- def __init__(self, env, multi_company=False, account_model="account.account"):
+ def __init__(self, env, multi_company=False, rdi_model="account.account"):
# cache language id for faster rendering
lang_model = env["res.lang"]
self.lang = lang_model._lang_get(env.user.lang)
self._style_model = env["mis.report.style"]
- self._account_model = env[account_model]
+ self._rdi_model = env[rdi_model]
# data structures
# { kpi: KpiMatrixRow }
self._kpi_rows = OrderedDict()
@@ -165,7 +166,7 @@ def __init__(self, env, multi_company=False, account_model="account.account"):
# { col_key (left of sum): (col_key, [(sign, sum_col_key)])
self._sum_todo = {}
# { account_id: account_name }
- self._account_names = {}
+ self._rdi_names = {}
self._multi_company = multi_company
def declare_kpi(self, kpi):
@@ -215,22 +216,23 @@ def set_values(self, kpi, col_key, vals, drilldown_args, tooltips=True):
kpi, col_key, None, vals, drilldown_args, tooltips
)
+ # TODO this could be renamed set_values_detail
def set_values_detail_account(
- self, kpi, col_key, account_id, vals, drilldown_args, tooltips=True
+ 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 in self._detail_rows[kpi]:
+ row = self._detail_rows[kpi][row_detail_identifier]
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] = row
col = self._cols[col_key]
cell_tuple = []
assert len(vals) == col.colspan
@@ -406,7 +408,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)
@@ -426,7 +428,7 @@ def compute_sums(self):
self.set_values_detail_account(
row.kpi,
sumcol_key,
- row.account_id,
+ row.row_detail_identifier,
acc,
[None] * (len(common_subkpis) or 1),
tooltips=False,
@@ -462,23 +464,27 @@ def iter_subcols(self):
for subcol in col.iter_subcols():
yield subcol
- def _load_account_names(self):
- account_ids = set()
+ def _load_rdi_names(self):
+ rdi_ids = set()
for detail_rows in self._detail_rows.values():
- account_ids.update(detail_rows.keys())
- accounts = self._account_model.search([("id", "in", list(account_ids))])
- self._account_names = {a.id: self._get_account_name(a) for a in accounts}
-
- def _get_account_name(self, account):
- result = "{} {}".format(account.code, account.name)
- if self._multi_company:
- result = "{} [{}]".format(result, account.company_id.name)
+ rdi_ids.update(detail_rows.keys())
+ rdi_ids = list(rdi_ids)
+ if UNCLASSIFIED_ROW_DETAIL in rdi_ids:
+ rdi_ids.remove(UNCLASSIFIED_ROW_DETAIL)
+ rdis = self._rdi_model.search([("id", "in", rdi_ids)])
+ self._rdi_names = {rdi.id: self._get_rdi_name(rdi) for rdi in rdis}
+ self._rdi_names[UNCLASSIFIED_ROW_DETAIL] = _("Other")
+
+ def _get_rdi_name(self, rdi):
+ result = rdi.name_get()[0][1]
+ if self._multi_company and rdi.company_id:
+ result = u"{} [{}]".format(result, rdi.company_id.name)
return result
- def get_account_name(self, account_id):
- if account_id not in self._account_names:
- self._load_account_names()
- return self._account_names[account_id]
+ def get_rdi_name(self, rdi_id):
+ if rdi_id not in self._rdi_names:
+ self._load_rdi_names()
+ return self._rdi_names[rdi_id]
def as_dict(self):
header = [{"cols": []}, {"cols": []}]
diff --git a/mis_builder/models/mis_report.py b/mis_builder/models/mis_report.py
index ee2074abf..a402d5569 100644
--- a/mis_builder/models/mis_report.py
+++ b/mis_builder/models/mis_report.py
@@ -91,11 +91,11 @@ class MisReportKpi(models.Model):
copy=True,
string="Expressions",
)
- auto_expand_accounts = fields.Boolean(string="Display details by account")
+
+ # TODO : this fields should be renamed to auto_expand_details or something like this
+ auto_expand_accounts = fields.Boolean(string="Display details")
auto_expand_accounts_style_id = fields.Many2one(
- string="Style for account detail rows",
- comodel_name="mis.report.style",
- required=False,
+ string="Style for details rows", comodel_name="mis.report.style", required=False
)
style_id = fields.Many2one(
string="Style", comodel_name="mis.report.style", required=False
@@ -465,6 +465,20 @@ def _default_move_lines_source(self):
)
account_model = fields.Char(compute="_compute_account_model")
+ auto_expand_col_name = fields.Selection(
+ [
+ ("account_id", _("Accounts")),
+ ("analytic_account_id", _("Analytic Accounts")),
+ ("partner_id", _("Parner")),
+ ],
+ required=True,
+ string="Auto Expand Details",
+ default="account_id",
+ help="Allow to drilldown kpis by the specified field, "
+ "it need to be activated in each kpi. You should use "
+ "style configuration to hide null.",
+ )
+
@api.depends("kpi_ids", "subreport_ids")
def _compute_all_kpi_ids(self):
for rec in self:
@@ -539,10 +553,14 @@ def copy(self, default=None):
return new
# TODO: kpi name cannot be start with query name
-
def prepare_kpi_matrix(self, multi_company=False):
self.ensure_one()
- kpi_matrix = KpiMatrix(self.env, multi_company, self.account_model)
+ auto_expand_model = self.move_lines_source.field_id.filtered(
+ lambda f: f.name == self.auto_expand_col_name
+ ).mapped("relation")
+ kpi_matrix = KpiMatrix(
+ self.env, multi_company, auto_expand_model[0] if auto_expand_model else None
+ )
for kpi in self.kpi_ids:
kpi_matrix.declare_kpi(kpi)
return kpi_matrix
@@ -748,21 +766,23 @@ def _declare_and_compute_col( # noqa: C901 (TODO simplify this fnction)
):
continue
- for (
- account_id,
- vals,
- drilldown_args,
- _name_error,
- ) in expression_evaluator.eval_expressions_by_account(
- expressions, locals_dict
- ):
+ rdis = expression_evaluator.eval_expressions_by_row_detail(
+ expressions, locals_dict # , self.auto_expand_col_name
+ )
+ for (rdi, vals, drilldown_args, _name_error) in rdis:
for drilldown_arg in drilldown_args:
if not drilldown_arg:
continue
drilldown_arg["period_id"] = col_key
drilldown_arg["kpi_id"] = kpi.id
+ drilldown_arg[
+ "auto_expand_col_name"
+ ] = self.auto_expand_col_name
+ drilldown_arg["auto_expand_id"] = rdi
+ if not self._should_display_auto_expand(kpi, rdi, vals):
+ continue
kpi_matrix.set_values_detail_account(
- kpi, col_key, account_id, vals, drilldown_args
+ kpi, col_key, rdi, vals, drilldown_args
)
if len(recompute_queue) == 0:
@@ -880,7 +900,7 @@ def _declare_and_compute_period(
)
# use AEP to do the accounting queries
- expression_evaluator.aep_do_queries()
+ expression_evaluator.aep_do_queries(self.auto_expand_col_name)
self._declare_and_compute_col(
expression_evaluator,
@@ -1001,3 +1021,6 @@ def _evaluate(
no_auto_expand_accounts=True,
)
return locals_dict
+
+ def _should_display_auto_expand(self, kpi, rdi, vals):
+ return True
diff --git a/mis_builder/models/mis_report_instance.py b/mis_builder/models/mis_report_instance.py
index 0c5712240..e19cf0274 100644
--- a/mis_builder/models/mis_report_instance.py
+++ b/mis_builder/models/mis_report_instance.py
@@ -887,6 +887,10 @@ def drilldown(self, arg):
account_id,
)
domain.extend(period._get_additional_move_line_filter())
+ if arg.get("auto_expand_id") and arg.get("auto_expand_col_name"):
+ domain.extend(
+ [(arg.get("auto_expand_col_name"), "=", arg.get("auto_expand_id"))]
+ )
return {
"name": self._get_drilldown_action_name(arg),
"domain": domain,
diff --git a/mis_builder/tests/test_aep.py b/mis_builder/tests/test_aep.py
index e0eb4cefb..526941968 100644
--- a/mis_builder/tests/test_aep.py
+++ b/mis_builder/tests/test_aep.py
@@ -9,7 +9,11 @@
from odoo.tools.safe_eval import safe_eval
from ..models.accounting_none import AccountingNone
-from ..models.aep import AccountingExpressionProcessor as AEP, _is_domain
+from ..models.aep import (
+ UNCLASSIFIED_ROW_DETAIL,
+ AccountingExpressionProcessor as AEP,
+ _is_domain,
+)
class TestAEP(common.TransactionCase):
@@ -17,6 +21,7 @@ def setUp(self):
super().setUp()
self.res_company = self.env["res.company"]
self.account_model = self.env["account.account"]
+ self.partner_model = self.env["res.partner"]
self.move_model = self.env["account.move"]
self.journal_model = self.env["account.journal"]
self.curr_year = datetime.date.today().year
@@ -53,6 +58,13 @@ def setUp(self):
"type": "sale",
}
)
+ # create partner
+ self.partner_a = self.partner_model.create(
+ {"company_id": self.company.id, "name": "Partner A"}
+ )
+ self.partner_b = self.partner_model.create(
+ {"company_id": self.company.id, "name": "Partner B"}
+ )
# create move in December last year
self._create_move(
date=datetime.date(self.prev_year, 12, 1),
@@ -73,6 +85,8 @@ def setUp(self):
amount=500,
debit_acc=self.account_ar,
credit_acc=self.account_in,
+ debit_partner=self.partner_a,
+ credit_partner=self.partner_b,
)
# create the AEP, and prepare the expressions we'll need
self.aep = AEP(self.company)
@@ -109,17 +123,40 @@ def setUp(self):
self.aep.parse_expr("bal_700IN") # deprecated
self.aep.parse_expr("bals[700IN]") # deprecated
- def _create_move(self, date, amount, debit_acc, credit_acc, post=True):
+ def _create_move(
+ self,
+ date,
+ amount,
+ debit_acc,
+ credit_acc,
+ debit_partner=None,
+ credit_partner=None,
+ post=True,
+ ):
move = self.move_model.create(
{
"journal_id": self.journal.id,
"date": fields.Date.to_string(date),
"line_ids": [
- (0, 0, {"name": "/", "debit": amount, "account_id": debit_acc.id}),
(
0,
0,
- {"name": "/", "credit": amount, "account_id": credit_acc.id},
+ {
+ "name": "/",
+ "debit": amount,
+ "account_id": debit_acc.id,
+ "partner_id": debit_partner.id if debit_partner else None,
+ },
+ ),
+ (
+ 0,
+ 0,
+ {
+ "name": "/",
+ "credit": amount,
+ "account_id": credit_acc.id,
+ "partner_id": credit_partner.id if credit_partner else None,
+ },
),
],
}
@@ -128,11 +165,18 @@ def _create_move(self, date, amount, debit_acc, credit_acc, post=True):
move._post()
return move
- def _do_queries(self, date_from, date_to):
- self.aep.do_queries(
- date_from=fields.Date.to_string(date_from),
- date_to=fields.Date.to_string(date_to),
- )
+ def _do_queries(self, date_from, date_to, auto_expand_col_name=None):
+ if auto_expand_col_name:
+ self.aep.do_queries(
+ date_from=fields.Date.to_string(date_from),
+ date_to=fields.Date.to_string(date_to),
+ auto_expand_col_name=auto_expand_col_name,
+ )
+ else:
+ self.aep.do_queries(
+ date_from=fields.Date.to_string(date_from),
+ date_to=fields.Date.to_string(date_to),
+ )
def _eval(self, expr):
eval_dict = {"AccountingNone": AccountingNone}
@@ -141,8 +185,17 @@ def _eval(self, expr):
def _eval_by_account_id(self, expr):
res = {}
eval_dict = {"AccountingNone": AccountingNone}
+
for account_id, replaced_exprs in self.aep.replace_exprs_by_account_id([expr]):
res[account_id] = safe_eval(replaced_exprs[0], eval_dict)
+
+ return res
+
+ def _eval_by_rdi(self, expr):
+ res = {}
+ eval_dict = {"AccountingNone": AccountingNone}
+ for rdi, replaced_exprs in self.aep.replace_exprs_by_row_detail([expr]):
+ res[rdi] = safe_eval(replaced_exprs[0], eval_dict)
return res
def test_sanity_check(self):
@@ -240,6 +293,43 @@ def test_aep_basic(self):
# TODO allocate profits, and then...
+ def test_aep_with_row_details(self):
+ self.aep.done_parsing()
+ self._do_queries(
+ datetime.date(self.curr_year, 3, 1),
+ datetime.date(self.curr_year, 3, 31),
+ auto_expand_col_name="partner_id",
+ )
+ initial = self._eval_by_rdi("crdi[]")
+ self.assertEqual(
+ initial,
+ {
+ self.partner_a.id: AccountingNone,
+ self.partner_b.id: AccountingNone,
+ UNCLASSIFIED_ROW_DETAIL: 300.0,
+ },
+ )
+
+ variation = self._eval_by_rdi("balp[]")
+ self.assertEqual(
+ variation,
+ {
+ self.partner_a.id: 500,
+ self.partner_b.id: -500,
+ UNCLASSIFIED_ROW_DETAIL: AccountingNone,
+ },
+ )
+
+ initial = self._eval_by_rdi("bale[400AR]")
+ self.assertEqual(
+ initial,
+ {
+ self.partner_a.id: 500,
+ self.partner_b.id: AccountingNone,
+ UNCLASSIFIED_ROW_DETAIL: 400.0,
+ },
+ )
+
def test_aep_by_account(self):
self.aep.done_parsing()
self._do_queries(
diff --git a/mis_builder/tests/test_mis_report_instance.py b/mis_builder/tests/test_mis_report_instance.py
index 55b23583c..ca90b6fa2 100644
--- a/mis_builder/tests/test_mis_report_instance.py
+++ b/mis_builder/tests/test_mis_report_instance.py
@@ -376,22 +376,22 @@ def test_multi_company_compute(self):
"company_ids": [(6, 0, self.report_instance.company_id.ids)],
}
)
+ self.report_instance.report_id.write({"auto_expand_col_name": "account_id"})
self.report_instance.report_id.kpi_ids.write({"auto_expand_accounts": True})
matrix = self.report_instance._compute_matrix()
for row in matrix.iter_rows():
- if row.account_id:
- account = self.env["account.account"].browse(row.account_id)
+ if row.row_detail_identifier:
+ account = self.env["account.account"].browse(row.row_detail_identifier)
self.assertEqual(
row.label,
- "%s %s [%s]"
- % (account.code, account.name, account.company_id.name),
+ "%s [%s]" % (account.name_get()[0][1], account.company_id.name),
)
self.report_instance.write({"multi_company": False})
matrix = self.report_instance._compute_matrix()
for row in matrix.iter_rows():
- if row.account_id:
- account = self.env["account.account"].browse(row.account_id)
- self.assertEqual(row.label, "{} {}".format(account.code, account.name))
+ if row.row_detail_identifier:
+ account = self.env["account.account"].browse(row.row_detail_identifier)
+ self.assertEqual(row.label, account.name_get()[0][1])
def test_evaluate(self):
company = self.env.ref("base.main_company")
diff --git a/mis_builder/views/mis_report.xml b/mis_builder/views/mis_report.xml
index 0b67e914e..0b49567e3 100644
--- a/mis_builder/views/mis_report.xml
+++ b/mis_builder/views/mis_report.xml
@@ -20,6 +20,7 @@
+