232 lines
11 KiB
Python
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)
|