import unittest import datetime 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.modules.account_do.tax import ( DGII_FISCAL_STATUS, TAX_APPLICATION, TAX_FISCAL_TYPE, TAX_KIND) 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_TEMPLATE_AUDIT = { 'do_tax_itbis_18_venta': ('do_account_21020101', 'itbis', 'itbis_sale_18', 'sale_invoice', 'current', '', ''), 'do_tax_itbis_16_venta': ('do_account_21020102', 'itbis', 'itbis_sale_16', 'sale_invoice', 'special', '', ''), 'do_tax_itbis_9_venta': ('do_account_21020103', 'itbis', 'itbis_sale_9', 'sale_invoice', 'special', '', ''), 'do_tax_itbis_18_compra': ('do_account_11040101', 'itbis', 'itbis_purchase_18', 'purchase_invoice', 'current', '', ''), 'do_tax_itbis_16_compra': ('do_account_11040102', 'itbis', 'itbis_purchase_16', 'purchase_invoice', 'special', '', ''), 'do_tax_itbis_9_compra': ('do_account_11040103', 'itbis', 'itbis_purchase_9', 'purchase_invoice', 'special', '', ''), 'do_tax_itbis_8_venta': ('do_account_21020104', 'itbis', 'itbis_sale_8', 'sale_invoice', 'special', '', ''), 'do_tax_itbis_8_compra': ('do_account_11040104', 'itbis', 'itbis_purchase_8', 'purchase_invoice', 'special', '', ''), 'do_tax_itbis_exento': ('', 'itbis', 'itbis_exempt', 'sale_invoice', 'current', '', ''), 'do_tax_itbis_tasa_cero': ('do_account_21020105', 'itbis', 'itbis_zero_rate', 'sale_invoice', 'current', '', ''), 'do_tax_ret_itbis_30': ('do_account_21021101', 'itbis_withholding', 'itbis_withholding_30', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_2_adq': ('do_account_11040601', 'itbis_withholding_acquirer', 'itbis_withholding_acquirer_2', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_hon_5': ('do_account_21021301', 'isr_withholding', 'isr_legal_entity_services_5', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_serv_10': ('do_account_21021302', 'isr_withholding', 'isr_individual_services_10', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_div_10': ('do_account_21020601', 'isr_withholding', 'isr_dividends_10', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_int_10': ('do_account_21021501', 'isr_withholding', 'isr_interest_individual_10', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_alq_10': ('do_account_21021401', 'isr_withholding', 'isr_rent_10', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_est_15': ('do_account_11040801', 'isr_withholding', 'isr_state_1_5', 'sale_invoice', 'current', '', ''), 'do_tax_ret_isr_est_5': ('do_account_11040802', 'isr_withholding', 'isr_state_5', 'sale_invoice', 'current', '', ''), 'do_tax_ret_isr_bovine_1': ('do_account_21021801', 'isr_withholding', 'isr_bovine_meat_1', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_exporter_25': ('do_account_11040803', 'isr_withholding', 'isr_exporter_sales_2_5', 'sale_invoice', 'current', '', ''), 'do_tax_ret_isr_int_pj_1': ('do_account_21021502', 'isr_withholding', 'isr_interest_legal_entity_1', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_premios_25': ('do_account_21021701', 'isr_withholding', 'isr_prizes_25', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_premios_10': ('do_account_21021702', 'isr_withholding', 'isr_prizes_10', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_premios_15': ('do_account_21021703', 'isr_withholding', 'isr_prizes_15', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_tragamonedas_10': ('do_account_21021704', 'isr_withholding', 'isr_slot_machine_prizes_10', 'payment_withholding', 'current', '', ''), 'do_tax_ret_isr_other_income_10': ('do_account_21020301', 'isr_withholding', 'isr_other_income_10', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_ext_27': ('do_account_21020701', 'isr_withholding', 'isr_foreign_services_27', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_isr_ext_10': ('do_account_21020702', 'isr_withholding', 'isr_foreign_interest_10', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_100_inf': ('do_account_21020201', 'itbis_withholding', 'itbis_withholding_100_services', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_75_inf': ('do_account_21021201', 'itbis_withholding', 'itbis_withholding_75_informal', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_75_inf_16': ('do_account_21021202', 'itbis_withholding', 'itbis_withholding_75_informal_16', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_100_goods_18': ('do_account_21021203', 'itbis_withholding', 'itbis_withholding_100_informal_goods_18', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_100_goods_16': ('do_account_21021204', 'itbis_withholding', 'itbis_withholding_100_informal_goods_16', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_rst_18': ('do_account_21020202', 'itbis_withholding', 'itbis_withholding_rst_18', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_rst_16': ('do_account_21020203', 'itbis_withholding', 'itbis_withholding_rst_16', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_insurance_100': ('do_account_21020204', 'itbis_withholding', 'itbis_withholding_insurance_100', 'purchase_invoice', 'current', '', ''), 'do_tax_ret_itbis_airline_100': ('do_account_11040701', 'itbis_withholding', 'itbis_withholding_airline_100', 'sale_invoice', 'current', '', ''), 'do_tax_ret_itbis_society_30_suf': ('do_account_11040702', 'itbis_withholding', 'itbis_withholding_society_30_suffered', 'sale_invoice', 'current', '', ''), 'do_tax_ret_itbis_hotel_100': ('do_account_11040703', 'itbis_withholding', 'itbis_withholding_hotel_100', 'sale_invoice', 'current', '', ''), 'do_tax_ret_itbis_state_100': ('do_account_11040704', 'itbis_withholding', 'itbis_withholding_state_100', 'sale_invoice', 'current', '', ''), 'do_tax_isc_bebidas_alc': ('do_account_21020801', 'isc', 'isc_alcohol_10', 'sale_invoice', 'current', '', ''), 'do_tax_isc_tabaco': ('do_account_21020802', 'isc', 'isc_tobacco_20', 'sale_invoice', 'current', '', ''), 'do_tax_isc_telecom': ('do_account_21020803', 'isc', 'isc_telecom_10', 'sale_invoice', 'current', '', ''), 'do_tax_isc_combustibles_16': ('do_account_21020804', 'isc', 'isc_fossil_fuel_16', 'sale_invoice', 'current', '', ''), 'do_tax_isc_avtur_65': ('do_account_21020807', 'isc', 'isc_avtur_6_5', 'sale_invoice', 'current', '', ''), 'do_tax_isc_fuel_rd2_gallon': ('do_account_21020808', 'isc', 'isc_fuel_rd2_gallon', 'sale_invoice', 'current', '', ''), 'do_tax_ret_isc_insurance_100': ('do_account_21020806', 'isc', 'isc_insurance_withholding_100', 'purchase_invoice', 'current', '', ''), 'do_tax_isc_vehiculos': ('do_account_21020805', 'isc', 'isc_vehicle_17', 'asset_transfer', 'current', '', ''), 'do_tax_cdt_indotel': ('do_account_21020901', 'cdt', 'cdt_indotel_2', 'sale_invoice', 'current', '', ''), 'do_tax_propina_10': ('do_account_21020501', 'tip', 'legal_tip_10', 'sale_invoice', 'current', '', ''), 'do_tax_cheques_015': ('do_account_21021901', 'others', 'check_transfer_tax_015', 'bank_charge', 'historical', '', 'datetime.date(2026, 7, 2)'), 'do_tax_cheques_020': ('do_account_21021902', 'others', 'check_transfer_tax_020', 'bank_charge', 'current', 'datetime.date(2026, 7, 3)', ''), 'do_tax_activos_1': ('do_account_21020502', 'others', 'asset_tax_1', 'annual_declaration', 'current', '', ''), 'do_tax_iti_3': ('do_account_21021001', 'others', 'real_estate_transfer_3', 'asset_transfer', 'current', '', ''), } 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_adq_inv': '+', 'do_tcl_ret_itbis_adq_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'): values = { field.get('name'): ( field.get('ref') or field.get('eval') or field.text or '') for field in record.findall('field') } yield filename, record.get('id'), record.get('model'), values def _po_entries(filename): entries = [] context = msgid = msgstr = None for line in (MODULE_DIR / filename).read_text(encoding='utf-8').splitlines(): if line.startswith('msgctxt '): context = line.split(' ', 1)[1].strip().strip('"') elif line.startswith('msgid '): msgid = line.split(' ', 1)[1].strip().strip('"') elif line.startswith('msgstr '): msgstr = line.split(' ', 1)[1].strip().strip('"') entries.append((context, msgid, msgstr)) context = msgid = msgstr = None return entries class AccountDoTestCase(ModuleTestCase): "Test account_do module" module = 'account_do' @with_transaction() def test_chart_template_is_loaded_by_default(self): 'Test Dominican chart template is loaded without Spanish language setup' pool = Pool() ModelData = pool.get('ir.model.data') self.assertTrue(ModelData.get_id('account_do', 'do_account_root')) @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')) 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) 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': 'Activos corrientes', '120591': 'Activos por derecho de uso', '120592': 'Activos por derecho de uso', '120891': 'Propiedades de inversión', '120892': 'Propiedades de inversión', '120992': 'Activos intangibles', '7101': 'Cuentas de cierre y control', '7102': 'Cuentas de cierre y control', } 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 — Balance Neto (Débito − Crédito)'), ], 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% Ventas'), ('tax_kind', '=', 'itbis'), ('rate', '=', Decimal('0.18')), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'Retención ITBIS Gran Contribuyente 30%'), ('tax_kind', '=', 'itbis_withholding'), ('tax_fiscal_type', '=', 'itbis_withholding_30'), ('rate', '=', Decimal('-0.054')), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'Retención ITBIS Proveedor Informal 75%'), ('tax_kind', '=', 'itbis_withholding'), ('tax_fiscal_type', '=', 'itbis_withholding_75_informal'), ('rate', '=', Decimal('-0.135')), ], limit=1)) check_015, = Tax.search([ ('company', '=', company.id), ('description', '=', 'Impuesto Cheques y Transferencias 0.15%'), ], limit=1) check_020, = Tax.search([ ('company', '=', company.id), ('description', '=', 'Impuesto Cheques y Transferencias 0.20%'), ], limit=1) bank_rule, = TaxRule.search([ ('company', '=', company.id), ('name', '=', ( 'Regla Bancaria Cheques / Transferencias ' 'Electrónicas (RD)')), ], limit=1) self.assertEqual(check_015.end_date, datetime.date(2026, 7, 2)) self.assertEqual(check_015.dgii_fiscal_status, 'historical') self.assertEqual(check_020.start_date, datetime.date(2026, 7, 3)) self.assertEqual(check_020.dgii_fiscal_status, 'current') 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% Ventas', 'invoice'): '+', ('ITBIS 18% Ventas', 'credit'): '-', ('ITBIS 18% Compras', 'invoice'): '-', ('ITBIS 18% Compras', 'credit'): '+', ('Retención ITBIS Gran Contribuyente 30%', 'invoice'): '-', ('Retención ITBIS Gran Contribuyente 30%', 'credit'): '+', ('Retención ISR Persona Física 10%', 'invoice'): '-', ('Retención ISR Persona Física 10%', 'credit'): '+', ('Retención ISR Estado 1.5%', 'invoice'): '+', ('Retención ISR Estado 1.5%', 'credit'): '-', ('Impuesto Cheques y Transferencias 0.20%', 'invoice'): '+', ('Impuesto Cheques y Transferencias 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 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.0.0') self.assertEqual( set(lines('tryton', 'depends')), {'account', 'company', 'currency'}) self.assertEqual( lines('tryton', 'xml'), [ 'account_chart_do.xml', 'tax_do.xml', 'tax_view.xml', 'tax_code_do.xml', 'tax_rule_do.xml', ]) self.assertEqual( lines('register', 'model'), [ 'tax.TaxTemplate', 'tax.Tax', 'tax.TaxCode', 'tax.TaxCodeLine', ]) for filename in lines('tryton', 'xml'): self.assertTrue((MODULE_DIR / filename).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) for package_file in [ 'README.rst', 'IMPUESTOS_RD.md', 'doc/**/*.rst', 'locale/**/*.po', 'tests/**/*.rst', ]: self.assertIn(package_file, pyproject) self.assertEqual( (MODULE_DIR / '__init__.py').read_text(encoding='utf-8'), '') def test_xml_record_inventory_is_explicit(self): expected = { 'account_chart_do.xml': { 'account.account.type.template': 36, 'account.account.template': 281, }, 'tax_do.xml': { 'account.tax.group': 7, 'account.tax.template': 55, }, 'tax_code_do.xml': { 'account.tax.code.template': 49, 'account.tax.code.line.template': 103, }, 'tax_rule_do.xml': { 'account.tax.rule.template': 28, 'account.tax.rule.line.template': 35, }, 'tax_view.xml': { 'ir.ui.view': 4, }, } 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_gitea_workflow_tests_before_publish(self): workflow = ( MODULE_DIR / '.gitea' / 'workflows' / 'publish.yml' ).read_text(encoding='utf-8') self.assertIn('test:', workflow) self.assertIn('publish:', workflow) self.assertIn('needs: test', workflow) self.assertIn("python -m pip install -e '.[test]'", workflow) self.assertIn('python -m compileall .', workflow) self.assertIn( "python -m unittest discover -s tests -p 'test*.py'", workflow) self.assertIn('python -m build', workflow) self.assertLess( workflow.index('test:'), workflow.index('publish:')) self.assertLess( workflow.index("python -m unittest discover -s tests -p 'test*.py'"), workflow.index('Set CI package version')) def test_tax_views_expose_all_localization_fields(self): localization_fields = [ 'tax_kind', 'tax_fiscal_type', 'tax_application', 'dgii_fiscal_status', 'dgii_form_hint', 'dgii_legal_reference', 'dgii_legal_article', ] form = ET.parse(MODULE_DIR / 'view/tax_form.xml').getroot() list_ = ET.parse(MODULE_DIR / 'view/tax_list.xml').getroot() form_fields = [ element.get('name') for element in form.findall('.//field') if element.get('name') in localization_fields ] list_fields = [ element.get('name') for element in list_.findall('.//field') if element.get('name') in localization_fields ] self.assertEqual(form_fields, localization_fields) self.assertEqual(list_fields, localization_fields[:3]) view_records = { (values['model'], values['name'], values['inherit']) for _, _, model, values in _iter_xml_records('tax_view.xml') if model == 'ir.ui.view' } self.assertEqual(view_records, { ('account.tax.template', 'tax_form', 'account.tax_template_view_form'), ('account.tax', 'tax_form', 'account.tax_view_form'), ('account.tax.template', 'tax_list', 'account.tax_template_view_list'), ('account.tax', 'tax_list', 'account.tax_view_list'), }) def test_translation_catalogs_cover_module_terms(self): type_templates = { record_id: values['name'] for _, record_id, model, values in _iter_xml_records( 'account_chart_do.xml') if model == 'account.account.type.template' } account_templates = { record_id: values['name'] for _, record_id, model, values in _iter_xml_records( 'account_chart_do.xml') if model == 'account.account.template' } required_field_contexts = { 'field:account.tax,tax_kind:', 'field:account.tax.template,tax_kind:', 'field:account.tax,tax_fiscal_type:', 'field:account.tax.template,tax_fiscal_type:', 'field:account.tax,tax_application:', 'field:account.tax.template,tax_application:', 'field:account.tax,dgii_fiscal_status:', 'field:account.tax.template,dgii_fiscal_status:', 'field:account.tax,dgii_form_hint:', 'field:account.tax.template,dgii_form_hint:', 'field:account.tax,dgii_legal_article:', 'field:account.tax.template,dgii_legal_article:', 'field:account.tax,dgii_legal_reference:', 'field:account.tax.template,dgii_legal_reference:', } for filename in ['locale/es.po', 'locale/es_419.po']: entries = _po_entries(filename) contexts = {context for context, _msgid, _msgstr in entries} entry_keys = [(context, msgid) for context, msgid, _ in entries] with self.subTest(filename=filename): self.assertEqual( [key for key, count in Counter(entry_keys).items() if count > 1], []) self.assertLessEqual(required_field_contexts, contexts) for record_id in type_templates: self.assertIn( 'model:account.account.type.template,name:%s' % record_id, contexts) for record_id in [ 'do_account_110201', 'do_account_210101', 'do_account_21021901', 'do_account_21021902', ]: self.assertIn( 'model:account.account.template,name:%s' % record_id, contexts) def test_migration_metadata_is_explicit(self): from trytond.modules.account_do.tax import ( MODEL_DATA_RENAMES, ROOT_TAX_CODE_CHILDREN) self.assertEqual(MODEL_DATA_RENAMES, { 'do_tax_group_otros': 'do_tax_group_others', 'do_tc_otros': 'do_tc_others', 'do_tc_otros_propina': 'do_tc_others_tip', 'do_tc_otros_cheques': 'do_tc_others_checks', 'do_tc_otros_activos': 'do_tc_others_assets', 'do_tc_otros_iti': 'do_tc_others_iti', }) self.assertEqual(ROOT_TAX_CODE_CHILDREN, [ 'ITBIS — Balance Neto (Débito − Crédito)', 'ISR - Retenciones', 'ISR - Retenciones en la Fuente', 'ISC - Impuesto Selectivo al Consumo', 'CDT INDOTEL 2% (Ley 153-98)', 'Otros Impuestos y Contribuciones', ]) def test_xml_references_are_resolved_inside_module(self): records = list(_iter_xml_records( 'account_chart_do.xml', 'tax_do.xml', 'tax_code_do.xml', 'tax_rule_do.xml', )) ids = {record_id for _, record_id, _, _ in records} missing = [] for filename in [ 'account_chart_do.xml', 'tax_do.xml', 'tax_code_do.xml', 'tax_rule_do.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.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.xml') if model == 'account.account.template' } types = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do.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.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.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.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.xml', 'tax_code_do.xml') } self.assertFalse([record_id for record_id in ids if 'otros' in record_id]) def test_tax_templates_are_classified(self): taxes = { record_id: values for _, record_id, model, values in _iter_xml_records('tax_do.xml') if model == 'account.tax.template' } accounts = { record_id: values for _, record_id, model, values in _iter_xml_records( 'account_chart_do.xml') if model == 'account.account.template' } tax_kind_codes = {code for code, _ in TAX_KIND} fiscal_type_codes = {code for code, _ in TAX_FISCAL_TYPE} application_codes = {code for code, _ in TAX_APPLICATION} fiscal_status_codes = {code for code, _ in DGII_FISCAL_STATUS} self.assertEqual(len(taxes), 55) self.assertEqual(set(taxes), set(TAX_TEMPLATE_AUDIT)) for record_id, expected in TAX_TEMPLATE_AUDIT.items(): account_id, tax_kind, fiscal_type, application, status, start, end = ( expected) with self.subTest(record_id=record_id): values = taxes[record_id] self.assertEqual(values.get('invoice_account', ''), account_id) self.assertEqual( values.get('credit_note_account', ''), account_id) if account_id: self.assertIn(account_id, accounts) else: self.assertEqual(values['type'], 'none') self.assertFalse(values.get('rate')) self.assertEqual(values['tax_kind'], tax_kind) self.assertEqual(values['tax_fiscal_type'], fiscal_type) self.assertEqual(values['tax_application'], application) self.assertEqual(values['dgii_fiscal_status'], status) self.assertEqual(values.get('start_date', ''), start) self.assertEqual(values.get('end_date', ''), end) self.assertIn(tax_kind, tax_kind_codes) self.assertIn(fiscal_type, fiscal_type_codes) self.assertIn(application, application_codes) self.assertIn(status, fiscal_status_codes) self.assertTrue(values.get('dgii_legal_reference')) self.assertTrue(values.get('dgii_legal_article')) self.assertTrue(values.get('dgii_form_hint')) self.assertTrue(values.get('legal_notice')) self.assertEqual( taxes['do_tax_ret_itbis_30']['tax_kind'], 'itbis_withholding') self.assertIn( "Decimal('-5.4')/100", taxes['do_tax_ret_itbis_30']['rate']) self.assertEqual( taxes['do_tax_ret_itbis_75_inf']['tax_fiscal_type'], 'itbis_withholding_75_informal') 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.assertEqual( taxes['do_tax_itbis_9_venta']['tax_fiscal_type'], 'itbis_sale_9') self.assertIn( "Decimal('9')/100", taxes['do_tax_itbis_9_compra']['rate']) self.assertEqual( taxes['do_tax_isc_bebidas_alc']['tax_fiscal_type'], 'isc_alcohol_10') self.assertIn( "Decimal('10')/100", taxes['do_tax_isc_bebidas_alc']['rate']) self.assertEqual( taxes['do_tax_isc_combustibles_16']['tax_fiscal_type'], 'isc_fossil_fuel_16') self.assertEqual( taxes['do_tax_cheques_020']['tax_kind'], 'others') self.assertEqual( taxes['do_tax_cheques_015']['end_date'], 'datetime.date(2026, 7, 2)') self.assertEqual( taxes['do_tax_cheques_015']['dgii_fiscal_status'], 'historical') self.assertEqual( taxes['do_tax_cheques_020']['start_date'], 'datetime.date(2026, 7, 3)') self.assertEqual( taxes['do_tax_cheques_020']['dgii_fiscal_status'], 'current') 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_code_templates_use_expected_signs(self): codes = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_code_do.xml') if model == 'account.tax.code.template' } lines = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_code_do.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 - Retenciones') 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_itbis9v_inv']['operator'], '+') self.assertEqual(lines['do_tcl_itbis9c_inv']['operator'], '-') self.assertEqual(lines['do_tcl_ret_itbis_inv']['operator'], '-') self.assertEqual(lines['do_tcl_ret_itbis_adq_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.xml') if model == 'account.tax.rule.template' } lines = { record_id: values for _, record_id, model, values in _iter_xml_records( 'tax_rule_do.xml') if model == 'account.tax.rule.line.template' } self.assertIn('do_tax_rule_bank_check_transfer', rules) 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)') def test_tax_kind_contains_dominican_tax_categories(self): codes = {code for code, _ in TAX_KIND} self.assertEqual(codes, { '', 'itbis', 'itbis_withholding', 'itbis_withholding_acquirer', 'isr_withholding', 'isc', 'cdt', 'tip', 'others', }) def test_tax_kind_codes_are_unique(self): codes = [code for code, _ in TAX_KIND] self.assertEqual(len(codes), len(set(codes))) def test_fiscal_classification_codes_are_unique(self): fiscal_codes = [code for code, _ in TAX_FISCAL_TYPE] application_codes = [code for code, _ in TAX_APPLICATION] status_codes = [code for code, _ in DGII_FISCAL_STATUS] self.assertEqual(len(fiscal_codes), len(set(fiscal_codes))) self.assertEqual(len(application_codes), len(set(application_codes))) self.assertEqual(len(status_codes), len(set(status_codes))) del ModuleTestCase