diff --git a/l10n_uy_edi_special_regime/README.rst b/l10n_uy_edi_special_regime/README.rst index f6dd764d..e7045a17 100644 --- a/l10n_uy_edi_special_regime/README.rst +++ b/l10n_uy_edi_special_regime/README.rst @@ -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 diff --git a/l10n_uy_edi_special_regime/models/account_move.py b/l10n_uy_edi_special_regime/models/account_move.py index 9c1fc464..f91aa142 100644 --- a/l10n_uy_edi_special_regime/models/account_move.py +++ b/l10n_uy_edi_special_regime/models/account_move.py @@ -1,4 +1,4 @@ -from odoo import _, models +from odoo import _, fields, models from odoo.exceptions import UserError @@ -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() @@ -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): @@ -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: diff --git a/l10n_uy_edi_special_regime/tests/expected_cfes/110_literal_e_expo_invoice.xml b/l10n_uy_edi_special_regime/tests/expected_cfes/110_literal_e_expo_invoice.xml new file mode 100644 index 00000000..884ba11d --- /dev/null +++ b/l10n_uy_edi_special_regime/tests/expected_cfes/110_literal_e_expo_invoice.xml @@ -0,0 +1,57 @@ + + + + + 121 + 2024-06-15 + 3 + 1 + 2024-06-15 + FOB + 1 + 1 + + + 215521750017 + (UY) Uruguay Company (Unit Tests) + 1 + Calle Falsa 254 + Montevideo + Montevideo + + + 7 + US + 17-2038053 + Foreign Inc + 7841 Red Road + San Francisco + California + United States + *DEMO + + + UYU + 100.00 + 100.00 + 1 + 100.00 + + + + + 1 + + INT1 + product UY + + 10 + Customizable Desk (VAT 10) + 1.000 + Unit + 100.000000 + 100.00 + + + + diff --git a/l10n_uy_edi_special_regime/tests/test_special_regime.py b/l10n_uy_edi_special_regime/tests/test_special_regime.py index edc867c3..79bf0e20 100644 --- a/l10n_uy_edi_special_regime/tests/test_special_regime.py +++ b/l10n_uy_edi_special_regime/tests/test_special_regime.py @@ -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, @@ -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: