17 Commits
Author SHA1 Message Date
José Arturo García 29e236de97 Use setuptools packaging for Tryton 7.0
Publish Package / publish (push) Successful in 45s
2026-09-30 18:49:33 -04:00
José Arturo García 8e0d90afae Add Tryton 7.0 compatibility branch
Publish Package / publish (push) Successful in 3m25s
2026-09-29 09:17:37 -04:00
joseagrc c4765f1469 Round Dominican invoice taxes independently
Publish Package / publish (push) Successful in 1m20s
2026-08-30 21:29:56 -04:00
José Arturo García c07f98508f Require Twine 7 for metadata 2.5
Publish Package / publish (push) Successful in 47s
2026-08-16 21:13:56 -04:00
José Arturo García 088e895151 Support current package metadata in publishing
Publish Package / publish (push) Failing after 39s
2026-08-16 21:12:17 -04:00
José Arturo García 100502f7cc Adopt Fundación package prefix
Publish Package / publish (push) Failing after 55s
2026-08-16 21:09:58 -04:00
José Arturo García e188a188be ci: add package publishing workflow
Publish Package / publish (push) Failing after 14m18s
2026-08-12 18:10:26 -04:00
joseagrc 3823f4ae89 Complete account_do localization readiness for 8.0
Add the tax classification model extension, tax views, translation catalogs, package test dependencies, and income statement account type structure required by the Dominican localization.

