Files
account_do/tests/test_module.py
T

1032 lines
45 KiB
Python
Raw Normal View History

2026-07-22 19:12:44 -04:00
import unittest
import datetime
2026-07-22 19:12:44 -04:00
from collections import Counter
from configparser import ConfigParser
2026-07-22 19:12:44 -04:00
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)
2026-07-22 19:12:44 -04:00
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
2026-08-09 13:13:22 -04:00
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'},
}
2026-07-22 19:12:44 -04:00
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
2026-07-22 19:12:44 -04:00
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')
2026-08-09 13:13:22 -04:00
TaxCodeLine = pool.get('account.tax.code.line')
2026-07-22 19:12:44 -04:00
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 [
2026-08-09 13:13:22 -04:00
'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']:
2026-07-22 19:12:44 -04:00
with self.subTest(code=code):
self.assertTrue(Account.search([
('company', '=', company.id),
('code', '=', code),
('closed', '!=', True),
], limit=1))
2026-08-09 13:13:22 -04:00
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)
2026-07-22 19:12:44 -04:00
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'),
2026-07-22 19:12:44 -04:00
('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])
2026-08-09 13:13:22 -04:00
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)
2026-07-22 19:12:44 -04:00
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',
])
2026-07-22 19:12:44 -04:00
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')
2026-08-09 13:13:22 -04:00
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)
2026-07-22 19:12:44 -04:00
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'
}
2026-08-09 13:13:22 -04:00
accounts = {
record_id: values
for _, record_id, model, values in _iter_xml_records(
'account_chart_do.xml')
if model == 'account.account.template'
}
2026-08-09 13:13:22 -04:00
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):
2026-08-09 13:13:22 -04:00
values = taxes[record_id]
self.assertEqual(values.get('invoice_account', ''), account_id)
self.assertEqual(
2026-08-09 13:13:22 -04:00
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'))
2026-07-22 19:12:44 -04:00
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')
2026-07-22 19:12:44 -04:00
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')
2026-07-22 19:12:44 -04:00
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')
2026-08-09 13:13:22 -04:00
for record_id, operator in TAX_CODE_LINE_AUDIT.items():
with self.subTest(record_id=record_id):
self.assertEqual(lines[record_id]['operator'], operator)
2026-07-22 19:12:44 -04:00
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'], '+')
2026-07-22 19:12:44 -04:00
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)')
2026-07-22 19:12:44 -04:00
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)))
2026-07-22 19:12:44 -04:00
del ModuleTestCase