diff --git a/mis_builder/models/kpimatrix.py b/mis_builder/models/kpimatrix.py index dc23b12a7..64babc45b 100644 --- a/mis_builder/models/kpimatrix.py +++ b/mis_builder/models/kpimatrix.py @@ -139,12 +139,19 @@ def __init__( class KpiMatrix: - def __init__(self, env, multi_company=False, account_model="account.account"): + def __init__( + self, + env, + multi_company=False, + query_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._query_companies = query_companies # data structures # { kpi: KpiMatrixRow } self._kpi_rows = OrderedDict() @@ -467,9 +474,18 @@ 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}" + code = account.code + # In Odoo 18, account.code is company-dependent. If the current env + # company doesn't match the account's company, code may be False. + # Read the code from the account's owning company. + if not code and self._query_companies: + account_companies = account.company_ids & self._query_companies + if account_companies: + code = account.with_company(account_companies[0]).code + result = f"{code} {account.name}" if code else account.name if self._multi_company: - result = f"{result} [{account.company_id.name}]" + company_names = ", ".join(account.company_ids.mapped("name")) + result = f"{result} [{company_names}]" return result def get_account_name(self, account_id): diff --git a/mis_builder/models/mis_report.py b/mis_builder/models/mis_report.py index c4442f239..e954db8e8 100644 --- a/mis_builder/models/mis_report.py +++ b/mis_builder/models/mis_report.py @@ -539,9 +539,11 @@ 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, multi_company=False, query_companies=None): self.ensure_one() - kpi_matrix = KpiMatrix(self.env, multi_company, self.account_model) + kpi_matrix = KpiMatrix( + self.env, multi_company, query_companies, self.account_model + ) for kpi in self.kpi_ids: kpi_matrix.declare_kpi(kpi) return kpi_matrix @@ -619,7 +621,7 @@ def _fetch_queries(self, date_from, date_to, get_additional_query_filter=None): v = data[0][field_name] except KeyError: _logger.error( - "field %s not found in read_group " "for %s; not summable?", + "field %s not found in read_group for %s; not summable?", field_name, model._name, ) diff --git a/mis_builder/models/mis_report_instance.py b/mis_builder/models/mis_report_instance.py index 9acf6fe63..29bf2cb84 100644 --- a/mis_builder/models/mis_report_instance.py +++ b/mis_builder/models/mis_report_instance.py @@ -423,8 +423,7 @@ def _check_mode_source(self): if rec.mode == MODE_NONE: raise DateFilterRequired( self.env._( - "A date filter is mandatory for this source " - "in column %s.", + "A date filter is mandatory for this source in column %s.", rec.name, ) ) @@ -432,8 +431,7 @@ def _check_mode_source(self): if rec.mode != MODE_NONE: raise DateFilterForbidden( self.env._( - "No date filter is allowed for this source " - "in column %s.", + "No date filter is allowed for this source in column %s.", rec.name, ) ) @@ -460,8 +458,7 @@ def _check_source_cmpcol(self): ): raise ValidationError( self.env._( - "Columns to compare must belong to the same report " - "in %s", + "Columns to compare must belong to the same report in %s", rec.name, ) ) @@ -499,7 +496,7 @@ def _compute_pivot_date(self): sequence = fields.Integer(default=10) description = fields.Char(related="report_id.description") date = fields.Date( - string="Base date", help="Report base date " "(leave empty to use current date)" + string="Base date", help="Report base date (leave empty to use current date)" ) pivot_date = fields.Date(compute="_compute_pivot_date") report_id = fields.Many2one("mis.report", required=True, string="Report") @@ -765,9 +762,7 @@ def get_views(self, views, options=None): context.get("from_dashboard") and context.get("active_model") == "mis.report.instance" ): - view_id = self.env.ref( - "mis_builder." "mis_report_instance_result_view_form" - ) + view_id = self.env.ref("mis_builder.mis_report_instance_result_view_form") mis_report_form_view = view_id and [view_id.id, "form"] for view in views: if view and view[1] == "form": @@ -778,7 +773,7 @@ def get_views(self, views, options=None): def preview(self): self.ensure_one() - view_id = self.env.ref("mis_builder." "mis_report_instance_result_view_form") + view_id = self.env.ref("mis_builder.mis_report_instance_result_view_form") return { "type": "ir.actions.act_window", "res_model": "mis.report.instance", @@ -883,7 +878,9 @@ 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( + multi_company, 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(