Validate the module with clean Python 3.11 install, full unit tests, package build, and twine check before publishing to the 8.0 branch.
2026-08-12 17:43:04 -04:00
José Arturo García 4f16961758 Complete Spanish translations for account_do 2026-08-10 23:33:03 -04:00
José Arturo García 3fab54339d Expose Dominican tax classification in views 2026-08-10 23:16:55 -04:00
José Arturo García 03d30589cb Restore shared Dominican tax classification 2026-08-10 23:04:26 -04:00
José Arturo García f3d942a6e4 Structure Dominican financial statements 2026-08-10 22:21:31 -04:00
José Arturo García 609b312611 Make the 8.0 package build reproducible 2026-08-10 22:07:30 -04:00
joseagrc 1583dd3387 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.
2026-08-10 09:10:05 -04:00
joseagrc e646947a6e Harden localization data and validation 2026-08-10 08:42:52 -04:00
joseagrc 6f12c82ce1 Update foreign withholding and tax rules 2026-08-10 08:18:26 -04:00
joseagrc 59f8dc0ede Classify foreign ISR withholdings separately
Assign payments to non-residents to the dedicated ISREXT group in both localization datasets and tax rules. Add regression checks for national and foreign ISR groups.
2026-08-10 02:23:14 -04:00
22 changed files with 780 additions and 126 deletions
+2 -4
View File
@@ -45,7 +45,7 @@ jobs:
if: env.PUBLISH_PACKAGE == 'true'
run: |
python -m pip install --upgrade pip
python -m pip install build twine
python -m pip install 'build>=1.2' 'twine>=7' 'packaging>=26'
- name: Check publishing credentials
if: env.PUBLISH_PACKAGE == 'true'
@@ -71,7 +71,6 @@ jobs:
import json
import os
import re
import tomllib
OWNER = 'tryton-do'
PACKAGE_TYPE = 'pypi'
@@ -89,8 +88,7 @@ jobs:
series = os.environ['PACKAGE_SERIES']
major, minor = map(int, series.split('.'))
pyproject = tomllib.loads(Path('pyproject.toml').read_text())
package_name = pyproject['project']['name']
package_name = 'unpaismejor_account_do'
normalized_name = normalize(package_name)
username = os.environ['TWINE_USERNAME']
+26 -1
View File
@@ -1,6 +1,31 @@
Version 8.1.0 - 2026-08-10
Version 8.0.5 - 2026-08-10
--------------------------
* Translate every field and selection introduced by the module into Spanish
and Latin American Spanish.
* Include translation catalogs in built distributions.
Version 8.0.4 - 2026-08-10
--------------------------
* Display the Dominican tax classification on tax and tax-template forms and
lists.
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
operating and non-operating sections compatible with Tryton core reports.
Version 8.0.1 - 2026-08-09
--------------------------
* Initial release.
* Include project metadata and review documentation in the wheel so the
installed package and its test suite are reproducible.
* Document tax requirements and official source provenance.
* Correct asset, real-estate transfer, legal-tip, vehicle and bovine tax data.
* Remove unsupported or unsafe ITBIS templates and their dependent records.
+19
View File
@@ -1,2 +1,21 @@
# 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 trytond.pool import Pool
from . import account, invoice, ir, tax
__all__ = ['register']
def register():
Pool.register(
ir.ModelData,
invoice.Invoice,
invoice.InvoiceLine,
tax.TaxTemplate,
tax.Tax,
module='account_do', type_='model')
Pool.register(
account.CreateChart,
module='account_do', type_='wizard')
+40 -15
View File
@@ -191,45 +191,70 @@
<field name="sequence" eval="30"/>
<field name="statement">balance</field>
</record>
<record id="do_type_profit_loss_en" model="account.account.type.template">
<field name="name">Current Period Profit or Loss</field>
<field name="parent" ref="do_type_equity_en"/>
<field name="sequence" eval="40"/>
<field name="statement">income</field>
</record>
<record id="do_type_operating_section_en" model="account.account.type.template">
<field name="name">Operating profit or loss</field>
<field name="parent" ref="do_type_profit_loss_en"/>
<field name="sequence" eval="10"/>
<field name="statement">income</field>
</record>
<record id="do_type_revenue_en" model="account.account.type.template">
<field name="name">Revenue</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="40"/>
<field name="parent" ref="do_type_operating_section_en"/>
<field name="sequence" eval="10"/>
<field name="statement">income</field>
<field name="revenue" eval="True"/>
</record>
<record id="do_type_other_income_en" model="account.account.type.template">
<field name="name">Other Income and Gains</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="50"/>
<record id="do_type_expenses_en" model="account.account.type.template">
<field name="name">Operating costs and expenses</field>
<field name="parent" ref="do_type_operating_section_en"/>
<field name="sequence" eval="20"/>
<field name="statement">income</field>
<field name="revenue" eval="True"/>
<field name="expense" eval="True"/>
</record>
<record id="do_type_cogs_en" model="account.account.type.template">
<field name="name">Cost of Sales and Services</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="60"/>
<field name="parent" ref="do_type_expenses_en"/>
<field name="sequence" eval="10"/>
<field name="statement">income</field>
<field name="expense" eval="True"/>
</record>
<record id="do_type_expense_en" model="account.account.type.template">
<field name="name">Operational expenses</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="70"/>
<field name="parent" ref="do_type_expenses_en"/>
<field name="sequence" eval="20"/>
<field name="statement">income</field>
<field name="expense" eval="True"/>
</record>
<record id="do_type_non_operating_section_en" model="account.account.type.template">
<field name="name">Non-operating profit or loss</field>
<field name="parent" ref="do_type_profit_loss_en"/>
<field name="sequence" eval="20"/>
<field name="statement">income</field>
</record>
<record id="do_type_other_income_en" model="account.account.type.template">
<field name="name">Other Income and Gains</field>
<field name="parent" ref="do_type_non_operating_section_en"/>
<field name="sequence" eval="10"/>
<field name="statement">income</field>
<field name="revenue" eval="True"/>
</record>
<record id="do_type_financial_expense_en" model="account.account.type.template">
<field name="name">Financial expenses</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="80"/>
<field name="parent" ref="do_type_non_operating_section_en"/>
<field name="sequence" eval="20"/>
<field name="statement">income</field>
<field name="expense" eval="True"/>
</record>
<record id="do_type_income_tax_en" model="account.account.type.template">
<field name="name">Income Tax</field>
<field name="parent" ref="do_type_root_en"/>
<field name="sequence" eval="90"/>
<field name="parent" ref="do_type_non_operating_section_en"/>
<field name="sequence" eval="30"/>
<field name="statement">income</field>
<field name="expense" eval="True"/>
</record>
+40 -15
View File
@@ -189,45 +189,70 @@
<field name="sequence" eval="30" />
<field name="statement">balance</field>
</record>
<record id="do_type_profit_loss_es_419" model="account.account.type.template">
<field name="name">Resultado del período actual</field>
<field name="parent" ref="do_type_equity_es_419" />
<field name="sequence" eval="40" />
<field name="statement">income</field>
</record>
<record id="do_type_operating_section_es_419" model="account.account.type.template">
<field name="name">Resultado de operación</field>
<field name="parent" ref="do_type_profit_loss_es_419" />
<field name="sequence" eval="10" />
<field name="statement">income</field>
</record>
<record id="do_type_revenue_es_419" model="account.account.type.template">
<field name="name">Ingresos de actividades ordinarias</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="40" />
<field name="parent" ref="do_type_operating_section_es_419" />
<field name="sequence" eval="10" />
<field name="statement">income</field>
<field name="revenue" eval="True" />
</record>
<record id="do_type_other_income_es_419" model="account.account.type.template">
<field name="name">Otros ingresos y ganancias</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="50" />
<record id="do_type_expenses_es_419" model="account.account.type.template">
<field name="name">Costos y gastos de operación</field>
<field name="parent" ref="do_type_operating_section_es_419" />
<field name="sequence" eval="20" />
<field name="statement">income</field>
<field name="revenue" eval="True" />
<field name="expense" eval="True" />
</record>
<record id="do_type_cogs_es_419" model="account.account.type.template">
<field name="name">Costo de ventas y servicios</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="60" />
<field name="parent" ref="do_type_expenses_es_419" />
<field name="sequence" eval="10" />
<field name="statement">income</field>
<field name="expense" eval="True" />
</record>
<record id="do_type_expense_es_419" model="account.account.type.template">
<field name="name">Gastos operacionales</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="70" />
<field name="parent" ref="do_type_expenses_es_419" />
<field name="sequence" eval="20" />
<field name="statement">income</field>
<field name="expense" eval="True" />
</record>
<record id="do_type_non_operating_section_es_419" model="account.account.type.template">
<field name="name">Resultado no operativo</field>
<field name="parent" ref="do_type_profit_loss_es_419" />
<field name="sequence" eval="20" />
<field name="statement">income</field>
</record>
<record id="do_type_other_income_es_419" model="account.account.type.template">
<field name="name">Otros ingresos y ganancias</field>
<field name="parent" ref="do_type_non_operating_section_es_419" />
<field name="sequence" eval="10" />
<field name="statement">income</field>
<field name="revenue" eval="True" />
</record>
<record id="do_type_financial_expense_es_419" model="account.account.type.template">
<field name="name">Gastos financieros</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="80" />
<field name="parent" ref="do_type_non_operating_section_es_419" />
<field name="sequence" eval="20" />
<field name="statement">income</field>
<field name="expense" eval="True" />
</record>
<record id="do_type_income_tax_es_419" model="account.account.type.template">
<field name="name">Impuesto a las ganancias</field>
<field name="parent" ref="do_type_root_es_419" />
<field name="sequence" eval="90" />
<field name="parent" ref="do_type_non_operating_section_es_419" />
<field name="sequence" eval="30" />
<field name="statement">income</field>
<field name="expense" eval="True" />
</record>
+4 -13
View File
@@ -14,31 +14,22 @@ else:
def get_info():
import configparser
import json
import subprocess
module_dir = os.path.dirname(os.path.dirname(__file__))
config = configparser.ConfigParser()
config.read_file(open(os.path.join(module_dir, 'tryton.cfg')))
info = dict(config.items('tryton'))
metadata_cmd = 'python3 -m build -qq --metadata'
if os.environ.get('DOC_NO_ISOLATION'):
metadata_cmd += ' --no-isolation'
metadata = subprocess.check_output(
metadata_cmd, shell=True, encoding='utf-8', cwd=module_dir).strip()
metadata = json.loads(metadata)
info['name'] = metadata['name']
info['description'] = metadata['summary']
major_version, minor_version, _ = metadata['version'].split('.', 2)
info['name'] = 'unpaismejor_account_do'
info['description'] = 'Dominican Republic accounting for Tryton'
major_version, minor_version, _ = info['version'].split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
if minor_version % 2:
info['series'] = 'latest'
info['branch'] = 'branch/default'
else:
info['series'] = '.'.join(metadata['version'].split('.', 2)[:2])
info['series'] = '.'.join(info['version'].split('.', 2)[:2])
info['branch'] = 'branch/' + info['series']
for key in {'depends', 'extras_depend'}:
+1 -1
View File
@@ -1,7 +1,7 @@
Releases
========
8.1.0
8.0.1
-----
Initial release with the Dominican chart of accounts, fiscal accounts, taxes,
+39
View File
@@ -0,0 +1,39 @@
"""Dominican invoice tax rounding policy.
Tryton's generic taxable mixin compensates the aggregate currency-rounding
remainder on the first non-zero tax. That keeps the rounded aggregate equal
to the aggregate of the unrounded taxes, but it can change the reported value
of a specific Dominican tax (ITBIS, ISC or CDT).
Dominican fiscal documents state every tax in currency precision. Each tax
must therefore retain its own rounded result; no residual is redistributed
between different tax liabilities or tax credits.
"""
from trytond.model import fields
from trytond.pool import PoolMeta
def round_taxes_independently(currency, taxes):
"""Round every tax amount without reallocating the aggregate residual."""
if not currency:
return
for taxline in taxes.values():
taxline.amount = currency.round(taxline.amount)
class IndependentTaxRoundingMixin:
__slots__ = ()
@fields.depends('currency')
def _round_taxes(self, taxes):
round_taxes_independently(self.currency, taxes)
class Invoice(IndependentTaxRoundingMixin, metaclass=PoolMeta):
__name__ = 'account.invoice'
class InvoiceLine(IndependentTaxRoundingMixin, metaclass=PoolMeta):
__name__ = 'account.invoice.line'
+83
View File
@@ -0,0 +1,83 @@
#
msgid ""
msgstr ""
"Project-Id-Version: account_do 8.0\n"
"PO-Revision-Date: 2026-08-10 00:00-0400\n"
"Last-Translator: Fundación Un País Mejor\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgctxt "field:account.tax,tax_kind:"
msgid "Tax Kind"
msgstr "Clasificación fiscal"
msgctxt "field:account.tax.template,tax_kind:"
msgid "Tax Kind"
msgstr "Clasificación fiscal"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS"
msgstr "ITBIS"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS Withholding"
msgstr "Retención de ITBIS"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS Acquirer Withholding"
msgstr "Retención de ITBIS por adquirencia"
msgctxt "selection:account.tax,tax_kind:"
msgid "ISR Withholding"
msgstr "Retención de ISR"
msgctxt "selection:account.tax,tax_kind:"
msgid "Selective Consumption Tax (ISC)"
msgstr "Impuesto Selectivo al Consumo (ISC)"
msgctxt "selection:account.tax,tax_kind:"
msgid "INDOTEL Contribution (CDT)"
msgstr "Contribución al Desarrollo de las Telecomunicaciones (CDT)"
msgctxt "selection:account.tax,tax_kind:"
msgid "Statutory Tip"
msgstr "Propina legal"
msgctxt "selection:account.tax,tax_kind:"
msgid "Other Taxes"
msgstr "Otros impuestos"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS"
msgstr "ITBIS"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS Withholding"
msgstr "Retención de ITBIS"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS Acquirer Withholding"
msgstr "Retención de ITBIS por adquirencia"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ISR Withholding"
msgstr "Retención de ISR"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Selective Consumption Tax (ISC)"
msgstr "Impuesto Selectivo al Consumo (ISC)"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "INDOTEL Contribution (CDT)"
msgstr "Contribución al Desarrollo de las Telecomunicaciones (CDT)"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Statutory Tip"
msgstr "Propina legal"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Other Taxes"
msgstr "Otros impuestos"
+83
View File
@@ -0,0 +1,83 @@
#
msgid ""
msgstr ""
"Project-Id-Version: account_do 8.0\n"
"PO-Revision-Date: 2026-08-10 00:00-0400\n"
"Last-Translator: Fundación Un País Mejor\n"
"Language-Team: Spanish (Latin America)\n"
"Language: es_419\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgctxt "field:account.tax,tax_kind:"
msgid "Tax Kind"
msgstr "Clasificación fiscal"
msgctxt "field:account.tax.template,tax_kind:"
msgid "Tax Kind"
msgstr "Clasificación fiscal"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS"
msgstr "ITBIS"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS Withholding"
msgstr "Retención de ITBIS"
msgctxt "selection:account.tax,tax_kind:"
msgid "ITBIS Acquirer Withholding"
msgstr "Retención de ITBIS por adquirencia"
msgctxt "selection:account.tax,tax_kind:"
msgid "ISR Withholding"
msgstr "Retención de ISR"
msgctxt "selection:account.tax,tax_kind:"
msgid "Selective Consumption Tax (ISC)"
msgstr "Impuesto Selectivo al Consumo (ISC)"
msgctxt "selection:account.tax,tax_kind:"
msgid "INDOTEL Contribution (CDT)"
msgstr "Contribución al Desarrollo de las Telecomunicaciones (CDT)"
msgctxt "selection:account.tax,tax_kind:"
msgid "Statutory Tip"
msgstr "Propina legal"
msgctxt "selection:account.tax,tax_kind:"
msgid "Other Taxes"
msgstr "Otros impuestos"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS"
msgstr "ITBIS"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS Withholding"
msgstr "Retención de ITBIS"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ITBIS Acquirer Withholding"
msgstr "Retención de ITBIS por adquirencia"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "ISR Withholding"
msgstr "Retención de ISR"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Selective Consumption Tax (ISC)"
msgstr "Impuesto Selectivo al Consumo (ISC)"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "INDOTEL Contribution (CDT)"
msgstr "Contribución al Desarrollo de las Telecomunicaciones (CDT)"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Statutory Tip"
msgstr "Propina legal"
msgctxt "selection:account.tax.template,tax_kind:"
msgid "Other Taxes"
msgstr "Otros impuestos"
-59
View File
@@ -1,59 +0,0 @@
[build-system]
requires = ['hatchling >= 1', 'hatch-tryton']
build-backend = 'hatchling.build'
[project]
name = 'trytond_account_do'
dynamic = ['version', 'dependencies', 'optional-dependencies', 'authors', 'readme']
requires-python = '>=3.10'
maintainers = [
{name = "Fundación Un País Mejor"},
]
description = "Dominican Republic accounting for Tryton"
license = 'GPL-3.0-or-later'
license-files = ['LICENSE', 'COPYRIGHT']
keywords = ["tryton", "accounting", "coa", "dominican-republic"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Plugins",
"Framework :: Tryton",
"Topic :: Office/Business",
"Topic :: Office/Business :: Financial :: Accounting",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[project.entry-points.'trytond.modules']
account_do = 'trytond.modules.account_do'
[project.urls]
homepage = "https://www.unpaismejor.org.do"
documentation = "https://docs.tryton.org/modules-account-do/"
changelog = "https://docs.tryton.org/modules-account-do/releases.html"
forum = "https://www.tryton.org/forum"
issues = "https://bugs.tryton.org/tryton"
repository = "https://code.unpaismejor.org.do/tryton-do/account_do"
funding = "https://www.tryton.org/donate"
[tool.hatch.build]
include = [
'**/tryton.cfg',
'**/*.py',
'**/*.xml',
'view/**/*.xml',
'**/*.fodt',
'icons/**/*.svg',
'tests/**/*.rst',
'tests/**/*.json',
]
exclude = ['doc']
[tool.hatch.build.targets.wheel.sources]
"" = "trytond/modules/account_do"
[tool.hatch.metadata.hooks.tryton]
dependencies = []
copyright = 'COPYRIGHT'
readme = 'README.rst'
[tool.hatch.metadata.hooks.tryton.tryton-optional-dependencies]
test = ['proteus']
+74
View File
@@ -0,0 +1,74 @@
from configparser import ConfigParser
from pathlib import Path
from setuptools import setup
MODULE = 'account_do'
PREFIX = 'trytond.modules'
PACKAGE = f'{PREFIX}.{MODULE}'
def read(path):
return Path(path).read_text(encoding='utf-8')
config = ConfigParser()
config.read('tryton.cfg')
version = config.get('tryton', 'version')
major, minor, _ = version.split('.', 2)
series = f'{major}.{minor}'
next_major = f'{major}.{int(minor) + 1}'
setup(
name='unpaismejor_account_do',
version=version,
description='Dominican Republic accounting for Tryton',
long_description=read('README.rst'),
long_description_content_type='text/x-rst',
author='Fundación Un País Mejor',
maintainer='Fundación Un País Mejor',
url='https://code.unpaismejor.org.do/tryton-do/account_do',
keywords='tryton accounting coa dominican-republic',
license='GPL-3.0-or-later',
python_requires='>=3.8',
packages=[PACKAGE],
package_dir={PACKAGE: '.'},
package_data={
PACKAGE: [
'tryton.cfg',
'*.xml',
'view/*.xml',
'locale/*.po',
'doc/*.rst',
],
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Framework :: Tryton',
'Topic :: Office/Business',
'Topic :: Office/Business :: Financial :: Accounting',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires=[
f'trytond >= {series}, < {next_major}',
f'trytond_account >= {series}, < {next_major}',
f'trytond_account_invoice >= {series}, < {next_major}',
],
extras_require={
'test': [
f'proteus >= {series}, < {next_major}',
f'trytond_account_asset >= {series}, < {next_major}',
f'trytond_account_deposit >= {series}, < {next_major}',
f'trytond_account_stock_continental >= {series}, < {next_major}',
],
},
entry_points={
'trytond.modules': [
f'{MODULE} = {PACKAGE}',
],
},
zip_safe=False,
)
+162
View File
@@ -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))
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="tax_view_form">
<field name="model">account.tax</field>
<field name="inherit" ref="account.tax_view_form"/>
<field name="name">tax_form</field>
</record>
<record model="ir.ui.view" id="tax_view_list">
<field name="model">account.tax</field>
<field name="inherit" ref="account.tax_view_list"/>
<field name="name">tax_list</field>
</record>
<record model="ir.ui.view" id="tax_template_view_form">
<field name="model">account.tax.template</field>
<field name="inherit" ref="account.tax_template_view_form"/>
<field name="name">tax_template_form</field>
</record>
<record model="ir.ui.view" id="tax_template_view_list">
<field name="model">account.tax.template</field>
<field name="inherit" ref="account.tax_template_view_list"/>
<field name="name">tax_template_list</field>
</record>
</data>
</tryton>
+2 -1
View File
@@ -11,7 +11,8 @@ Imports::
Install account_do::
>>> config = activate_modules('account_do', create_company)
>>> config = activate_modules('account_do')
>>> _ = create_company()
Get the test company::
+136 -15
View File
@@ -4,12 +4,15 @@ from collections import Counter
from configparser import ConfigParser
from decimal import Decimal
from pathlib import Path
from types import SimpleNamespace
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
from trytond.modules.account_do.invoice import round_taxes_independently
MODULE_DIR = Path(__file__).resolve().parent.parent
TAX_CODE_LINE_AUDIT = {
@@ -125,6 +128,56 @@ class AccountDoTestCase(ModuleTestCase):
'account_stock_continental',
]
@with_transaction()
def test_invoice_taxes_round_independently(self):
"""A rounding residual must not change ITBIS, ISC or CDT."""
pool = Pool()
Currency = pool.get('currency.currency')
currency, = Currency.create([{
'name': 'Peso dominicano',
'code': 'DOP',
'symbol': 'RD$',
'digits': 2,
'rounding': Decimal('0.01'),
}])
taxes = {
'itbis': SimpleNamespace(amount=Decimal('91.8036')),
'isc': SimpleNamespace(amount=Decimal('51.0020')),
'cdt': SimpleNamespace(amount=Decimal('9.9000')),
}
round_taxes_independently(currency, taxes)
self.assertEqual(taxes['itbis'].amount, Decimal('91.80'))
self.assertEqual(taxes['isc'].amount, Decimal('51.00'))
self.assertEqual(taxes['cdt'].amount, Decimal('9.90'))
self.assertEqual(
sum(tax.amount for tax in taxes.values()), Decimal('152.70'))
@with_transaction()
def test_invoice_credit_note_taxes_round_independently(self):
"""The same policy applies symmetrically to credit notes."""
pool = Pool()
Currency = pool.get('currency.currency')
currency, = Currency.create([{
'name': 'Peso dominicano',
'code': 'DOP',
'symbol': 'RD$',
'digits': 2,
'rounding': Decimal('0.01'),
}])
taxes = {
'itbis': SimpleNamespace(amount=Decimal('-91.8036')),
'isc': SimpleNamespace(amount=Decimal('-51.0020')),
'cdt': SimpleNamespace(amount=Decimal('-9.9000')),
}
round_taxes_independently(currency, taxes)
self.assertEqual(taxes['itbis'].amount, Decimal('-91.80'))
self.assertEqual(taxes['isc'].amount, Decimal('-51.00'))
self.assertEqual(taxes['cdt'].amount, Decimal('-9.90'))
@with_transaction()
def test_chart_template_is_loaded_by_default(self):
'Test English Dominican chart is loaded in an English database'
@@ -229,6 +282,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 +416,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,10 +621,10 @@ class AccountDoUnitTestCase(unittest.TestCase):
if line.strip()
]
self.assertEqual(config.get('tryton', 'version'), '8.1.0')
self.assertEqual(config.get('tryton', 'version'), '7.0.0')
self.assertEqual(
set(lines('tryton', 'depends')),
{'account', 'ir'})
{'account', 'account_invoice', 'ir'})
self.assertEqual(
lines('tryton', 'xml'),
[
@@ -580,26 +636,40 @@ class AccountDoUnitTestCase(unittest.TestCase):
'tax_do_es_419.xml',
'tax_code_do_es_419.xml',
'tax_rule_do_es_419.xml',
'tax.xml',
])
self.assertEqual(
lines('register', 'model'),
[
'ir.ModelData',
'invoice.Invoice',
'invoice.InvoiceLine',
'tax.TaxTemplate',
'tax.Tax',
])
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.assertTrue((MODULE_DIR / 'tax.py').is_file())
setup_py = (MODULE_DIR / 'setup.py').read_text(encoding='utf-8')
self.assertIn("name='unpaismejor_account_do'", setup_py)
self.assertIn(
"account_do = 'trytond.modules.account_do'", pyproject)
"f'{MODULE} = {PACKAGE}'", setup_py)
self.assertIn(
'{name = "Fundación Un País Mejor"}', pyproject)
"maintainer='Fundación Un País Mejor'", setup_py)
self.assertIn(
'homepage = "https://www.unpaismejor.org.do"', pyproject)
"url='https://code.unpaismejor.org.do/tryton-do/account_do'",
setup_py)
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)
"python_requires='>=3.8'", setup_py)
for package_file in [
"'*.xml'",
"'view/*.xml'",
"'locale/*.po'",
"'doc/*.rst'",
]:
self.assertIn(package_file, setup_py)
for filename in [
'__init__.py',
'account.py',
@@ -640,7 +710,7 @@ class AccountDoUnitTestCase(unittest.TestCase):
def test_xml_record_inventory_is_explicit(self):
expected = {
'account_chart_do_en.xml': {
'account.account.type.template': 36,
'account.account.type.template': 40,
'account.account.template': 287,
},
'tax_do_en.xml': {
@@ -656,7 +726,7 @@ class AccountDoUnitTestCase(unittest.TestCase):
'account.tax.rule.line.template': 34,
},
'account_chart_do_es_419.xml': {
'account.account.type.template': 36,
'account.account.type.template': 40,
'account.account.template': 287,
},
'tax_do_es_419.xml': {
@@ -742,6 +812,33 @@ class AccountDoUnitTestCase(unittest.TestCase):
with self.subTest(marker=marker):
self.assertNotIn(marker, text)
def test_spanish_translations_cover_module_strings(self):
translations = {
'Tax Kind': 'Clasificación fiscal',
'ITBIS': 'ITBIS',
'ITBIS Withholding': 'Retención de ITBIS',
'ITBIS Acquirer Withholding': (
'Retención de ITBIS por adquirencia'),
'ISR Withholding': 'Retención de ISR',
'Selective Consumption Tax (ISC)': (
'Impuesto Selectivo al Consumo (ISC)'),
'INDOTEL Contribution (CDT)': (
'Contribución al Desarrollo de las Telecomunicaciones (CDT)'),
'Statutory Tip': 'Propina legal',
'Other Taxes': 'Otros impuestos',
}
for language in ['es', 'es_419']:
text = (MODULE_DIR / 'locale' / f'{language}.po').read_text(
encoding='utf-8')
with self.subTest(language=language):
for source, target in translations.items():
self.assertIn(f'msgid "{source}"', text)
self.assertIn(f'msgstr "{target}"', text)
self.assertIn(
'msgctxt "field:account.tax,tax_kind:"', text)
self.assertIn(
'msgctxt "field:account.tax.template,tax_kind:"', text)
def test_spanish_chart_has_complete_parallel_identifiers(self):
pairs = [
('account_chart_do_en.xml', 'account_chart_do_es_419.xml'),
@@ -873,6 +970,30 @@ class AccountDoUnitTestCase(unittest.TestCase):
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(
types['do_type_profit_loss']['parent'], 'do_type_equity')
self.assertEqual(
types['do_type_operating_section']['parent'],
'do_type_profit_loss')
self.assertEqual(
types['do_type_revenue']['parent'],
'do_type_operating_section')
self.assertEqual(
types['do_type_expenses']['parent'],
'do_type_operating_section')
self.assertEqual(
types['do_type_cogs']['parent'], 'do_type_expenses')
self.assertEqual(
types['do_type_expense']['parent'], 'do_type_expenses')
self.assertEqual(
types['do_type_non_operating_section']['parent'],
'do_type_profit_loss')
for account_type in [
'do_type_other_income', 'do_type_financial_expense',
'do_type_income_tax']:
self.assertEqual(
types[account_type]['parent'],
'do_type_non_operating_section')
self.assertEqual(
accounts['do_account_110201']['type'], 'do_type_receivable')
self.assertEqual(
+1 -1
View File
@@ -1,5 +1,5 @@
[tox]
envlist = {py310,py311,py312,py313,py314}-{sqlite,postgresql}
envlist = {py38,py39,py310,py311}-{sqlite,postgresql}
[testenv]
changedir = {env_site_packages_dir}
+7 -1
View File
@@ -1,7 +1,8 @@
[tryton]
version=8.1.0
version=7.0.0
depends:
account
account_invoice
ir
xml:
account_chart_do_en.xml
@@ -12,9 +13,14 @@ xml:
tax_do_es_419.xml
tax_code_do_es_419.xml
tax_rule_do_es_419.xml
tax.xml
[register]
model:
ir.ModelData
invoice.Invoice
invoice.InvoiceLine
tax.TaxTemplate
tax.Tax
wizard:
account.CreateChart
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//page[@id='general']/field[@name='group']" position="after">
<label name="tax_kind"/>
<field name="tax_kind"/>
</xpath>
</data>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='group']" position="after">
<field name="tax_kind" optional="1"/>
</xpath>
</data>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//page[@id='general']/field[@name='group']" position="after">
<label name="tax_kind"/>
<field name="tax_kind"/>
</xpath>
</data>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='group']" position="after">
<field name="tax_kind" optional="1"/>
</xpath>
</data>