diff --git a/mis_builder/models/kpimatrix.py b/mis_builder/models/kpimatrix.py index dc23b12a7..63c706d7e 100644 --- a/mis_builder/models/kpimatrix.py +++ b/mis_builder/models/kpimatrix.py @@ -139,12 +139,18 @@ def __init__( class KpiMatrix: - def __init__(self, env, multi_company=False, account_model="account.account"): + def __init__( + self, + env, + companies=None, + account_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._companies = companies # data structures # { kpi: KpiMatrixRow } self._kpi_rows = OrderedDict() @@ -158,7 +164,6 @@ def __init__(self, env, multi_company=False, account_model="account.account"): self._sum_todo = {} # { account_id: account_name } self._account_names = {} - self._multi_company = multi_company def declare_kpi(self, kpi): """Declare a new kpi (row) in the matrix. @@ -467,10 +472,34 @@ def _load_account_names(self): self._account_names = {a.id: self._get_account_name(a) for a in accounts} def _get_account_name(self, account): - result = f"{account.code} {account.name}" - if self._multi_company: - result = f"{result} [{account.company_id.name}]" - return result + # display_name is account code + account name. Note the account may have + # no code for the user current active company, in which case only the + # name is displayed. It is consistent with other places where accounts + # are displayed in Odoo. + account_companies = ( + account.company_ids & self._companies + if self._companies + else account.company_ids + ) + if len(account_companies) == 1: + # When there is no ambiguity on the company, use it to compute the label + account_name = account.with_company(account_companies).display_name + else: + # Otherwise use the default Odoo behaviour to get the account label + # (this may return a name without code) + account_name = account.display_name + is_multi_company = self._companies and len(self._companies) > 1 + if is_multi_company and len(account_companies) == 1: + # In a multi-company report, if the account is bound to one + # company, it makes sense to show the company name. If the account + # is bound to multiple companies it does not make sense, because we + # don't know to which companies this detail line effectively + # contributes, so the list of companies in it would not add useful + # information. To be able to accurately display the company on + # detail lines when the account is bound to multiple companies, + # we'll need a generalized kpi details expansion. + account_name = f"{account_name} [{account_companies.display_name}]" + return account_name def get_account_name(self, account_id): if account_id not in self._account_names: diff --git a/mis_builder/models/mis_report.py b/mis_builder/models/mis_report.py index c4442f239..98907a5cf 100644 --- a/mis_builder/models/mis_report.py +++ b/mis_builder/models/mis_report.py @@ -539,9 +539,9 @@ def copy(self, default=None): # TODO: kpi name cannot be start with query name - def prepare_kpi_matrix(self, multi_company=False): + def prepare_kpi_matrix(self, companies=None): self.ensure_one() - kpi_matrix = KpiMatrix(self.env, multi_company, self.account_model) + kpi_matrix = KpiMatrix(self.env, companies, self.account_model) for kpi in self.kpi_ids: kpi_matrix.declare_kpi(kpi) return kpi_matrix diff --git a/mis_builder/models/mis_report_instance.py b/mis_builder/models/mis_report_instance.py index 9acf6fe63..64e51a536 100644 --- a/mis_builder/models/mis_report_instance.py +++ b/mis_builder/models/mis_report_instance.py @@ -882,8 +882,7 @@ def _compute_matrix(self): """ self.ensure_one() aep = self.report_id._prepare_aep(self.query_company_ids, self.currency_id) - multi_company = self.multi_company and len(self.query_company_ids) > 1 - kpi_matrix = self.report_id.prepare_kpi_matrix(multi_company) + kpi_matrix = self.report_id.prepare_kpi_matrix(self.query_company_ids) for period in self.period_ids: description = None if period.mode == MODE_NONE: diff --git a/mis_builder/tests/test_mis_report_instance.py b/mis_builder/tests/test_mis_report_instance.py index ba8d83aed..37acf04e7 100644 --- a/mis_builder/tests/test_mis_report_instance.py +++ b/mis_builder/tests/test_mis_report_instance.py @@ -490,6 +490,68 @@ def test_drilldown_views(self): [[False, "list"], [False, "form"], [False, "pivot"], [False, "graph"]], ) + def test_multicompany_account_code_display(self): + """Account codes should display correctly in multi-company reports. + + In Odoo 18, account.code is company-dependent. When a report belongs + to a different company than the user's current company, account codes + must still display correctly in auto-expanded rows. + """ + company2 = self.env["res.company"].create({"name": "Test Co 2"}) + account = ( + self.env["account.account"] + .with_company(company2) + .create( + { + "name": "Test Account", + "code": "999001", + "account_type": "expense", + "company_ids": [(6, 0, [company2.id])], + } + ) + ) + # Verify code is visible from company2 but not from main company + self.assertEqual(account.with_company(company2).code, "999001") + self.assertFalse(account.with_company(self.env.ref("base.main_company")).code) + # Create report + instance for company2 + report = self.env["mis.report"].create({"name": "MC Test Report"}) + self.env["mis.report.kpi"].create( + { + "report_id": report.id, + "name": "exp", + "description": "Test Expense", + "auto_expand_accounts": True, + "sequence": 1, + "expression_ids": [(0, 0, {"name": "balp[999%]"})], + } + ) + instance = self.env["mis.report.instance"].create( + { + "name": "MC Test Instance", + "report_id": report.id, + "company_id": company2.id, + "period_ids": [ + ( + 0, + 0, + { + "name": "2024", + "mode": "fix", + "manual_date_from": "2024-01-01", + "manual_date_to": "2024-12-31", + }, + ), + ], + } + ) + matrix = instance.compute() + body = matrix.get("body", []) + has_false = any("False" in (r.get("label") or "") for r in body) + self.assertFalse( + has_false, + "Account codes should not show as 'False' in multi-company reports", + ) + def test_qweb(self): self.report_instance.print_pdf() # get action test_reports.try_report(