Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions l10n_uy_edi_special_regime/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3

======================================
Uruguay - CFE Special Taxpayer Regimes
======================================

Este módulo permite emitir CFE válidos para compañías bajo régimen especial de DGI
(Literal E / IVA mínimo o monotributo).

Cuando la compañía está bajo un régimen especial, Uruware firma los CFE con un CAE especial
(valores 2, 3 o 4) y DGI exige que el comprobante cumpla dos condiciones que el módulo estándar
``l10n_uy_edi`` no contempla:

* Indicador de montos brutos ``MntBruto = 3`` (zona A10 del encabezado).
* Indicador de facturación ``IndFact = 16`` (IVA mínimo, Monotributo u otros) en lugar de los
indicadores de tasa de IVA (1 exento, 2 tasa mínima, 3 tasa básica, 4 otra tasa). Los
indicadores conceptuales se siguen usando igual que en el régimen general: 5 (entrega
gratuita), 6/7 (no facturable, ej. anticipos y líneas de descuento) y 10 (exportación) —
confirmado por Uruware.

Sin estos indicadores DGI rechaza el comprobante con el error
*"Si el valor del CAE Especial es 2, 3 o 4 entonces el Ind. Mnt Bruto debe ser 3"* (código 05,
rechazo definitivo que quema el número de CAE).

Aplica a todos los tipos de CFE domésticos: e-Ticket (101), e-Factura (111) y sus notas de
crédito y débito (102, 103, 112, 113). Los CFE de exportación mantienen el comportamiento estándar.

Además, el módulo valida que las líneas del comprobante no tengan IVA a tasa distinta de 0%
(exento): al confirmar la factura se le avisa al usuario con un error bloqueante, y el mismo
chequeo corre antes del envío. Cada rechazo de DGI quema un número de CAE, por lo que conviene
frenar el error en Odoo lo antes posible.

Configuración
-------------

#. En Odoo: en la compañía, campo **Régimen de contribuyente DGI**, seleccionar el régimen que
corresponda (por defecto: régimen general, que no cambia ningún comportamiento).
#. En Uruware: menú **Empresa** → sección **Información Extra** → marcar **"Literal E o
monotributo"** en *Sí*. Sin esta configuración Uruware no firma con el CAE especial.

Ambas configuraciones deben estar alineadas: son espejo una de la otra.

Known issues / Roadmap
======================

Credits
=======

ADHOC

Contributors
------------

* ADHOC

Maintainer
----------

