import datetime import unittest from collections import Counter from configparser import ConfigParser from decimal import Decimal from pathlib import Path from xml.etree import ElementTree as ET from trytond.pool import Pool from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.transaction import Transaction MODULE_DIR = Path(__file__).resolve().parent.parent TAX_CODE_LINE_AUDIT = { 'do_tcl_itbis18v_inv': '+', 'do_tcl_itbis18v_cr': '-', 'do_tcl_itbis18c_inv': '-', 'do_tcl_itbis18c_cr': '+', 'do_tcl_ret_itbis_inv': '-', 'do_tcl_ret_itbis_cr': '+', 'do_tcl_ret_itbis_inf75_inv': '-', 'do_tcl_ret_itbis_inf75_cr': '+', 'do_tcl_isr_hon_inv': '-', 'do_tcl_isr_hon_cr': '+', 'do_tcl_isr_est_inv': '+', 'do_tcl_isr_est_cr': '-', 'do_tcl_isc_comb_inv': '+', 'do_tcl_isc_comb_cr': '-', 'do_tcl_chq_inv': '+', 'do_tcl_chq_cr': '-', 'do_tcl_chq020_inv': '+', 'do_tcl_chq020_cr': '-', } IFRS_REQUIRED_ACCOUNT_CODES = { '1', '11', '12', '1101', '1102', '1103', '1104', '1105', '1106', '1107', '1110', '110101', '110103', '110104', '110201', '110205', '110301', '110305', '110601', '110701', '110702', '110703', '111001', '111002', '1201', '1205', '1206', '1207', '1208', '1209', '120101', '120191', '120192', '120501', '120591', '120592', '120601', '120701', '120702', '120703', '120801', '120891', '120892', '120901', '120991', '120992', '2', '21', '22', '23', '2101', '2104', '2105', '2106', '2201', '2301', '2302', '2303', '2304', '210101', '210401', '210501', '210601', '220101', '230201', '230202', '230301', '230401', '3', '31', '3101', '3104', '3105', '3106', '310601', '310604', '4', '41', '42', '4101', '4102', '4103', '4104', '4201', '4208', '4209', '4210', '5', '51', '5101', '5102', '5106', '6', '61', '62', '63', '6101', '6103', '6111', '6112', '6115', '6116', '6117', '6118', '6119', '6120', '6201', '6203', '6206', '6207', '6301', '6302', '6303', '7', '7101', '7102', } IFRS_ACCOUNT_TYPE_AUDIT = { '111002': 'do_type_current_asset', '120192': 'do_type_fixed_asset', '120292': 'do_type_fixed_asset', '120392': 'do_type_fixed_asset', '120591': 'do_type_rou_asset', '120592': 'do_type_rou_asset', '120891': 'do_type_investment_property', '120892': 'do_type_investment_property', '120992': 'do_type_intangible', '7101': 'do_type_control', '7102': 'do_type_control', } IFRS_POLICY_COVERAGE = { 'NIC 1': {'11', '12', '21', '23', '3106', '7101', '7102'}, 'NIC 2': {'1103', '110305', '5101', '5106'}, 'NIIF 9': { '110205', '110701', '110702', '110703', '120701', '120702', '120703', '4208', '6206', '310604'}, 'NIIF 15': {'110601', '210501', '4101', '4102', '4103', '4104'}, 'NIIF 16': { '120501', '120591', '120592', '230201', '230202', '6112', '6203'}, 'NIC 12': {'120601', '230301', '6301', '6302', '6303'}, 'NIC 16': {'120101', '120191', '120192', '6110', '6116'}, 'NIC 36': { '111002', '120192', '120292', '120392', '120592', '120892', '120992', '6116', '6117', '4206'}, 'NIC 37': {'220101', '230401', '6118', '4207'}, 'NIC 38': {'120901', '120991', '120992', '6111', '6117'}, 'NIC 40': {'120801', '120891', '120892', '4209', '6207'}, 'NIIF 5': {'111001', '111002', '210601', '4210', '6120'}, } def _iter_xml_records(*filenames): for filename in filenames: path = MODULE_DIR / filename root = ET.parse(path).getroot() for record in root.findall('.//record'): english = filename.endswith('_en.xml') values = { field.get('name'): ( field.get('ref') or field.get('eval') or field.text or '') for field in record.findall('field') } record_id = record.get('id') if english: record_id = record_id.removesuffix('_en') values = { name: value.removesuffix('_en') if name not in {'name', 'description'} else value for name, value in values.items() } yield filename, record_id, record.get('model'), values class AccountDoTestCase(ModuleTestCase): "Test account_do module" module = 'account_do' extras = [ 'account_asset', 'account_deposit', 'account_stock_continental', ] @with_transaction() def test_chart_template_is_loaded_by_default(self): 'Test English Dominican chart is loaded in an English database' pool = Pool() ModelData = pool.get('ir.model.data') self.assertTrue(ModelData.get_id('account_do', 'do_account_root_en')) @with_transaction() def test_default_properties_ignore_unrelated_chart(self): 'Test account_do does not override defaults for another chart' pool = Pool() AccountTemplate = pool.get('account.account.template') Company = pool.get('company.company') Currency = pool.get('currency.currency') ModelData = pool.get('ir.model.data') Party = pool.get('party.party') CreateChart = pool.get('account.create_chart', type='wizard') currency, = Currency.create([{ 'name': 'Test Currency', 'code': 'TST', 'symbol': 'T', 'digits': 2, 'rounding': '0.01', }]) party, = Party.create([{'name': 'Unrelated Chart Company'}]) company, = Company.create([{ 'party': party.id, 'currency': currency.id, }]) template = AccountTemplate(ModelData.get_id( 'account', 'account_template_root_en')) session_id, _start, _end = CreateChart.create() chart = CreateChart(session_id) chart.account.account_template = template chart.account.company = company defaults = chart.default_properties([ 'company', 'account_receivable', 'account_payable']) self.assertEqual(defaults['company'], company.id) self.assertIsNone(defaults['account_receivable']) self.assertIsNone(defaults['account_payable']) CreateChart.delete(session_id) @with_transaction() def test_spanish_chart_creates_spanish_accounts_and_taxes(self): 'Test the es_419 chart creates a complete independent chart' pool = Pool() Account = pool.get('account.account') AccountTemplate = pool.get('account.account.template') Company = pool.get('company.company') Currency = pool.get('currency.currency') ModelData = pool.get('ir.model.data') Party = pool.get('party.party') Tax = pool.get('account.tax') TaxCode = pool.get('account.tax.code') CreateChart = pool.get('account.create_chart', type='wizard') currency, = Currency.create([{ 'name': 'Peso dominicano', 'code': 'DOP', 'symbol': 'RD$', 'digits': 2, 'rounding': '0.01', }]) party, = Party.create([{'name': 'Empresa Dominicana'}]) company, = Company.create([{ 'party': party.id, 'currency': currency.id, }]) try: template_id = ModelData.get_id( 'account_do', 'do_account_root_es_419') except KeyError: self.skipTest( 'The es_419 chart is loaded only for es_419 databases') template = AccountTemplate(template_id) session_id, _start, _end = CreateChart.create() chart = CreateChart(session_id) chart.account.account_template = template chart.account.company = company chart.transition_create_account() receivable, = Account.search([ ('company', '=', company.id), ('code', '=', '110201'), ], limit=1) payable, = Account.search([ ('company', '=', company.id), ('code', '=', '210101'), ], limit=1) defaults = chart.default_properties([ 'company', 'account_receivable', 'account_payable']) self.assertEqual(receivable.name, 'Cuentas por cobrar clientes') self.assertEqual(payable.name, 'Cuentas por pagar proveedores') self.assertEqual(defaults['account_receivable'], receivable.id) self.assertEqual(defaults['account_payable'], payable.id) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'ITBIS 18% Ventas'), ('rate', '=', Decimal('0.18')), ], limit=1)) self.assertTrue(TaxCode.search([ ('company', '=', company.id), ('name', '=', 'Otros Impuestos y Contribuciones'), ], limit=1)) CreateChart.delete(session_id) @with_transaction() def test_chart_creates_company_accounts_and_taxes(self): 'Test Dominican chart creates usable accounts and taxes' pool = Pool() Account = pool.get('account.account') AccountTemplate = pool.get('account.account.template') Company = pool.get('company.company') Currency = pool.get('currency.currency') ModelData = pool.get('ir.model.data') Party = pool.get('party.party') Tax = pool.get('account.tax') TaxCode = pool.get('account.tax.code') TaxCodeLine = pool.get('account.tax.code.line') TaxRule = pool.get('account.tax.rule') CreateChart = pool.get('account.create_chart', type='wizard') currency, = Currency.create([{ 'name': 'Dominican Peso', 'code': 'DOP', 'symbol': 'RD$', 'digits': 2, 'rounding': '0.01', }]) party, = Party.create([{ 'name': 'Empresa Dominicana', }]) company, = Company.create([{ 'party': party.id, 'currency': currency.id, }]) template = AccountTemplate(ModelData.get_id( 'account_do', 'do_account_root_en')) session_id, _start, _end = CreateChart.create() chart = CreateChart(session_id) chart.account.account_template = template chart.account.company = company chart.transition_create_account() receivable, = Account.search([ ('company', '=', company.id), ('code', '=', '110201'), ('type.receivable', '=', True), ('party_required', '=', True), ('closed', '!=', True), ], limit=1) payable, = Account.search([ ('company', '=', company.id), ('code', '=', '210101'), ('type.payable', '=', True), ('party_required', '=', True), ('closed', '!=', True), ], limit=1) defaults = chart.default_properties([ 'company', 'account_receivable', 'account_payable']) self.assertEqual(defaults['company'], company.id) self.assertEqual(defaults['account_receivable'], receivable.id) self.assertEqual(defaults['account_payable'], payable.id) for code in [ '111002', '120192', '120292', '120392', '120591', '120592', '120891', '120892', '120992', '7101', '7102', '110401', '110406', '110407', '110408', '11040101', '11040102', '11040103', '11040104', '11040601', '11040801', '11040802', '11040803', '210201', '210202', '210203', '210206', '210207', '210208', '210209', '210210', '210211', '210212', '210213', '210214', '210215', '210216', '210217', '210218', '210219', '21020101', '21020102', '21020103', '21020104', '21020105', '21020201', '21020501', '21020502', '21020601', '21020701', '21020702', '21020801', '21020802', '21020803', '21020804', '21020805', '21020901', '21021001', '21021101', '21021201', '21021301', '21021302', '21021401', '21021501', '21021502', '21021701', '21021801', '21021901', '21021902', '6208']: with self.subTest(code=code): self.assertTrue(Account.search([ ('company', '=', company.id), ('code', '=', code), ('closed', '!=', True), ], limit=1)) company_ifrs_types = { '111002': 'Current Assets', '120591': 'Right-of-use Assets', '120592': 'Right-of-use Assets', '120891': 'Investment Property', '120892': 'Investment Property', '120992': 'Intangible assets', '7101': 'Closing and control accounts', '7102': 'Closing and control accounts', } for code, account_type in company_ifrs_types.items(): with self.subTest(code=code, account_type=account_type): account, = Account.search([ ('company', '=', company.id), ('code', '=', code), ('closed', '!=', True), ], limit=1) self.assertEqual(account.type.name, account_type) self.assertGreaterEqual(len(Account.search([ ('company', '=', company.id), ])), 200) self.assertGreaterEqual(len(Tax.search([ ('company', '=', company.id), ])), 25) self.assertTrue(TaxCode.search([ ('company', '=', company.id), ('name', '=', 'ITBIS — Net Balance (Debit − Credit)'), ], limit=1)) self.assertTrue(TaxRule.search([ ('company', '=', company.id), ('kind', '=', 'sale'), ], limit=1)) self.assertTrue(TaxRule.search([ ('company', '=', company.id), ('kind', '=', 'purchase'), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'ITBIS 18% Sales'), ('rate', '=', Decimal('0.18')), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'ITBIS Withholding Large Taxpayer 30%'), ('rate', '=', Decimal('-0.054')), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'ITBIS Withholding Informal Supplier 75%'), ('rate', '=', Decimal('-0.135')), ], limit=1)) check_015, = Tax.search([ ('company', '=', company.id), ('description', '=', 'Checks and Transfers Tax 0.15%'), ], limit=1) check_020, = Tax.search([ ('company', '=', company.id), ('description', '=', 'Checks and Transfers Tax 0.20%'), ], limit=1) bank_rule, = TaxRule.search([ ('company', '=', company.id), ('name', '=', ( 'Banking Rule Checks / Electronic Transfers ' '(RD)')), ], limit=1) self.assertEqual(check_015.end_date, datetime.date(2026, 7, 2)) self.assertEqual(check_020.start_date, datetime.date(2026, 7, 3)) self.assertEqual( bank_rule.apply(check_015, { 'date': datetime.date(2026, 7, 2), }), [check_015.id]) self.assertEqual( bank_rule.apply(check_015, { 'date': datetime.date(2026, 7, 3), }), [check_020.id]) real_tax_code_signs = { ('ITBIS 18% Sales', 'invoice'): '+', ('ITBIS 18% Sales', 'credit'): '-', ('ITBIS 18% Purchases', 'invoice'): '-', ('ITBIS 18% Purchases', 'credit'): '+', ('ITBIS Withholding Large Taxpayer 30%', 'invoice'): '-', ('ITBIS Withholding Large Taxpayer 30%', 'credit'): '+', ('ISR Withholding Individual 10%', 'invoice'): '-', ('ISR Withholding Individual 10%', 'credit'): '+', ('ISR Withholding State 1.5%', 'invoice'): '+', ('ISR Withholding State 1.5%', 'credit'): '-', ('Checks and Transfers Tax 0.20%', 'invoice'): '+', ('Checks and Transfers Tax 0.20%', 'credit'): '-', } for (description, line_type), operator in real_tax_code_signs.items(): with self.subTest(description=description, line_type=line_type): tax, = Tax.search([ ('company', '=', company.id), ('description', '=', description), ], limit=1) line, = TaxCodeLine.search([ ('tax', '=', tax.id), ('type', '=', line_type), ('amount', '=', 'tax'), ], limit=1) self.assertEqual(line.operator, operator) chart.properties.company = company chart.properties.account_receivable = receivable chart.properties.account_payable = payable with Transaction().set_context(company=company.id): chart.transition_create_properties() CreateChart.delete(session_id) class SpanishAccountDoTestCase(ModuleTestCase): "Test the Latin American Spanish account_do data set" module = 'account_do' extras = AccountDoTestCase.extras language = 'es_419' test_spanish_chart_creates_spanish_accounts_and_taxes = ( AccountDoTestCase .test_spanish_chart_creates_spanish_accounts_and_taxes) @with_transaction() def test_migrate_pre_language_identifiers(self): 'Test 8.0.0 identifiers migrate without deleting referenced records' pool = Pool() AccountTemplate = pool.get('account.account.template') ModelData = pool.get('ir.model.data') root_id = ModelData.get_id( 'account_do', 'do_account_root_es_419') root_data, = ModelData.search([ ('module', '=', 'account_do'), ('fs_id', '=', 'do_account_root_es_419'), ]) ModelData.write([root_data], {'fs_id': 'do_account_root'}) legacy, = AccountTemplate.create([{ 'name': 'Legacy Obsolete Tax Placeholder', 'parent': root_id, }]) ModelData.create([{ 'module': 'account_do', 'fs_id': 'do_tax_itbis_8_venta', 'model': 'account.account.template', 'db_id': legacy.id, 'noupdate': False, }]) ModelData._migrate_localization_identifiers() self.assertEqual( ModelData.get_id('account_do', 'do_account_root_es_419'), root_id) self.assertFalse(ModelData.search([ ('module', '=', 'account_do'), ('fs_id', '=', 'do_tax_itbis_8_venta'), ])) class AccountDoUnitTestCase(unittest.TestCase): def test_static_project_configuration_is_complete(self): config = ConfigParser() config.read(MODULE_DIR / 'tryton.cfg', encoding='utf-8') def lines(section, option): return [ line.strip() for line in config.get(section, option).splitlines() if line.strip() ] self.assertEqual(config.get('tryton', 'version'), '8.1.0') self.assertEqual( set(lines('tryton', 'depends')), {'account', 'ir'}) self.assertEqual( lines('tryton', 'xml'), [ 'account_chart_do_en.xml', 'tax_do_en.xml', 'tax_code_do_en.xml', 'tax_rule_do_en.xml', 'account_chart_do_es_419.xml', 'tax_do_es_419.xml', 'tax_code_do_es_419.xml', 'tax_rule_do_es_419.xml', ]) self.assertEqual(lines('register', 'model'), ['ir.ModelData']) self.assertEqual(lines('register', 'wizard'), ['account.CreateChart']) for filename in lines('tryton', 'xml'): self.assertTrue((MODULE_DIR / filename).is_file()) self.assertTrue((MODULE_DIR / 'account.py').is_file()) pyproject = (MODULE_DIR / 'pyproject.toml').read_text(encoding='utf-8') self.assertIn("name = 'trytond_account_do'", pyproject) self.assertIn( "account_do = 'trytond.modules.account_do'", pyproject) self.assertIn( '{name = "Fundación Un País Mejor"}', pyproject) self.assertIn( 'homepage = "https://www.unpaismejor.org.do"', pyproject) self.assertIn( ('repository = "https://code.unpaismejor.org.do/' 'tryton-do/account_do"'), pyproject) self.assertIn("exclude = ['doc']", pyproject) for package_file in ['**/*.xml', 'tests/**/*.rst']: self.assertIn(package_file, pyproject) for filename in [ '__init__.py', 'account.py', 'doc/conf.py', 'tests/test_scenario.py', ]: with self.subTest(filename=filename): source = (MODULE_DIR / filename).read_text(encoding='utf-8') self.assertIn('This file is part of Tryton.', source) account = (MODULE_DIR / 'account.py').read_text(encoding='utf-8') self.assertIn("class CreateChart(metaclass=PoolMeta):", account) self.assertIn( "'account_do.do_account_110201_en'", account) self.assertIn( "'account_do.do_account_210101_en'", account) readme = (MODULE_DIR / 'README.rst').read_text(encoding='utf-8') self.assertNotIn('100% de la base estructural', readme) self.assertNotIn('catalogo_cuentas_niif_rd_tryton_completo', readme) for filename in [ 'doc/conf.py', 'doc/design.rst', 'doc/reference.rst', 'doc/releases.rst', ]: self.assertTrue((MODULE_DIR / filename).is_file()) def test_xml_record_inventory_is_explicit(self): expected = { 'account_chart_do_en.xml': { 'account.account.type.template': 36, 'account.account.template': 281, }, 'tax_do_en.xml': { 'account.tax.group': 7, 'account.tax.template': 50, }, 'tax_code_do_en.xml': { 'account.tax.code.template': 44, 'account.tax.code.line.template': 93, }, 'tax_rule_do_en.xml': { 'account.tax.rule.template': 23, 'account.tax.rule.line.template': 30, }, 'account_chart_do_es_419.xml': { 'account.account.type.template': 36, 'account.account.template': 281, }, 'tax_do_es_419.xml': { 'account.tax.group': 7, 'account.tax.template': 50, }, 'tax_code_do_es_419.xml': { 'account.tax.code.template': 44, 'account.tax.code.line.template': 93, }, 'tax_rule_do_es_419.xml': { 'account.tax.rule.template': 23, 'account.tax.rule.line.template': 30, }, } for filename, expected_counts in expected.items(): records = list(_iter_xml_records(filename)) with self.subTest(filename=filename): self.assertEqual( dict(Counter(model for _, _, model, _ in records)), expected_counts) self.assertFalse([ (record_id, model) for _, record_id, model, _ in records if not record_id or not model]) duplicate_ids = [ record_id for record_id, count in Counter( record_id for _, record_id, _, _ in records).items() if count > 1] self.assertEqual(duplicate_ids, []) def test_xml_data_is_scoped_to_its_language(self): 'Every data section follows the language-specific chart convention' for language in ['en', 'es_419']: for stem in [ 'account_chart_do', 'tax_do', 'tax_code_do', 'tax_rule_do']: filename = f'{stem}_{language}.xml' root = ET.parse(MODULE_DIR / filename).getroot() with self.subTest(filename=filename): self.assertTrue(root.findall('data')) self.assertEqual( {data.get('language') for data in root.findall('data')}, {language}) def test_english_accounting_terminology_is_consistent(self): 'English datasets avoid literal Spanish accounting translations' text = '\n'.join( field.text.strip() for filename in [ 'account_chart_do_en.xml', 'tax_do_en.xml', 'tax_code_do_en.xml', 'tax_rule_do_en.xml'] for field in ET.parse(MODULE_DIR / filename).getroot().findall( './/field') if field.get('name') in {'name', 'description'} and field.text) for phrase in [ 'advance itbis', 'assets for right of use', 'charged for paying', 'clients tax rule', 'collected to pay', 'deterioration', 'in favor', 'itbis supported', 'other income and profits', 'physical persons', 'retention', 'withheld status', ]: with self.subTest(phrase=phrase): self.assertNotIn(phrase, text.lower()) def test_spanish_chart_has_complete_parallel_identifiers(self): pairs = [ ('account_chart_do_en.xml', 'account_chart_do_es_419.xml'), ('tax_do_en.xml', 'tax_do_es_419.xml'), ('tax_code_do_en.xml', 'tax_code_do_es_419.xml'), ('tax_rule_do_en.xml', 'tax_rule_do_es_419.xml'), ] for english_file, spanish_file in pairs: english = { record_id: model for _, record_id, model, _ in _iter_xml_records(english_file) } spanish = { record_id: model for _, record_id, model, _ in _iter_xml_records(spanish_file) } with self.subTest(spanish_file=spanish_file): self.assertEqual( spanish, {record_id + '_es_419': model for record_id, model in english.items()}) spanish_accounts = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do_es_419.xml') if model == 'account.account.template' } spanish_taxes = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_do_es_419.xml') if model == 'account.tax.template' } self.assertEqual( spanish_accounts['do_account_root_es_419']['name'], 'Plan de Cuentas NIIF - República Dominicana') self.assertEqual( spanish_accounts['do_account_110201_es_419']['name'], 'Cuentas por cobrar clientes') self.assertEqual( spanish_taxes['do_tax_itbis_18_venta_es_419']['description'], 'ITBIS 18% Ventas') def test_xml_references_are_resolved_inside_module(self): records = list(_iter_xml_records( 'account_chart_do_en.xml', 'tax_do_en.xml', 'tax_code_do_en.xml', 'tax_rule_do_en.xml', 'account_chart_do_es_419.xml', 'tax_do_es_419.xml', 'tax_code_do_es_419.xml', 'tax_rule_do_es_419.xml', )) ids = set() for filename in { filename for filename, _record_id, _model, _values in records}: root = ET.parse(MODULE_DIR / filename).getroot() ids.update( record.get('id') for record in root.findall('.//record')) missing = [] for filename in [ 'account_chart_do_en.xml', 'tax_do_en.xml', 'tax_code_do_en.xml', 'tax_rule_do_en.xml', 'account_chart_do_es_419.xml', 'tax_do_es_419.xml', 'tax_code_do_es_419.xml', 'tax_rule_do_es_419.xml', ]: root = ET.parse(MODULE_DIR / filename).getroot() for record in root.findall('.//record'): for field in record.findall('field'): ref = field.get('ref') if ref and '.' not in ref and ref not in ids: missing.append( (filename, record.get('id'), field.get('name'), ref)) self.assertEqual(missing, []) def test_chart_template_codes_are_unique(self): accounts = [ values for _, _, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.template' ] codes = [values['code'] for values in accounts if values.get('code')] duplicates = [ code for code, count in Counter(codes).items() if count > 1] self.assertEqual(duplicates, []) def test_chart_root_and_statement_classification(self): accounts = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.template' } types = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.type.template' } self.assertEqual([ accounts[f'do_account_{code}']['code'] for code in range(1, 8) ], ['1', '2', '3', '4', '5', '6', '7']) self.assertEqual( types['do_type_retained_earnings']['statement'], 'balance') self.assertEqual(types['do_type_revenue']['statement'], 'income') self.assertEqual(types['do_type_expense']['statement'], 'income') self.assertEqual( accounts['do_account_110201']['type'], 'do_type_receivable') self.assertEqual( accounts['do_account_210101']['type'], 'do_type_payable') def test_ifrs_chart_is_complete_and_typed(self): accounts = { values['code']: values for _, _, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.template' and values.get('code') } types = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.type.template' } self.assertLessEqual(IFRS_REQUIRED_ACCOUNT_CODES, set(accounts)) self.assertEqual(types['do_type_control']['statement'], 'off-balance') self.assertEqual( types['do_type_deferred_tax_asset']['statement'], 'balance') self.assertEqual( types['do_type_deferred_tax_liability']['statement'], 'balance') self.assertEqual(types['do_type_rou_asset']['statement'], 'balance') self.assertEqual( types['do_type_investment_property']['statement'], 'balance') self.assertEqual(types['do_type_intangible']['statement'], 'balance') missing_type = [ (code, values['name']) for code, values in accounts.items() if values.get('closed') != 'True' and not values.get('type') ] self.assertEqual(missing_type, []) for code, account_type in IFRS_ACCOUNT_TYPE_AUDIT.items(): with self.subTest(code=code): self.assertEqual(accounts[code]['type'], account_type) def test_ifrs_policy_matrix_is_represented_in_chart(self): accounts = { values['code'] for _, _, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.template' and values.get('code') } for standard, codes in IFRS_POLICY_COVERAGE.items(): with self.subTest(standard=standard): self.assertLessEqual(codes, accounts) def test_no_spanish_technical_other_tax_ids_remain(self): ids = { record_id for _, record_id, _, _ in _iter_xml_records( 'tax_do_en.xml', 'tax_code_do_en.xml') } self.assertFalse([ record_id for record_id in ids if 'otros' in record_id]) def test_tax_templates_are_complete(self): taxes = { record_id: values for _, record_id, model, values in _iter_xml_records('tax_do_en.xml') if model == 'account.tax.template' } accounts = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do_en.xml') if model == 'account.account.template' } self.assertEqual(len(taxes), 50) for record_id, values in taxes.items(): with self.subTest(record_id=record_id): self.assertEqual( values.get('credit_note_account'), values.get('invoice_account')) account_id = values.get('invoice_account') if values['type'] != 'none': self.assertTrue(account_id) self.assertIn(account_id, accounts) self.assertTrue( values.get('rate') or values.get('amount')) else: self.assertFalse(values.get('rate')) self.assertTrue(values.get('legal_notice')) self.assertIn( "Decimal('-5.4')/100", taxes['do_tax_ret_itbis_30']['rate']) self.assertIn( "Decimal('-13.5')/100", taxes['do_tax_ret_itbis_75_inf']['rate']) for record_id in [ 'do_tax_ret_isr_hon_5', 'do_tax_ret_isr_serv_10', 'do_tax_ret_isr_alq_10', 'do_tax_ret_isr_est_5', 'do_tax_ret_isr_bovine_1', 'do_tax_ret_isr_exporter_25', 'do_tax_ret_isr_ext_27', ]: self.assertIn("Decimal('-", taxes[record_id]['rate']) self.assertIn( "Decimal('10')/100", taxes['do_tax_isc_bebidas_alc']['rate']) self.assertEqual( taxes['do_tax_ret_isr_hon_5']['group'], 'do_tax_group_isr') self.assertEqual( taxes['do_tax_ret_isr_serv_10']['group'], 'do_tax_group_isr') self.assertEqual( taxes['do_tax_ret_isr_ext_27']['group'], 'do_tax_group_isr_ext') self.assertEqual( taxes['do_tax_ret_isr_ext_10']['group'], 'do_tax_group_isr_ext') self.assertEqual( taxes['do_tax_ret_isr_bovine_1']['start_date'], 'datetime.date(2025, 6, 20)') self.assertIn( 'arts. 401-405; art. 404', taxes['do_tax_activos_1']['legal_notice']) self.assertIn( 'art. 7 Ley 173-07', taxes['do_tax_iti_3']['legal_notice']) self.assertIn( 'art. 228', taxes['do_tax_propina_10']['legal_notice']) self.assertEqual( taxes['do_tax_isc_vehiculos']['group'], 'do_tax_group_others') self.assertNotIn('do_tax_itbis_8_venta', taxes) self.assertNotIn('do_tax_itbis_9_venta', taxes) self.assertEqual( taxes['do_tax_cheques_015']['end_date'], 'datetime.date(2026, 7, 2)') self.assertEqual( taxes['do_tax_cheques_020']['start_date'], 'datetime.date(2026, 7, 3)') self.assertEqual( taxes['do_tax_cheques_020']['invoice_account'], 'do_account_21021902') self.assertIn( "Decimal('0.20')/100", taxes['do_tax_cheques_020']['rate']) def test_tax_validation_register_is_complete(self): 'Every tax template is inventoried in the validation register' taxes = { record_id for _, record_id, model, _ in _iter_xml_records('tax_do_en.xml') if model == 'account.tax.template' } documentation = (MODULE_DIR / 'doc' / 'tax_validation.rst').read_text( encoding='utf-8') documented = { token.strip('`') for token in documentation.split() if token.startswith('``do_tax_') and token.endswith('``') } self.assertEqual(documented, taxes) def test_tax_code_templates_use_expected_signs(self): codes = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_code_do_en.xml') if model == 'account.tax.code.template' } lines = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_code_do_en.xml') if model == 'account.tax.code.line.template' } for record_id in [ 'do_tc_itbis', 'do_tc_isr', 'do_tc_isc', 'do_tc_cdt', 'do_tc_others', ]: self.assertEqual(codes[record_id].get('parent'), 'None') self.assertEqual(codes['do_tc_isr']['name'], 'ISR: Withholdings') self.assertEqual( lines['do_tcl_isr_int_pf_inv']['code'], 'do_tc_isr_intereses_pf') self.assertEqual( lines['do_tcl_isr_int_pf_cr']['code'], 'do_tc_isr_intereses_pf') self.assertEqual( lines['do_tcl_isr_int_pj_inv']['code'], 'do_tc_isr_intereses') self.assertEqual( lines['do_tcl_isr_int_pj_cr']['code'], 'do_tc_isr_intereses') self.assertEqual( lines['do_tcl_chq020_inv']['code'], 'do_tc_others_checks_020') self.assertEqual( lines['do_tcl_chq020_cr']['code'], 'do_tc_others_checks_020') for record_id, operator in TAX_CODE_LINE_AUDIT.items(): with self.subTest(record_id=record_id): self.assertEqual(lines[record_id]['operator'], operator) self.assertEqual(lines['do_tcl_itbis18v_inv']['operator'], '+') self.assertEqual(lines['do_tcl_itbis18c_inv']['operator'], '-') self.assertEqual(lines['do_tcl_ret_itbis_inv']['operator'], '-') self.assertEqual(lines['do_tcl_ret_itbis_inf75_inv']['operator'], '-') self.assertEqual(lines['do_tcl_isr_hon_inv']['operator'], '-') self.assertEqual(lines['do_tcl_isr_est_inv']['operator'], '+') self.assertEqual(lines['do_tcl_isr_est5_inv']['operator'], '+') self.assertEqual(lines['do_tcl_isr_bovine_inv']['operator'], '-') self.assertEqual(lines['do_tcl_isr_exporter_inv']['operator'], '+') self.assertEqual(lines['do_tcl_isc_comb_inv']['operator'], '+') self.assertEqual(lines['do_tcl_prop_inv']['code'], 'do_tc_others_tip') def test_tax_rule_templates_include_date_sensitive_bank_tax(self): rules = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_rule_do_en.xml') if model == 'account.tax.rule.template' } lines = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_rule_do_en.xml') if model == 'account.tax.rule.line.template' } self.assertIn('do_tax_rule_bank_check_transfer', rules) self.assertEqual( lines['do_trline_supp_ext_isr27']['group'], 'do_tax_group_isr_ext') self.assertEqual( lines['do_trline_bank_check_transfer_015']['origin_tax'], 'do_tax_cheques_015') self.assertEqual( lines['do_trline_bank_check_transfer_015']['tax'], 'do_tax_cheques_015') self.assertEqual( lines['do_trline_bank_check_transfer_015']['end_date'], 'datetime.date(2026, 7, 2)') self.assertEqual( lines['do_trline_bank_check_transfer_020']['origin_tax'], 'do_tax_cheques_015') self.assertEqual( lines['do_trline_bank_check_transfer_020']['tax'], 'do_tax_cheques_020') self.assertEqual( lines['do_trline_bank_check_transfer_020']['start_date'], 'datetime.date(2026, 7, 3)') del ModuleTestCase