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
7 changes: 5 additions & 2 deletions l10n_uy_edi_special_regime/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ 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.
El indicador ``MntBruto = 3`` aplica a todos los tipos de CFE que emite la compañía —
e-Ticket (101), e-Factura (111), sus notas de crédito y débito (102, 103, 112, 113) y también
los CFE de exportación (121, 122, 123): el CAE especial es una propiedad del contribuyente, por
lo que Uruware firma todos los comprobantes con él y DGI exige el indicador en todos. Las líneas
de exportación conservan su indicador de facturación propio (``IndFact = 10``).

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
Expand Down
29 changes: 21 additions & 8 deletions l10n_uy_edi_special_regime/models/account_move.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from odoo import _, models
from odoo import _, fields, models
from odoo.exceptions import UserError


Expand All @@ -8,10 +8,14 @@ class AccountMove(models.Model):
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."""
It applies to every CFE type the company issues, export ones included: the special CAE is
a property of the taxpayer (Uruware signs all the company CFEs with it), and DGI requires
MntBruto = 3 whenever a special CAE is used (Formato CFE, zone A field 10 conditional on
e-Fac Exp too; zone G field 5: "Si G-C5=2, 3 o 4, A-C10 debe ser igual a 3"). Export
lines keep their own billing indicator (IndFact = 10): the 16 mapping only replaces the
VAT rate indicators."""
self.ensure_one()
return self.company_id._l10n_uy_edi_is_special_regime() and not self._l10n_uy_edi_is_expo_cfe()
return self.company_id._l10n_uy_edi_is_special_regime()

def _l10n_uy_edi_cfe_A_iddoc(self):
res = super()._l10n_uy_edi_cfe_A_iddoc()
Expand Down Expand Up @@ -40,10 +44,14 @@ def _l10n_uy_edi_special_regime_taxed_lines_error(self):
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."""
Export CFEs are also excluded from THIS check only: the native _l10n_uy_edi_check_move
already rejects taxed lines on any export CFE, whatever the company regime."""
self.ensure_one()
if not (self.l10n_uy_edi_is_needed and self._l10n_uy_edi_apply_special_regime()):
if not (
self.l10n_uy_edi_is_needed
and self.company_id._l10n_uy_edi_is_special_regime()
and not self._l10n_uy_edi_is_expo_cfe()
):
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):
Expand All @@ -61,8 +69,13 @@ 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.
to_check = self
if soft:
# Mirror the soft filter of super(): future-dated moves are not posted in this pass
# (they are scheduled for auto-post), so they must not abort the rest of the batch
to_check = self.filtered(lambda move: move.date <= fields.Date.context_today(self))
errors = []
for move in self:
for move in to_check:
if error := move._l10n_uy_edi_special_regime_taxed_lines_error():
errors.append("%s:\n%s" % (move.display_name, error))
if errors:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<CFE xmlns="http://cfe.dgi.gub.uy" version="1.0">
<eFact_Exp>
<Encabezado>
<IdDoc>
<TipoCFE>121</TipoCFE>
<FchEmis>2024-06-15</FchEmis>
<MntBruto>3</MntBruto>
<FmaPago>1</FmaPago>
<FchVenc>2024-06-15</FchVenc>
<ClauVenta>FOB</ClauVenta>
<ModVenta>1</ModVenta>
<ViaTransp>1</ViaTransp>
</IdDoc>
<Emisor>
<RUCEmisor>215521750017</RUCEmisor>
<RznSoc>(UY) Uruguay Company (Unit Tests)</RznSoc>
<CdgDGISucur>1</CdgDGISucur>
<DomFiscal>Calle Falsa 254</DomFiscal>
<Ciudad>Montevideo</Ciudad>
<Departamento>Montevideo</Departamento>
</Emisor>
<Receptor>
<TipoDocRecep>7</TipoDocRecep>
<CodPaisRecep>US</CodPaisRecep>
<DocRecepExt>17-2038053</DocRecepExt>
<RznSocRecep>Foreign Inc</RznSocRecep>
<DirRecep>7841 Red Road</DirRecep>
<CiudadRecep>San Francisco</CiudadRecep>
<DeptoRecep>California</DeptoRecep>
<PaisRecep>United States</PaisRecep>
<CompraID>*DEMO</CompraID>
</Receptor>
<Totales>
<TpoMoneda>UYU</TpoMoneda>
<MntExpoyAsim>100.00</MntExpoyAsim>
<MntTotal>100.00</MntTotal>
<CantLinDet>1</CantLinDet>
<MntPagar>100.00</MntPagar>
</Totales>
</Encabezado>
<Detalle>
<Item>
<NroLinDet>1</NroLinDet>
<CodItem>
<TpoCod>INT1</TpoCod>
<Cod>product UY</Cod>
</CodItem>
<IndFact>10</IndFact>
<NomItem>Customizable Desk (VAT 10)</NomItem>
<Cantidad>1.000</Cantidad>
<UniMed>Unit</UniMed>
<PrecioUnitario>100.000000</PrecioUnitario>
<MontoItem>100.00</MontoItem>
</Item>
</Detalle>
</eFact_Exp>
</CFE>
8 changes: 4 additions & 4 deletions l10n_uy_edi_special_regime/tests/test_special_regime.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ def test_100_literal_e_global_discount(self):
self._check_cfe(invoice, "e-FC", "100_literal_e_global_discount")

def test_110_literal_e_expo_invoice(self):
"""Export CFEs keep the standard behavior under the special regime (IndFact = 10, no
MntBruto = 3): the generated XML must be identical to the l10n_uy_edi standard one."""
self.mocked_cfes_path = "l10n_uy_edi/tests/expected_cfes/"
"""Export CFEs under the special regime report MntBruto = 3 like every other CFE of the
company (Uruware signs them with the special CAE too), but keep their own billing
indicator (IndFact = 10): the XML must be the l10n_uy_edi standard one plus MntBruto."""
invoice = self._create_move(
l10n_latam_document_type_id=self.env.ref("l10n_uy.dc_e_inv_exp").id,
partner_id=self.foreign_partner.id,
Expand All @@ -163,7 +163,7 @@ def test_110_literal_e_expo_invoice(self):
self.assertEqual(invoice.l10n_latam_document_type_id.code, "121", "Not Expo e-invoice")
invoice.action_post()
self._send_and_print(invoice)
self._check_cfe(invoice, "e-FCE", "40_e_expo_invoice")
self._check_cfe(invoice, "e-FCE", "110_literal_e_expo_invoice")

def test_80_check_move_blocks_taxed_lines(self):
"""A special regime company must not be able to send a CFE with 10% / 22% VAT lines:
Expand Down
Loading