This module is maintained by ADHOC
1 change: 1 addition & 0 deletions l10n_uy_edi_special_regime/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
17 changes: 17 additions & 0 deletions l10n_uy_edi_special_regime/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": """Uruguay - CFE Special Taxpayer Regimes""",
"version": "19.0.1.0.0",
"category": "Accounting/Localizations/EDI",
"countries": ["uy"],
"author": "ADHOC SA",
"depends": [
"l10n_uy_edi",
],
"data": [
"views/res_company_views.xml",
],
"installable": True,
"auto_install": False,
"application": False,
"license": "LGPL-3",
}
73 changes: 73 additions & 0 deletions l10n_uy_edi_special_regime/i18n/es.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_uy_edi_special_regime
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: l10n_uy_edi_special_regime
#: model:ir.model,name:l10n_uy_edi_special_regime.model_res_company
msgid "Companies"
msgstr "Empresas"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__l10n_uy_edi_taxpayer_regime
msgid "DGI Taxpayer Regime"
msgstr "Régimen de contribuyente DGI"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_account_move__display_name
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__display_name
msgid "Display Name"
msgstr "Nombre para mostrar"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields.selection,name:l10n_uy_edi_special_regime.selection__res_company__l10n_uy_edi_taxpayer_regime__general
msgid "General Regime"
msgstr "Régimen general"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_account_move__id
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__id
msgid "ID"
msgstr "ID"

#. module: l10n_uy_edi_special_regime
#: model:ir.model,name:l10n_uy_edi_special_regime.model_account_move
msgid "Journal Entry"
msgstr "Asiento contable"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields.selection,name:l10n_uy_edi_special_regime.selection__res_company__l10n_uy_edi_taxpayer_regime__iva_minimo
msgid "Minimum VAT (Literal E)"
msgstr "IVA mínimo (Literal E)"

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,help:l10n_uy_edi_special_regime.field_res_company__l10n_uy_edi_taxpayer_regime
msgid ""
"Tax regime of the company before DGI. For the Minimum VAT (Literal E) regime the CFE is built with gross amount indicator (MntBruto) = 3 and billing indicator (IndFact) = 16 on every line, as DGI requires when the CFE is signed with a special CAE.\n"
"IMPORTANT: the option 'Literal E o monotributo' must also be enabled in the Uruware company settings ('Información Extra' section) so Uruware signs the CFE with the special CAE."
msgstr ""
"Régimen tributario de la compañía frente a DGI. Para el régimen de IVA mínimo (Literal E) el CFE se genera con indicador de montos brutos (MntBruto) = 3 e indicador de facturación (IndFact) = 16 en todas las líneas, como exige DGI cuando el CFE se firma con un CAE especial.\n"
"IMPORTANTE: la opción 'Literal E o monotributo' también debe activarse en la configuración de la empresa en Uruware (sección 'Información Extra') para que Uruware firme el CFE con el CAE especial."

#. module: l10n_uy_edi_special_regime
#. odoo-python
#: code:addons/l10n_uy_edi_special_regime/models/account_move.py:0
msgid ""
"The company %(company)s is registered under a special DGI taxpayer regime "
"(Literal E), so CFE lines can not include VAT taxes with a rate other than "
"0%% (exempt). Please fix the taxes on the invoice lines before sending "
"(%(taxes)s)"
msgstr ""
"La compañía %(company)s está inscripta en un régimen especial de DGI "
"(Literal E), por lo que las líneas del CFE no pueden incluir impuestos de "
"IVA con tasa distinta de 0%% (exento). Corrija los impuestos de las líneas "
"de la factura antes de enviar (%(taxes)s)"
70 changes: 70 additions & 0 deletions l10n_uy_edi_special_regime/i18n/l10n_uy_edi_special_regime.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_uy_edi_special_regime
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-04 17:05+0000\n"
"PO-Revision-Date: 2026-09-04 17:05+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: l10n_uy_edi_special_regime
#: model:ir.model,name:l10n_uy_edi_special_regime.model_res_company
msgid "Companies"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__l10n_uy_edi_taxpayer_regime
msgid "DGI Taxpayer Regime"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_account_move__display_name
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__display_name
msgid "Display Name"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields.selection,name:l10n_uy_edi_special_regime.selection__res_company__l10n_uy_edi_taxpayer_regime__general
msgid "General Regime"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_account_move__id
#: model:ir.model.fields,field_description:l10n_uy_edi_special_regime.field_res_company__id
msgid "ID"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model,name:l10n_uy_edi_special_regime.model_account_move
msgid "Journal Entry"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields.selection,name:l10n_uy_edi_special_regime.selection__res_company__l10n_uy_edi_taxpayer_regime__iva_minimo
msgid "Minimum VAT (Literal E)"
msgstr ""

#. module: l10n_uy_edi_special_regime
#: model:ir.model.fields,help:l10n_uy_edi_special_regime.field_res_company__l10n_uy_edi_taxpayer_regime
msgid ""
"Tax regime of the company before DGI. For the Minimum VAT (Literal E) regime the CFE is built with gross amount indicator (MntBruto) = 3 and billing indicator (IndFact) = 16 on every line, as DGI requires when the CFE is signed with a special CAE.\n"
"IMPORTANT: the option 'Literal E o monotributo' must also be enabled in the Uruware company settings ('Información Extra' section) so Uruware signs the CFE with the special CAE."
msgstr ""

#. module: l10n_uy_edi_special_regime
#. odoo-python
#: code:addons/l10n_uy_edi_special_regime/models/account_move.py:0
msgid ""
"The company %(company)s is registered under a special DGI taxpayer regime "
"(Literal E), so CFE lines can not include VAT taxes with a rate other than "
"0%% (exempt). Please fix the taxes on the invoice lines before sending "
"(%(taxes)s)"
msgstr ""
2 changes: 2 additions & 0 deletions l10n_uy_edi_special_regime/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import res_company
from . import account_move
76 changes: 76 additions & 0 deletions l10n_uy_edi_special_regime/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from odoo import _, models
from odoo.exceptions import UserError


class AccountMove(models.Model):
_inherit = "account.move"

def _l10n_uy_edi_apply_special_regime(self):
"""Whether this CFE must report the special regime gross amount indicator (MntBruto = 3).

