Update Dominican withholding catalog

Apply the Article 309 rates introduced by Law 30-26 with dated historical templates and rules. Complete tax-code reversals, improve English terminology, harden migrations, and expand structural and functional tests.
This commit is contained in:
José Arturo García
2026-08-10 09:10:05 -04:00
parent e646947a6e
commit 1583dd3387
16 changed files with 765 additions and 154 deletions
+120 -13
View File
@@ -310,8 +310,10 @@ class AccountDoTestCase(ModuleTestCase):
'21020801',
'21020802', '21020803', '21020804', '21020805',
'21020901', '21021001', '21021101', '21021201',
'21021301', '21021302', '21021401', '21021501',
'21021502', '21021701', '21021801', '21021901',
'21020302', '21021301', '21021302', '21021303',
'21021401', '21021402', '21021501',
'21021502', '21021701', '21021705', '21021706',
'21021801', '21021901',
'21021902', '6208']:
with self.subTest(code=code):
self.assertTrue(Account.search([
@@ -426,6 +428,28 @@ class AccountDoTestCase(ModuleTestCase):
'date': datetime.date(2026, 7, 1),
}),
[foreign_15.id])
service_10, = Tax.search([
('company', '=', company.id),
('description', '=', 'ISR Withholding Individual 10%'),
], limit=1)
service_15, = Tax.search([
('company', '=', company.id),
('description', '=', 'ISR Withholding Individual 15%'),
], limit=1)
service_rule, = TaxRule.search([
('company', '=', company.id),
('name', '=', 'Individual Services ISR Rule (RD)'),
], limit=1)
self.assertEqual(
service_rule.apply(service_10, {
'date': datetime.date(2026, 6, 30),
}),
[service_10.id])
self.assertEqual(
service_rule.apply(service_10, {
'date': datetime.date(2026, 7, 1),
}),
[service_15.id])
itbis_sale, = Tax.search([
('company', '=', company.id),
('description', '=', 'ITBIS 18% Sales'),
@@ -452,6 +476,8 @@ class AccountDoTestCase(ModuleTestCase):
('ITBIS Withholding Large Taxpayer 30%', 'credit'): '+',
('ISR Withholding Individual 10%', 'invoice'): '-',
('ISR Withholding Individual 10%', 'credit'): '+',
('ISR Withholding Individual 15%', 'invoice'): '-',
('ISR Withholding Individual 15%', 'credit'): '+',
('ISR Withholding State 1.5%', 'invoice'): '+',
('ISR Withholding State 1.5%', 'credit'): '-',
('Checks and Transfers Tax 0.20%', 'invoice'): '+',
@@ -514,6 +540,8 @@ class SpanishAccountDoTestCase(ModuleTestCase):
}])
ModelData._migrate_localization_identifiers()
# A retried module update must be harmless.
ModelData._migrate_localization_identifiers()
self.assertEqual(
ModelData.get_id('account_do', 'do_account_root_es_419'),
@@ -597,40 +625,51 @@ class AccountDoUnitTestCase(unittest.TestCase):
'doc/releases.rst',
]:
self.assertTrue((MODULE_DIR / filename).is_file())
self.assertNotIn(
'Solu' + 'tema',
'\n'.join(
path.read_text(encoding='utf-8')
for path in MODULE_DIR.rglob('*')
if path.is_file()
and '.git' not in path.parts
and 'dist' not in path.parts
and (path.suffix in {'.py', '.rst', '.toml', '.xml'}
or path.name in {
'CHANGELOG', 'COPYRIGHT', 'tryton.cfg'})))
def test_xml_record_inventory_is_explicit(self):
expected = {
'account_chart_do_en.xml': {
'account.account.type.template': 36,
'account.account.template': 282,
'account.account.template': 287,
},
'tax_do_en.xml': {
'account.tax.group': 6,
'account.tax.template': 51,
'account.tax.template': 56,
},
'tax_code_do_en.xml': {
'account.tax.code.template': 43,
'account.tax.code.line.template': 95,
'account.tax.code.template': 44,
'account.tax.code.line.template': 112,
},
'tax_rule_do_en.xml': {
'account.tax.rule.template': 28,
'account.tax.rule.line.template': 31,
'account.tax.rule.line.template': 34,
},
'account_chart_do_es_419.xml': {
'account.account.type.template': 36,
'account.account.template': 282,
'account.account.template': 287,
},
'tax_do_es_419.xml': {
'account.tax.group': 6,
'account.tax.template': 51,
'account.tax.template': 56,
},
'tax_code_do_es_419.xml': {
'account.tax.code.template': 43,
'account.tax.code.line.template': 95,
'account.tax.code.template': 44,
'account.tax.code.line.template': 112,
},
'tax_rule_do_es_419.xml': {
'account.tax.rule.template': 28,
'account.tax.rule.line.template': 31,
'account.tax.rule.line.template': 34,
},
}
for filename, expected_counts in expected.items():
@@ -689,6 +728,20 @@ class AccountDoUnitTestCase(unittest.TestCase):
with self.subTest(phrase=phrase):
self.assertNotIn(phrase, text.lower())
def test_english_xml_comments_are_in_english(self):
text = '\n'.join(
(MODULE_DIR / filename).read_text(encoding='utf-8').lower()
for filename in [
'account_chart_do_en.xml', 'tax_do_en.xml',
'tax_code_do_en.xml', 'tax_rule_do_en.xml'])
for marker in [
'á', 'é', 'í', 'ó', 'ú', 'ñ',
' cuenta ', ' impuesto ', ' proveedor ', ' regla ',
' retención ', ' servicios ', ' tasa ',
]:
with self.subTest(marker=marker):
self.assertNotIn(marker, text)
def test_spanish_chart_has_complete_parallel_identifiers(self):
pairs = [
('account_chart_do_en.xml', 'account_chart_do_es_419.xml'),
@@ -729,6 +782,9 @@ class AccountDoUnitTestCase(unittest.TestCase):
self.assertEqual(
spanish_accounts['do_account_110201_es_419']['name'],
'Cuentas por cobrar clientes')
self.assertEqual(
spanish_accounts['do_account_110406_es_419']['name'],
'ITBIS retenido en adquisiciones a recuperar')
self.assertEqual(
spanish_taxes['do_tax_itbis_18_venta_es_419']['description'],
'ITBIS 18% Ventas')
@@ -891,7 +947,7 @@ class AccountDoUnitTestCase(unittest.TestCase):
'account_chart_do_en.xml')
if model == 'account.account.template'
}
self.assertEqual(len(taxes), 51)
self.assertEqual(len(taxes), 56)
for record_id, values in taxes.items():
with self.subTest(record_id=record_id):
self.assertEqual(
@@ -939,6 +995,26 @@ class AccountDoUnitTestCase(unittest.TestCase):
self.assertEqual(
taxes['do_tax_ret_isr_bovine_1']['start_date'],
'datetime.date(2025, 6, 20)')
for old_id in [
'do_tax_ret_isr_serv_10',
'do_tax_ret_isr_alq_10',
'do_tax_ret_isr_premios_10',
'do_tax_ret_isr_premios_15',
'do_tax_ret_isr_tragamonedas_10',
'do_tax_ret_isr_other_income_10',
]:
self.assertEqual(
taxes[old_id]['end_date'], 'datetime.date(2026, 6, 30)')
for current_id in [
'do_tax_ret_isr_serv_15',
'do_tax_ret_isr_alq_15',
'do_tax_ret_isr_betting_15',
'do_tax_ret_isr_tragamonedas_15',
'do_tax_ret_isr_other_income_15',
]:
self.assertEqual(
taxes[current_id]['start_date'],
'datetime.date(2026, 7, 1)')
self.assertIn(
'Tax Code Arts. 401-405; Art. 404',
taxes['do_tax_activos_1']['legal_notice'])
@@ -1036,6 +1112,37 @@ class AccountDoUnitTestCase(unittest.TestCase):
self.assertEqual(lines['do_tcl_isc_comb_inv']['operator'], '+')
self.assertEqual(lines['do_tcl_prop_inv']['code'], 'do_tc_others_tip')
def test_tax_code_lines_reverse_credit_notes(self):
"""Every reported amount must have an inverse credit-note line."""
pairs = {}
for _, record_id, model, values in _iter_xml_records(
'tax_code_do_en.xml'):
if model != 'account.tax.code.line.template':
continue
key = (values['tax'], values['amount'])
pair = pairs.setdefault(key, {})
self.assertNotIn(values['type'], pair)
pair[values['type']] = (
record_id, values['operator'])
for key, lines in pairs.items():
with self.subTest(tax=key[0], amount=key[1]):
self.assertEqual(set(lines), {'invoice', 'credit'})
self.assertNotEqual(
lines['invoice'][1], lines['credit'][1])
def test_every_tax_is_reported_by_a_tax_code(self):
taxes = {
record_id for _, record_id, model, _values
in _iter_xml_records('tax_do_en.xml')
if model == 'account.tax.template'
}
reported = {
values['tax'] for _, _, model, values
in _iter_xml_records('tax_code_do_en.xml')
if model == 'account.tax.code.line.template'
}
self.assertEqual(taxes, reported)
def test_tax_rule_templates_include_date_sensitive_bank_tax(self):
rules = {
record_id: values