diff --git a/CHANGELOG b/CHANGELOG index 471bf14..3787766 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +Version 8.0.3 - 2026-08-10 +-------------------------- +* Restore the shared Dominican tax classification required by invoicing, NCF + management and electronic fiscal documents. +* Populate tax classifications for existing charts and language-scoped tax + templates. + Version 8.0.2 - 2026-08-10 -------------------------- * Structure income-statement types under current-period profit or loss, with diff --git a/__init__.py b/__init__.py index 4effdfa..5c88c97 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + +from . import tax + +__all__ = ['tax'] diff --git a/tax.py b/tax.py new file mode 100644 index 0000000..37f22a9 --- /dev/null +++ b/tax.py @@ -0,0 +1,162 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. + +from sql import Null, Table + +from trytond.model import fields +from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction + + +TAX_KIND = [ + ('', ''), + ('itbis', 'ITBIS'), + ('itbis_withholding', 'ITBIS Withholding'), + ('itbis_withholding_acquirer', 'ITBIS Acquirer Withholding'), + ('isr_withholding', 'ISR Withholding'), + ('isc', 'Selective Consumption Tax (ISC)'), + ('cdt', 'INDOTEL Contribution (CDT)'), + ('tip', 'Statutory Tip'), + ('others', 'Other Taxes'), +] + +TAX_TEMPLATE_KINDS = { + 'itbis': { + 'do_tax_itbis_18_venta', 'do_tax_itbis_16_venta', + 'do_tax_itbis_18_compra', 'do_tax_itbis_16_compra', + 'do_tax_itbis_exento', 'do_tax_itbis_tasa_cero', + }, + 'itbis_withholding': { + 'do_tax_ret_itbis_30', 'do_tax_ret_itbis_100_inf', + 'do_tax_ret_itbis_75_inf', 'do_tax_ret_itbis_75_inf_16', + 'do_tax_ret_itbis_100_goods_18', + 'do_tax_ret_itbis_100_goods_16', 'do_tax_ret_itbis_rst_18', + 'do_tax_ret_itbis_rst_16', 'do_tax_ret_itbis_insurance_100', + 'do_tax_ret_itbis_airline_100', + 'do_tax_ret_itbis_society_30_suf', 'do_tax_ret_itbis_hotel_100', + 'do_tax_ret_itbis_state_100', + }, + 'isr_withholding': { + 'do_tax_ret_isr_hon_5', 'do_tax_ret_isr_serv_10', + 'do_tax_ret_isr_serv_15', 'do_tax_ret_isr_div_10', + 'do_tax_ret_isr_int_10', 'do_tax_ret_isr_alq_10', + 'do_tax_ret_isr_alq_15', 'do_tax_ret_isr_est_15', + 'do_tax_ret_isr_est_5', 'do_tax_ret_isr_bovine_1', + 'do_tax_ret_isr_exporter_25', 'do_tax_ret_isr_int_pj_1', + 'do_tax_ret_isr_premios_25', 'do_tax_ret_isr_premios_10', + 'do_tax_ret_isr_premios_15', 'do_tax_ret_isr_betting_15', + 'do_tax_ret_isr_tragamonedas_10', + 'do_tax_ret_isr_tragamonedas_15', + 'do_tax_ret_isr_other_income_10', + 'do_tax_ret_isr_other_income_15', 'do_tax_ret_isr_ext_27', + 'do_tax_ret_isr_ext_15', 'do_tax_ret_isr_ext_10', + }, + 'isc': { + 'do_tax_isc_bebidas_alc', 'do_tax_isc_tabaco', + 'do_tax_isc_telecom', 'do_tax_isc_combustibles_16', + 'do_tax_isc_avtur_65', 'do_tax_isc_fuel_rd2_gallon', + 'do_tax_ret_isc_insurance_100', 'do_tax_isc_vehiculos', + }, + 'cdt': {'do_tax_cdt_indotel'}, + 'tip': {'do_tax_propina_10'}, + 'others': { + 'do_tax_cheques_015', 'do_tax_cheques_020', + 'do_tax_activos_1', 'do_tax_iti_3', + }, +} + + +def _set_template_kinds(table): + cursor = Transaction().connection.cursor() + model_data = Table('ir_model_data') + kind_by_identifier = { + identifier + suffix: tax_kind + for tax_kind, identifiers in TAX_TEMPLATE_KINDS.items() + for identifier in identifiers + for suffix in ('', '_en', '_es_419') + } + cursor.execute(*model_data.select( + model_data.db_id, model_data.fs_id, + where=model_data.module == 'account_do')) + for template_id, identifier in cursor.fetchall(): + tax_kind = kind_by_identifier.get(identifier) + if not tax_kind: + continue + cursor.execute(*table.update( + [table.tax_kind], [tax_kind], + where=(table.id == template_id) + & ((table.tax_kind == Null) | (table.tax_kind == '')))) + + +def _kind_from_values(values): + pool = Pool() + TaxGroup = pool.get('account.tax.group') + group = values.get('group') + if isinstance(group, int): + group = TaxGroup(group) + code = getattr(group, 'code', None) + if code == 'ITBIS': + amount = values.get('rate') or values.get('amount') or 0 + return 'itbis_withholding' if amount < 0 else 'itbis' + if code in {'ISR', 'ISREXT'}: + return 'isr_withholding' + if code == 'ISC': + return 'isc' + if code == 'CDT': + return 'cdt' + if code == 'OTROS': + name = (values.get('name') or '').casefold() + if 'propina' in name or 'tip' in name: + return 'tip' + if 'vehĂ­culo' in name or 'vehicle' in name: + return 'isc' + return 'others' + return '' + + +class TaxTemplate(metaclass=PoolMeta): + __name__ = 'account.tax.template' + + tax_kind = fields.Selection(TAX_KIND, 'Tax Kind', sort=False) + + @classmethod + def __register__(cls, module_name): + super().__register__(module_name) + _set_template_kinds(cls.__table__()) + + @classmethod + def create(cls, vlist): + vlist = [values.copy() for values in vlist] + for values in vlist: + values.setdefault('tax_kind', _kind_from_values(values)) + return super().create(vlist) + + def _get_tax_value(self, tax=None): + values = super()._get_tax_value(tax=tax) + if not tax or tax.tax_kind != self.tax_kind: + values['tax_kind'] = self.tax_kind + return values + + +class Tax(metaclass=PoolMeta): + __name__ = 'account.tax' + + tax_kind = fields.Selection(TAX_KIND, 'Tax Kind', sort=False) + + @classmethod + def __register__(cls, module_name): + super().__register__(module_name) + table = cls.__table__() + template = Table('account_tax_template') + cursor = Transaction().connection.cursor() + cursor.execute(*table.join( + template, condition=template.id == table.template).select( + table.id, template.tax_kind, + where=(table.template != Null) + & (template.tax_kind != Null) + & (template.tax_kind != '') + & ((table.tax_kind == Null) | (table.tax_kind == '')))) + for tax_id, tax_kind in cursor.fetchall(): + cursor.execute(*table.update( + [table.tax_kind], [tax_kind], + where=table.id == tax_id)) diff --git a/tests/test_module.py b/tests/test_module.py index 6f4430c..c9fffa1 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -229,6 +229,7 @@ class AccountDoTestCase(ModuleTestCase): ('company', '=', company.id), ('description', '=', 'ITBIS 18% Ventas'), ('rate', '=', Decimal('0.18')), + ('tax_kind', '=', 'itbis'), ], limit=1)) self.assertTrue(TaxCode.search([ ('company', '=', company.id), @@ -362,12 +363,14 @@ class AccountDoTestCase(ModuleTestCase): ('company', '=', company.id), ('description', '=', 'ITBIS 18% Sales'), ('rate', '=', Decimal('0.18')), + ('tax_kind', '=', 'itbis'), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), ('description', '=', 'ITBIS Withholding Large Taxpayer 30%'), ('rate', '=', Decimal('-0.054')), + ('tax_kind', '=', 'itbis_withholding'), ], limit=1)) self.assertTrue(Tax.search([ ('company', '=', company.id), @@ -565,7 +568,7 @@ class AccountDoUnitTestCase(unittest.TestCase): if line.strip() ] - self.assertEqual(config.get('tryton', 'version'), '8.0.2') + self.assertEqual(config.get('tryton', 'version'), '8.0.3') self.assertEqual( set(lines('tryton', 'depends')), {'account', 'ir'}) @@ -581,11 +584,14 @@ class AccountDoUnitTestCase(unittest.TestCase): 'tax_code_do_es_419.xml', 'tax_rule_do_es_419.xml', ]) - self.assertEqual(lines('register', 'model'), ['ir.ModelData']) + self.assertEqual( + lines('register', 'model'), + ['ir.ModelData', 'tax.TaxTemplate', 'tax.Tax']) 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()) + self.assertTrue((MODULE_DIR / 'tax.py').is_file()) pyproject = (MODULE_DIR / 'pyproject.toml').read_text(encoding='utf-8') self.assertIn("name = 'trytond_account_do'", pyproject) self.assertIn( diff --git a/tryton.cfg b/tryton.cfg index afba38f..37ee71a 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=8.0.2 +version=8.0.3 depends: account ir @@ -16,5 +16,7 @@ xml: [register] model: ir.ModelData + tax.TaxTemplate + tax.Tax wizard: account.CreateChart