diff --git a/l10n_es_extension/README.rst b/l10n_es_extension/README.rst index 342855a5f..cc46a128f 100644 --- a/l10n_es_extension/README.rst +++ b/l10n_es_extension/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================= L10n ES Extension ================= @@ -13,7 +17,7 @@ L10n ES Extension .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github @@ -22,7 +26,18 @@ L10n ES Extension |badge1| |badge2| |badge3| -* This module extends l10n_es +This module extends l10n_es with generic pieces the official Spanish +localization does not ship: + +* Split of the exempt output VAT into "con derecho a deducción" / + "sin derecho a deducción" variants. +* The complete non-deductible input VAT family: goods variants + (bienes corrientes / bienes de inversión) for the official 4%, 10% + and 21% non-deductible templates, plus the intra-community and + reverse-charge non-deductible cases, wired into the official + intra-community and IRPF 15 fiscal positions. +* VAT group flags (``is_vat``) on the official tax groups and + consistency checks on taxes and journal entries. **Table of contents** diff --git a/l10n_es_extension/__manifest__.py b/l10n_es_extension/__manifest__.py index ec021220d..0e0822356 100644 --- a/l10n_es_extension/__manifest__.py +++ b/l10n_es_extension/__manifest__.py @@ -5,7 +5,7 @@ { "name": "L10n ES Extension", "summary": "This module extends l10n_es", - "version": "14.0.1.0.1", + "version": "14.0.3.1.0", "category": "Accounting", "author": "NuoBiT Solutions, S.L.", "website": "https://github.com/nuobit/odoo-addons", @@ -16,5 +16,6 @@ "data": [ "data/account_data.xml", "data/account_tax_data.xml", + "data/account_fiscal_position_template_data.xml", ], } diff --git a/l10n_es_extension/data/account_fiscal_position_template_data.xml b/l10n_es_extension/data/account_fiscal_position_template_data.xml new file mode 100644 index 000000000..1c04b55e9 --- /dev/null +++ b/l10n_es_extension/data/account_fiscal_position_template_data.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_es_extension/data/account_tax_data.xml b/l10n_es_extension/data/account_tax_data.xml index 882eb3a76..2c2b53b2c 100644 --- a/l10n_es_extension/data/account_tax_data.xml +++ b/l10n_es_extension/data/account_tax_data.xml @@ -50,4 +50,771 @@ ]" /> + + + + + + + + + IVA Soportado no deducible 4% (servicios corrientes) + + + + IVA Soportado no deducible 5% (servicios corrientes) + + + + IVA Soportado no deducible 10% (servicios corrientes) + + + + IVA Soportado no deducible 21% (servicios corrientes) + + + + + purchase + IVA Soportado no deducible 4% (bienes corrientes) + + + percent + + + + + + + + purchase + IVA Soportado no deducible 4% (bienes inversión) + + + percent + + + + + + + + + purchase + IVA Soportado no deducible 10% (bienes corrientes) + + + percent + + + + + + + + purchase + IVA Soportado no deducible 10% (bienes inversión) + + + percent + + + + + + + + + purchase + IVA Soportado no deducible 21% (bienes corrientes) + + + percent + + + + + + + + purchase + IVA Soportado no deducible 21% (bienes inversión) + + + percent + + + + + + + + + + + + + + purchase + IVA 4% NO DEDUCIBLE Adquisición Intracomunitario. Bienes de inversión + + + percent + + + + + + + + purchase + IVA 4% NO DEDUCIBLE Adquisición Intracomunitario. Bienes corrientes + + + percent + + + + + + + + purchase + IVA 4% NO DEDUCIBLE Adquisición de servicios intracomunitarios + + + percent + + + + + + + + + purchase + IVA 10% NO DEDUCIBLE Adquisición Intracomunitario. Bienes de inversión + + + percent + + + + + + + + purchase + IVA 10% NO DEDUCIBLE Adquisición Intracomunitario. Bienes corrientes + + + percent + + + + + + + + purchase + IVA 10% NO DEDUCIBLE Adquisición de servicios intracomunitarios + + + percent + + + + + + + + + purchase + IVA 21% NO DEDUCIBLE Adquisición Intracomunitario. Bienes de inversión + + + percent + + + + + + + + purchase + IVA 21% NO DEDUCIBLE Adquisición Intracomunitario. Bienes corrientes + + + percent + + + + + + + + purchase + IVA 21% NO DEDUCIBLE Adquisición de servicios intracomunitarios + + + percent + + + + + diff --git a/l10n_es_extension/migrations/14.0.3.0.0/post-migration.py b/l10n_es_extension/migrations/14.0.3.0.0/post-migration.py new file mode 100644 index 000000000..822ad5d08 --- /dev/null +++ b/l10n_es_extension/migrations/14.0.3.0.0/post-migration.py @@ -0,0 +1,128 @@ +# Copyright 2026 NuoBiT Solutions - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + +MODULE = "l10n_es_extension" + + +def _company_record(env, company, template, model): + """Resolve the company record instantiated from ``template``. + + The chart wizard stamps company records with the xml-id + ``._``, but during a + module handover the custody of an existing record may still sit + under the previous module, so the lookup matches by terminal name + only. More than one match means two modules claim the same + terminal name for the same company: that is never legitimate, so + fail loudly instead of guessing. + """ + xmlid = template.get_external_id().get(template.id) + if not xmlid: + return None + name = "%s_%s" % (company.id, xmlid.split(".", 1)[1]) + imds = env["ir.model.data"].search([("model", "=", model), ("name", "=", name)]) + if len(imds) > 1: + raise RuntimeError( + "fptt materialization: ambiguous company record for %s (company %s):" + " modules %s" % (xmlid, company.id, imds.mapped("module")) + ) + return env[model].browse(imds.res_id) if imds else None + + +def migrate(cr, version): + """Materialize this module's fiscal position lines on existing companies. + + Template mapping lines only flow to companies through the chart + wizard, so the lines added by this version (and any line of this + module a company is missing) must be created explicitly on the + fiscal positions that already exist. Companies or taxes not + instantiated are skipped with a log line: completing a partial + chart is the wizard's job, not this migration's. + """ + if not version: + return + env = api.Environment(cr, SUPERUSER_ID, {}) + imd_model = env["ir.model.data"] + line_model = env["account.fiscal.position.tax"] + tmpl_imds = imd_model.search( + [ + ("module", "=", MODULE), + ("model", "=", "account.fiscal.position.tax.template"), + ] + ) + companies = env["res.company"].search([]) + for imd in tmpl_imds: + tmpl = env["account.fiscal.position.tax.template"].browse(imd.res_id).exists() + if not tmpl: + continue + for company in companies: + position = _company_record( + env, company, tmpl.position_id, "account.fiscal.position" + ) + if not position: + continue + src = _company_record(env, company, tmpl.tax_src_id, "account.tax") + if not src: + _logger.info( + "fptt materialization: company %s has no tax for %s, skipping" + " %s (complete the chart with the update wizard if needed)", + company.id, + tmpl.tax_src_id.name, + imd.name, + ) + continue + dest = None + if tmpl.tax_dest_id: + dest = _company_record(env, company, tmpl.tax_dest_id, "account.tax") + if not dest: + _logger.info( + "fptt materialization: company %s has no tax for %s," + " skipping %s", + company.id, + tmpl.tax_dest_id.name, + imd.name, + ) + continue + if line_model.search_count( + [ + ("position_id", "=", position.id), + ("tax_src_id", "=", src.id), + ("tax_dest_id", "=", dest.id if dest else False), + ] + ): + continue + line_name = "%s_%s" % (company.id, imd.name) + if imd_model.search_count( + [ + ("model", "=", "account.fiscal.position.tax"), + ("name", "=", line_name), + ] + ): + continue + line = line_model.create( + { + "position_id": position.id, + "tax_src_id": src.id, + "tax_dest_id": dest.id if dest else False, + } + ) + imd_model.create( + { + "module": MODULE, + "name": line_name, + "model": "account.fiscal.position.tax", + "res_id": line.id, + "noupdate": True, + } + ) + _logger.info( + "fptt materialization: created %s.%s on position %s (company %s)", + MODULE, + line_name, + position.name, + company.id, + ) diff --git a/l10n_es_extension/migrations/14.0.3.1.0/post-migration.py b/l10n_es_extension/migrations/14.0.3.1.0/post-migration.py new file mode 100644 index 000000000..6631a2129 --- /dev/null +++ b/l10n_es_extension/migrations/14.0.3.1.0/post-migration.py @@ -0,0 +1,87 @@ +# Copyright 2026 NuoBiT Solutions - Eric Antones +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +import logging +import re + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + +# Materialized taxes carrying the l10n_es template xml-id p_iva4_sp_ex, +# any company prefix, any owning module. +_XMLID_RE = re.compile(r"^\d+_account_tax_template_p_iva4_sp_ex$") +_TEMPLATE_ORDER = "base:100|tax:-100|tax:100" + + +def _order_sig(lines): + """Return the "type:factor|..." signature in (sequence, id) order.""" + ordered = lines.sorted(key=lambda r: (r.sequence, r.id)) + return "|".join( + "%s:%d" % (r.repartition_type, int(r.factor_percent)) for r in ordered + ) + + +def migrate(cr, version): + """Re-align p_iva4_sp_ex repartition-line ORDER with the l10n_es template. + + Where a company holds this tax with its two TAX repartition lines in + the REVERSE order of the l10n_es template, the order-sensitive core + update_taxes_from_templates check judges the tax "different" at the + next major-version hop, strips its xml-id and forks it into + "[old] ..." + a duplicate. Setting sequence=2 on the +100% TAX line + restores the (sequence, id) order to base|-100|+100 so the tax stays + template-managed. Pure ordering field: no amounts, accounts, tags, or + rows touched. Idempotent; tolerant of installs without this tax. + """ + if not version: + return + env = api.Environment(cr, SUPERUSER_ID, {}) + imds = env["ir.model.data"].search( + [ + ("model", "=", "account.tax"), + ("name", "=like", "%_account_tax_template_p_iva4_sp_ex"), + ] + ) + taxes = ( + env["account.tax"] + .browse(imds.filtered(lambda d: _XMLID_RE.match(d.name)).mapped("res_id")) + .exists() + ) + if not taxes: + _logger.info("p_iva4_sp_ex reorder: not present on this database, skipping") + return + lines = env["account.tax.repartition.line"].search( + [ + "|", + ("invoice_tax_id", "in", taxes.ids), + ("refund_tax_id", "in", taxes.ids), + ("repartition_type", "=", "tax"), + ("factor_percent", "=", 100), + ("sequence", "!=", 2), + ] + ) + for tax in taxes: + _logger.info( + "p_iva4_sp_ex reorder: tax %s (company %s) BEFORE " + "invoice=[%s] refund=[%s]", + tax.id, + tax.company_id.id, + _order_sig(tax.invoice_repartition_line_ids), + _order_sig(tax.refund_repartition_line_ids), + ) + lines.write({"sequence": 2}) + bad = taxes.filtered( + lambda t: _order_sig(t.invoice_repartition_line_ids) != _TEMPLATE_ORDER + or _order_sig(t.refund_repartition_line_ids) != _TEMPLATE_ORDER + ) + if bad: + raise RuntimeError( + "l10n_es_extension 14.0.3.1.0: %d p_iva4_sp_ex tax(es) still not " + "in template order (%s) after reorder: %s" + % (len(bad), _TEMPLATE_ORDER, bad.ids) + ) + _logger.info( + "p_iva4_sp_ex reorder OK: %d tax(es) in template order (%s)", + len(taxes), + _TEMPLATE_ORDER, + ) diff --git a/l10n_es_extension/readme/DESCRIPTION.rst b/l10n_es_extension/readme/DESCRIPTION.rst index 63562352b..2a377aa9c 100644 --- a/l10n_es_extension/readme/DESCRIPTION.rst +++ b/l10n_es_extension/readme/DESCRIPTION.rst @@ -1 +1,12 @@ -* This module extends l10n_es +This module extends l10n_es with generic pieces the official Spanish +localization does not ship: + +* Split of the exempt output VAT into "con derecho a deducción" / + "sin derecho a deducción" variants. +* The complete non-deductible input VAT family: goods variants + (bienes corrientes / bienes de inversión) for the official 4%, 10% + and 21% non-deductible templates, plus the intra-community and + reverse-charge non-deductible cases, wired into the official + intra-community and IRPF 15 fiscal positions. +* VAT group flags (``is_vat``) on the official tax groups and + consistency checks on taxes and journal entries. diff --git a/l10n_es_extension/static/description/index.html b/l10n_es_extension/static/description/index.html index c4224bb70..59cc49e0a 100644 --- a/l10n_es_extension/static/description/index.html +++ b/l10n_es_extension/static/description/index.html @@ -1,18 +1,18 @@ - -L10n ES Extension +README.rst -
-

L10n ES Extension

+
+ + +Odoo Community Association + +
+

L10n ES Extension

-

Beta License: AGPL-3 NuoBiT/odoo-addons

+

Beta License: AGPL-3 NuoBiT/odoo-addons

+

This module extends l10n_es with generic pieces the official Spanish +localization does not ship:

    -
  • This module extends l10n_es
  • +
  • Split of the exempt output VAT into “con derecho a deducción” / +“sin derecho a deducción” variants.
  • +
  • The complete non-deductible input VAT family: goods variants +(bienes corrientes / bienes de inversión) for the official 4%, 10% +and 21% non-deductible templates, plus the intra-community and +reverse-charge non-deductible cases, wired into the official +intra-community and IRPF 15 fiscal positions.
  • +
  • VAT group flags (is_vat) on the official tax groups and +consistency checks on taxes and journal entries.

Table of contents

@@ -386,7 +401,7 @@

L10n ES Extension

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -394,16 +409,16 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • NuoBiT Solutions
  • S.L.
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is part of the NuoBiT/odoo-addons project on GitHub.

You are welcome to contribute.

+