From fc5cef658fe461f88933ba83bc6f35e7d7168365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Arturo=20Garci=CC=81a?= Date: Sun, 9 Aug 2026 13:24:59 -0400 Subject: [PATCH] Strengthen account_do tests and publishing checks Add static coverage for module configuration, XML inventory, tax views, translations, migration metadata, and the Gitea publishing workflow. Make Gitea Actions run install, compileall, unittest, build, and twine check before publishing packages from series branches. Fix packaging documentation and RST rendering issues so package validation passes before upload. --- .gitea/workflows/publish.yml | 41 +++++- README.rst | 44 +++++-- doc/publishing.rst | 23 ++-- locale/es.po | 4 + locale/es_419.po | 4 + tests/test_module.py | 239 +++++++++++++++++++++++++++++++++++ 6 files changed, 330 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index 683b45d..1224c6c 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -1,14 +1,53 @@ -name: Publish Package +name: Test and Publish Package on: push: branches: - "**" + pull_request: + branches: + - "**" workflow_dispatch: jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install package and test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e '.[test]' + + - name: Compile Python sources + run: | + python -m compileall . + + - name: Run module tests + run: | + python -m unittest discover -s tests -p 'test*.py' + + - name: Build package + run: | + python -m pip install build twine + python -m build + + - name: Check package + run: | + python -m twine check dist/* + publish: runs-on: ubuntu-latest + needs: test + if: github.event_name != 'pull_request' steps: - name: Checkout diff --git a/README.rst b/README.rst index 1bd8494..526ed48 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ Pruebas Las pruebas validan la instalación del módulo, la creación del plan para una compañía, la integridad de referencias XML, la clasificación contable principal, los códigos fiscales y las categorías fiscales dominicanas. Cumplimiento base NIIF ---------------------- +---------------------- ``account_do`` cubre el 100% de la base estructural que corresponde a una localización contable: catálogo NIIF/RD, tipos de presentación, cuentas fiscales dominicanas, impuestos, reglas, metadata legal y pruebas de instalación. La medición NIIF, estimaciones, cierres, estados financieros, notas y revelaciones dependen de políticas contables y módulos operativos complementarios. @@ -49,15 +49,33 @@ Matriz de auditoría La cobertura de auditoría fiscal queda formalizada en pruebas para evitar cambios silenciosos en cuentas, clasificación o vigencias. -=============================== =============================================== ============================================== -Área Cobertura Prueba -=============================== =============================================== ============================================== -Impuesto contra cuenta 55 plantillas de ``tax_do.xml`` contra cuenta ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT`` -Clasificación fiscal ``tax_kind``, ``tax_fiscal_type`` y aplicación ``test_tax_templates_are_classified`` -Metadata legal y vigencia Referencia, artículo, formulario, estado y fecha ``test_tax_templates_are_classified`` -Signos de tax codes Plantillas y códigos reales creados por compañía ``test_tax_code_templates_use_expected_signs`` y chart real -Reglas con sustitución Impuesto cheques 0.15% hasta 2026-07-02 y 0.20% desde 2026-07-03 ``test_tax_rule_templates_include_date_sensitive_bank_tax`` -Documentación contra XML La matriz apunta a XML real y falla ante ids/campos faltantes ``test_xml_references_are_resolved_inside_module`` -Plan NIIF contra XML Códigos NIIF críticos, tipos y políticas ``test_ifrs_chart_is_complete_and_typed`` y ``test_ifrs_policy_matrix_is_represented_in_chart`` -Facturas reales Factura venta/compra posteada con ITBIS 18% ``account_invoice_do.tests.test_module`` -=============================== =============================================== ============================================== +.. list-table:: + :header-rows: 1 + + * - Área + - Cobertura + - Prueba + * - Impuesto contra cuenta + - 55 plantillas de ``tax_do.xml`` contra cuenta. + - ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT`` + * - Clasificación fiscal + - ``tax_kind``, ``tax_fiscal_type`` y aplicación. + - ``test_tax_templates_are_classified`` + * - Metadata legal y vigencia + - Referencia, artículo, formulario, estado y fecha. + - ``test_tax_templates_are_classified`` + * - Signos de tax codes + - Plantillas y códigos reales creados por compañía. + - ``test_tax_code_templates_use_expected_signs`` y chart real. + * - Reglas con sustitución + - Impuesto cheques 0.15% hasta 2026-07-02 y 0.20% desde 2026-07-03. + - ``test_tax_rule_templates_include_date_sensitive_bank_tax`` + * - Documentación contra XML + - La matriz apunta a XML real y falla ante ids/campos faltantes. + - ``test_xml_references_are_resolved_inside_module`` + * - Plan NIIF contra XML + - Códigos NIIF críticos, tipos y políticas. + - ``test_ifrs_chart_is_complete_and_typed`` y ``test_ifrs_policy_matrix_is_represented_in_chart`` + * - Facturas reales + - Factura venta/compra posteada con ITBIS 18%. + - ``account_invoice_do.tests.test_module`` diff --git a/doc/publishing.rst b/doc/publishing.rst index a0082fc..17d4cb6 100644 --- a/doc/publishing.rst +++ b/doc/publishing.rst @@ -1,29 +1,30 @@ Publishing ========== -Every pushed commit builds, validates, and publishes the package to the Gitea -PyPI-compatible package registry. The workflow intentionally does not use -``actions/upload-artifact`` because current Gitea Actions runners do not support -the GitHub artifact API required by ``upload-artifact@v4``. +Every pushed commit installs the package, compiles the Python sources, runs the +module tests, builds the distribution, and validates it with ``twine check``. +Only branches named with the Tryton series, such as ``8.0``, continue to the +publishing step after those checks pass. -The publishing workflow creates a CI-only package version by appending the run -number to the module version from ``tryton.cfg``. For example, a module version -of ``8.0.0`` is published as ``8.0.0.post123`` on run number ``123``. +The publishing workflow creates a CI-only package version by reading the +published versions in Gitea's PyPI-compatible package registry and assigning the +next patch version for the branch series. For example, branch ``8.0`` publishes +``8.0.0`` when no package exists yet, then ``8.0.1``, ``8.0.2``, and so on. This is necessary because Gitea's PyPI-compatible registry is a custom package repository for Twine and does not support ``twine upload --skip-existing``. Required repository secrets: -* ``GITEA_PACKAGE_USER`` -* ``GITEA_PACKAGE_TOKEN`` +* ``REGISTRY_USER`` +* ``REGISTRY_PASSWORD`` Publish a release branch: .. code-block:: console - git switch -c 8.0.0 - git push origin 8.0.0 + git switch 8.0 + git push origin 8.0 Install from Gitea: diff --git a/locale/es.po b/locale/es.po index 4061208..a140bbc 100644 --- a/locale/es.po +++ b/locale/es.po @@ -1074,6 +1074,10 @@ msgctxt "model:account.account.type.template,name:do_type_income_tax" msgid "Impuesto a las ganancias" msgstr "Impuesto a las ganancias" +msgctxt "model:account.account.type.template,name:do_type_control" +msgid "Cuentas de cierre y control" +msgstr "Cuentas de cierre y control" + msgctxt "model:account.account.type.template,name:do_type_intangible" msgid "Activos intangibles" msgstr "Activos intangibles" diff --git a/locale/es_419.po b/locale/es_419.po index b6b5e6c..da3adf8 100644 --- a/locale/es_419.po +++ b/locale/es_419.po @@ -1066,6 +1066,10 @@ msgctxt "model:account.account.type.template,name:do_type_income_tax" msgid "Impuesto a las ganancias" msgstr "Impuesto a las ganancias" +msgctxt "model:account.account.type.template,name:do_type_control" +msgid "Cuentas de cierre y control" +msgstr "Cuentas de cierre y control" + msgctxt "model:account.account.type.template,name:do_type_intangible" msgid "Activos intangibles" msgstr "Activos intangibles" diff --git a/tests/test_module.py b/tests/test_module.py index a3756cf..63fba50 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -1,6 +1,7 @@ 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 @@ -237,6 +238,21 @@ def _iter_xml_records(*filenames): 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' @@ -448,6 +464,229 @@ class AccountDoTestCase(ModuleTestCase): 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',