From 608fd0055e26411baf3b5395fc73b3463ae8335a Mon Sep 17 00:00:00 2001
From: agb80 <atin81@gmail.com>
Date: Thu, 11 Jan 2024 11:51:54 -0600
Subject: [PATCH] fix(account.invoice): corrige la diferencia en montos cuando
 el cliente no es sujeto de IEPS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Por la forma en la que se estaban procesando los impuestos cuando el cliente no era sujeto de IEPS
se forzaba realizar nuevamente el cálculo y esto ocasionaba que el precio unitario de las líneas de
factura acumulara una pequeña diferencia en decimales. El problema era que al acumular esa
diferencia en grandes cantidades acababa surgiendo una diferencia significativa en los montos de las
líneas y en el monto final de la factura. Para evitar estas diferencias se cambió la forma en como
se procesan los impuestos para evitar esas diferencias.
---
 l10n_mx_facturae/models/account_invoice.py    | 64 ++++++++-----------
 .../tests/features/account_invoice.feature    | 12 ++++
 2 files changed, 39 insertions(+), 37 deletions(-)

diff --git a/l10n_mx_facturae/models/account_invoice.py b/l10n_mx_facturae/models/account_invoice.py
index 5c1116c5f8..17e4cabd58 100644
--- a/l10n_mx_facturae/models/account_invoice.py
+++ b/l10n_mx_facturae/models/account_invoice.py
@@ -579,9 +579,6 @@ class AccountInvoiceLine(models.Model):
             """
             tax_record = self.env["account.tax"].browse(tax["id"])
             tax_group = tax_record.tax_category_id
-            # IEPS tax only must be included when partner is IEPS subjected
-            if tax_group.name == "IEPS" and not partner.ieps_subjected:
-                return
             # TODO: Delete on version 3.0.0
             if "base" not in tax:
                 tax["base"] = currency.cfdi_round(tax["price_unit"] * self.quantity)
@@ -597,16 +594,7 @@ class AccountInvoiceLine(models.Model):
         total_discount *= 1 - self.invoice_id.global_discount / 100
         price = float_round(self.price_unit * total_discount, precision)
         partner = self.invoice_id.partner_id
-        # Check if IEPS is on taxes, this will be used later to know if need price
-        # to be recalculated because IEPS must be price included as partner is not
-        # IEPS subjected and product include IEPS taxes
-        ieps_group = self.env.ref("l10n_mx.tax_category_ieps")
-        is_ieps_tax_subjected = any(
-            tax.tax_category_id == ieps_group for tax in self.invoice_line_tax_id
-        )
-        is_price_included = any(
-            tax.price_include for tax in self.invoice_line_tax_id
-        )
+
         # Compute taxes using original compute_all function from
         # account.invoice.tax to get same result for CFDI display
         res = self.invoice_line_tax_id.compute_all(
@@ -616,36 +604,19 @@ class AccountInvoiceLine(models.Model):
             partner=partner,
             currency=self.invoice_id.currency_id,
         )
-        # pylint: disable=C1801
-        if len(res["taxes"]) == 0:
+        if not len(res["taxes"]):
             raise ValidationError(
                 _("Product {p} must have at least one tax selected.").format(
                     p=self.product_id.name
                 )
             )
-        taxes = []
-        taxes_list = iter(res["taxes"])
-        tax = next(taxes_list)
-        # Iterate taxes and append to the new tax list as needed
-        while True:
-            tax = process_tax(tax)
-            if tax:
-                taxes.append(tax)
-            try:
-                tax = next(taxes_list)
-            except StopIteration:
-                if tax is None:
-                    raise ValidationError(
-                        _(
-                            "Incorrect tax sequence configuration, check "
-                            "this data in Account >> Tax >> Sequence"
-                        )
-                    )
-                break
+
+        is_price_included = any(
+            tax.price_include for tax in self.invoice_line_tax_id
+        )
         res["price_unit"] = self.price_unit
-        # Recompute price_unit is needed when any tax is setup to price included or
-        # when product is IEPS subjected but not partner
-        if is_price_included or (not partner.ieps_subjected and is_ieps_tax_subjected):
+        # Recompute price_unit is needed when any tax is setup to price included
+        if is_price_included:
             # Send round=False in context to avoid rounding to wrong value when working
             # with high Product Price precision (6 digits)
             res["price_unit"] = self.invoice_line_tax_id.with_context(
@@ -661,6 +632,25 @@ class AccountInvoiceLine(models.Model):
                 self.env["decimal.precision"].precision_get("Product Price"),
             )
 
+        taxes = []
+        taxes_list = iter(res["taxes"])
+        tax = next(taxes_list)
+        # Iterate taxes and append to the new tax list as needed
+        while True:
+            tax = process_tax(tax)
+            # IEPS tax only must be included when partner is IEPS subjected
+            if tax["group"] == "IEPS" and not partner.ieps_subjected:
+                res["price_unit"] = float_round(
+                    res["price_unit"]  + (tax["amount"] / self.quantity),
+                    self.env["decimal.precision"].precision_get("Product Price"),
+                )
+            else:
+                taxes.append(tax)
+            try:
+                tax = next(taxes_list)
+            except StopIteration:
+                break
+
         res["importe"] = currency.round(res["price_unit"] * self.quantity)
         res["descuento"] = currency.round(res["importe"] * (1 - total_discount))
         # Overrides original taxes with the list computed by us
diff --git a/l10n_mx_facturae/tests/features/account_invoice.feature b/l10n_mx_facturae/tests/features/account_invoice.feature
index 0f25342473..7dea9a1417 100644
--- a/l10n_mx_facturae/tests/features/account_invoice.feature
+++ b/l10n_mx_facturae/tests/features/account_invoice.feature
@@ -31,3 +31,15 @@ Característica: Cancelar facturas con CFDIS no timbrados.
 	  Cuando se cancele la Factura
 	  Entonces la factura cambia a Cancelada
 	  Y el CFDI cambia a Cancelado
+
+
+Característica: Validación de XML y PDF en el total de factura, importe y precio unitario del producto
+    Esquema del escenario:
+      Dado una factura con la siguiente información
+        El cliente no está sujeto a IEPS
+        | Concepto  | Precio Unitario | Cantidad | Impuesto |
+        | ProductoA |         308.3554|      5.0 | IEPS 30% |
+        |           |                 |          | IVA 16%  |
+      Cuando: Valido la factura.
+      Entonces: la factura cambia a Abierta
+      Y el total de la factura, el importe y precio unitario del producto es correcto en el XML
-- 
GitLab