Files
enterprise-addons/l10n_be_reports/tests/test_xbrl_export.py
T

232 lines
11 KiB
Python

from unittest.mock import patch
from freezegun import freeze_time
from odoo import fields
from odoo.fields import Command
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.addons.account_reports.tests.common import TestAccountReportsCommon
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestXBRLExports(TestAccountReportsCommon):
@classmethod
@TestAccountReportsCommon.setup_country('be')
def setUpClass(cls):
super().setUpClass()
cls.company.update({
'street': 'Rue du Paradis 200',
'zip': '1000',
'vat': 'BE0897223670',
'l10n_be_company_type_id': cls.env['l10n_be.company.type'].search([], limit=1).id,
})
def _generate_file(self, report, options):
""" Helper to generate the XBRL file with mocked data for the extra data method. If other
modules override this method, we don't want to test their behavior here, just the XBRL export. """
with patch.object(self.env.registry[report.custom_handler_model_name], '_get_extra_file_data', return_value={}):
return self.env[report.custom_handler_model_name].generate_xbrl_file(options)
def _get_be_report(self, chart_template):
return next(
variant for variant in self.env.ref('account_reports.annual_statements').variant_report_ids
if variant.chart_template == chart_template
)
def _activate_report_pair(self, be_report, balance_sheet_xmlid, profit_and_loss_xmlid):
be_report.section_report_ids.active = False
self.env.ref(balance_sheet_xmlid).active = True
self.env.ref(profit_and_loss_xmlid).active = True
def _generate_be_xbrl_file(self, chart_template, balance_sheet_xmlid, profit_and_loss_xmlid, date_to):
base_report = self.env.ref('account_reports.annual_statements')
be_report = self._get_be_report(chart_template)
self._activate_report_pair(be_report, balance_sheet_xmlid, profit_and_loss_xmlid)
fiscal_dates = self.company.compute_fiscalyear_dates(fields.Date.to_date(date_to))
options = self._generate_options(be_report, fiscal_dates['date_from'], fiscal_dates['date_to'])
options['report_id'] = be_report.id
options['last_deed_date'] = '2024-06-30'
return self._generate_file(base_report, options)
def _get_or_create_account(self, code, account_type):
account = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self.company.id),
('code', '=', code),
], limit=1)
return account or self.env['account.account'].create({
'name': code,
'code': code,
'account_type': account_type,
})
@freeze_time('2025-06-30')
def test_all_context_used(self):
""" Test that all context tags are used in the XBRL export. """
report_versions = [
('be_comp', [('comp_acon', 'comp_a'), ('comp_fcon', 'comp_f'), ('comp_acap', 'comp_a'), ('comp_fcap', 'comp_f')]),
('be_asso', [('asso_a', 'asso_a'), ('asso_f', 'asso_f')]),
]
for chart_template, versions in report_versions:
for version in versions:
xbrl_file = self._generate_be_xbrl_file(
chart_template=chart_template,
balance_sheet_xmlid=f'l10n_be_reports.account_financial_report_bs_{version[0]}',
profit_and_loss_xmlid=f'l10n_be_reports.account_financial_report_pl_{version[1]}',
date_to=fields.Date.today(),
)
self.assertTrue(xbrl_file)
self.assertEqual(xbrl_file['file_type'], 'xml')
file_content = self.get_xml_tree_from_string(xbrl_file['file_content'])
# Check that the root element is 'xbrl'
self.assertTrue(file_content.tag.endswith('xbrl'))
context = file_content.findall('.//{*}context')
context_refs = file_content.findall('.//*[@contextRef]')
context_ids = {c.attrib.get('id') for c in context}
context_ref_values = {c.attrib.get('contextRef') for c in context_refs}
missing_context_ids = context_ref_values - context_ids
self.assertEqual(len(missing_context_ids), 0, f"Each contextRef should have a corresponding context element in XBRL template: {version}. Missing {missing_context_ids} context ids.")
# Check that each context id is unique
self.assertEqual(len(context), len(context_ids), f"Each context id should be unique in XBRL template: {version}")
self.assertEqual(len(context_refs), len(context_ref_values), f"Each contextRef should be unique in XBRL template: {version}")
# Check that each contextRef matches an existing context id
for value in context_refs:
self.assertIn(value.attrib.get('contextRef'), context_ids, f"contextRef should match an existing context id in XBRL template: {version}")
# Check that required context tags are present
expected_context_vars = {
'registration_number', 'date', 'start_period', 'end_period',
'company_name', 'company_type', 'company_street', 'company_house_number',
'company_postal_code', 'company_country', 'company_region', 'last_deed_date',
}
for var in expected_context_vars:
self.assertIn(var, context_ids, f"Context variable '{var}' is missing in XBRL template: {version}")
def test_wrong_report_selection(self):
""" Test that an error is raised when the selected reports do not match the XBRL export requirements. """
base_report = self.env.ref('account_reports.annual_statements')
be_report = self._get_be_report('be_comp')
# Try to generate XBRL from non-Belgian report
options = self._generate_options(base_report, False, fields.Date.today())
with self.assertRaisesRegex(UserError, r"XBRL export is only applicable for Belgium reports. Please change to Belgium annual statement report."):
self._generate_file(base_report, options)
# Using Belgian report, select only one report instead of a pair of balance sheet and profit & loss
be_report.section_report_ids.active = False
self.env.ref('l10n_be_reports.account_financial_report_bs_comp_acon').active = True
options = self._generate_options(be_report, False, fields.Date.today())
options['report_id'] = be_report.id
with self.assertRaisesRegex(UserError, r"Please add a single balance sheet and a single profit and loss report that have a matching \(Abbr or Full\) format for the XBRL export."):
self._generate_file(base_report, options)
# Now select a pair of reports with initial wrong selection
self.env.ref('l10n_be_reports.account_financial_report_bs_comp_acap').active = True
self.env.ref('l10n_be_reports.account_financial_report_pl_comp_a').active = True
with self.assertRaisesRegex(UserError, r"Please add a single balance sheet and a single profit and loss report that have a matching \(Abbr or Full\) format for the XBRL export."):
self._generate_file(base_report, options)
# Now with a valid pair of reports
self._activate_report_pair(
be_report,
'l10n_be_reports.account_financial_report_bs_comp_fcon',
'l10n_be_reports.account_financial_report_pl_comp_f',
)
xbrl_file = self._generate_file(base_report, options)
self.assertTrue(xbrl_file)
self.assertEqual(xbrl_file['file_type'], 'xml')
def test_alphanumeric_zip_code(self):
"""
Test that the Belgian region is correctly computed for a valid numeric ZIP code
and that it is safely ignored for non-numeric ZIP codes.
"""
self.company.l10n_be_region_id = False
self.company.zip = 'R93R2R3'
self.assertFalse(self.company.l10n_be_region_id)
self.company.zip = '1000'
self.assertTrue(self.company.l10n_be_region_id)
def test_all_disclosures_present_in_xbrl(self):
counterpart_account = self.company_data['default_journal_bank'].default_account_id
move_specs = [
('280000', 'asset_non_current', 100.0, 0.0, {}),
('280800', 'asset_non_current', 20.0, 0.0, {}),
('280900', 'asset_non_current', 0.0, 5.0, {}),
('280100', 'asset_non_current', 0.0, 7.0, {}),
('220000', 'asset_fixed', 200.0, 0.0, {}),
('220008', 'asset_fixed', 30.0, 0.0, {}),
('220009', 'asset_fixed', 0.0, 40.0, {}),
('174', 'liability_non_current', 0.0, 50.0, {'date_maturity': '2027-06-15'}),
('174', 'liability_non_current', 0.0, 60.0, {'date_maturity': '2032-06-15'}),
('630000', 'expense_depreciation', 11.0, 0.0, {}),
('630100', 'expense_depreciation', 12.0, 0.0, {}),
('630800', 'expense_other', 13.0, 0.0, {}),
('630200', 'expense_depreciation', 14.0, 0.0, {}),
('630900', 'expense_other', 15.0, 0.0, {}),
]
for code, account_type, debit, credit, extra_vals in move_specs:
account = self._get_or_create_account(code, account_type)
move = self.env['account.move'].create({
'move_type': 'entry',
'date': '2025-06-15',
'journal_id': self.company_data['default_journal_misc'].id,
'line_ids': [
Command.create({
'account_id': account.id,
'debit': debit,
'credit': credit,
**extra_vals,
}),
Command.create({
'account_id': counterpart_account.id,
'debit': credit,
'credit': debit,
}),
],
})
move.action_post()
xbrl_file = self._generate_be_xbrl_file(
chart_template='be_comp',
balance_sheet_xmlid='l10n_be_reports.account_financial_report_bs_comp_acon',
profit_and_loss_xmlid='l10n_be_reports.account_financial_report_pl_comp_a',
date_to='2025-12-31',
)
file_content = self.get_xml_tree_from_string(xbrl_file['file_content'])
expected_disclosures = {
'exp_disclosure_financial_acquisition': 100.0,
'exp_disclosure_financial_capital_gains': 20.0,
'exp_disclosure_financial_amount_written_down': 5.0,
'exp_disclosure_financial_uncalled_amount': 7.0,
'exp_disclosure_tangible_acquisition': 200.0,
'exp_disclosure_tangible_revaluation_surplus': 30.0,
'exp_disclosure_tangible_amount_written_down': 40.0,
'exp_disclosure_payable_more_than_one_less_than_five': 50.0,
'exp_disclosure_payable_more_than_five': 60.0,
'exp_disclosure_depreciation_on_formation_expense': 11.0,
'exp_disclosure_depreciation_on_intangible_assets': 54.0,
}
for context_ref, expected_value in expected_disclosures.items():
disclosure_values = file_content.findall(f'.//*[@contextRef="{context_ref}"]')
self.assertTrue(disclosure_values, f"Expected disclosure {context_ref} to be present in the XBRL export.")
self.assertEqual(float(disclosure_values[0].text), expected_value)