Export CFEs keep the standard behavior: they have their own indicators (e.g. IndFact = 10) and are not
part of the special regime treatment defined by DGI for domestic documents."""
self.ensure_one()
return self.company_id._l10n_uy_edi_is_special_regime() and not self._l10n_uy_edi_is_expo_cfe()

def _l10n_uy_edi_cfe_A_iddoc(self):
res = super()._l10n_uy_edi_cfe_A_iddoc()
if self._l10n_uy_edi_apply_special_regime():
# A10: DGI format validation: "Si el valor del CAE Especial es 2, 3 o 4 entonces el
# Ind. Mnt Bruto debe ser 3". Uruware signs with special CAE = 2 when the company is
# flagged as "Literal E o monotributo" on their side.
res["MntBruto"] = 3
return res

def _get_invoice_indicator(self, line, tax_details):
invoice_ind = super()._get_invoice_indicator(line, tax_details)
if invoice_ind in (1, 2, 3, 4) and self._l10n_uy_edi_apply_special_regime():
# B4: per Uruware, under the special regime the indicator 16 (IVA mínimo, Monotributo
# u otros) replaces only the VAT rate indicators (1 exempt, 2 minimum, 3 basic,
# 4 other rate). The conceptual ones are still used: 5 (free delivery), 6/7 (non
# billable, e.g. down payments and discount lines) and 10 (exports)
return 16
return invoice_ind

def _l10n_uy_edi_special_regime_taxed_lines_error(self):
"""Error message when a special regime company bills VAT rates other than 0%, or False.

Every CFE rejected by DGI burns a CAE number, so this is checked as early as possible.

Gated by l10n_uy_edi_is_needed: only electronic sale documents that will actually become
a CFE (vendor bills, non-electronic journals and already sent/accepted CFEs are out —
the latter keeps the re-post inside l10n_uy_edi_action_update_dgi_state working).
Export CFEs are also excluded: they keep the standard behavior and the native
_l10n_uy_edi_check_move already rejects taxed lines on them."""
self.ensure_one()
if not (self.l10n_uy_edi_is_needed and self._l10n_uy_edi_apply_special_regime()):
return False
lines = self.invoice_line_ids.filtered(lambda x: x.display_type not in ("line_section", "line_note"))
if taxed := lines.tax_ids.filtered(lambda x: x.l10n_uy_tax_category == "vat" and x.amount):
return _(
"The company %(company)s is registered under a special DGI taxpayer regime"
" (Literal E), so CFE lines can not include VAT taxes with a rate"
" other than 0%% (exempt). Please fix the taxes on the invoice lines before"
" sending (%(taxes)s)",
company=self.company_id.name,
taxes=", ".join(taxed.mapped("name")),
)
return False

def _post(self, soft=True):
# Block at validation time: do not let the user post a CFE we already know DGI will
# reject. All the offending moves are reported at once so a batch posting can be fixed
# in one pass instead of one error at a time.
errors = []
for move in self:
if error := move._l10n_uy_edi_special_regime_taxed_lines_error():
errors.append("%s:\n%s" % (move.display_name, error))
if errors:
raise UserError("\n\n".join(errors))
return super()._post(soft=soft)

def _l10n_uy_edi_check_move(self):
errors = super()._l10n_uy_edi_check_move()
if error := self._l10n_uy_edi_special_regime_taxed_lines_error():
errors.append(error)
return errors
27 changes: 27 additions & 0 deletions l10n_uy_edi_special_regime/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from odoo import fields, models

# Monotributo / Monotributo MIDES should get the same treatment, but Uruware only confirmed the
# indicators for Literal E so far: add them here once confirmed
L10N_UY_EDI_SPECIAL_REGIMES = ["iva_minimo"]


class ResCompany(models.Model):
_inherit = "res.company"

l10n_uy_edi_taxpayer_regime = fields.Selection(
selection=[
("general", "General Regime"),
("iva_minimo", "Minimum VAT (Literal E)"),
],
string="DGI Taxpayer Regime",
default="general",
help="Tax regime of the company before DGI. For the Minimum VAT (Literal E) regime the CFE is built"
" with gross amount indicator (MntBruto) = 3 and billing indicator (IndFact) = 16 on every line,"
" as DGI requires when the CFE is signed with a special CAE.\n"
"IMPORTANT: the option 'Literal E o monotributo' must also be enabled in the Uruware company settings"
" ('Información Extra' section) so Uruware signs the CFE with the special CAE.",
)

def _l10n_uy_edi_is_special_regime(self):
self.ensure_one()
return self.l10n_uy_edi_taxpayer_regime in L10N_UY_EDI_SPECIAL_REGIMES
1 change: 1 addition & 0 deletions l10n_uy_edi_special_regime/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_special_regime
Loading
Loading