[IMP] l10n_ar_tax: Hacer cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto.#1316
Conversation
… cuenta el monto mínimo no imponible establecido en el impuesto. Ejemplo cálculo de percepción de venta argentina con impuesto que tiene alícuota 2.5%: a) Si el mínimo no imponible es 350000 y la base es 351000 entonces el monto a percibir es 25 --> (351000-350000)*2.5% b) S.i el mínimo no imponible es 350000 y la base es inferior a dicho monto entonces el monto a percibir es 0 independientemente de la alícuota aplicada. Tarea: 64477
There was a problem hiding this comment.
Pull request overview
Este PR implementa el cálculo correcto de percepciones de venta argentinas considerando el monto mínimo no imponible configurado en el impuesto. La funcionalidad es específicamente requerida para percepciones de la provincia de Santa Fe, donde se debe restar el monto mínimo no imponible de la base antes de aplicar la alícuota del impuesto.
Changes:
- Se modifica la vista del formulario de impuestos para hacer visible el campo
l10n_ar_non_taxable_amounten percepciones de venta y retenciones de compra conamount_typeporcentual - Se implementa el cálculo de percepciones en
_get_tax_detailsque resta el monto mínimo no imponible de la base antes de calcular el impuesto - Se propaga el contexto
l10n_ar_perception_invoicedesdeaccount.movepara activar la lógica especial solo en facturas de venta argentinas
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| l10n_ar_tax/views/account_tax_view.xml | Modifica la visibilidad del campo l10n_ar_non_taxable_amount para que sea visible en percepciones de venta y retenciones de proveedor argentinas con tipo porcentual |
| l10n_ar_tax/models/account_tax.py | Implementa el override de _get_tax_details que calcula la percepción restando el monto mínimo no imponible de la base, ajustando tanto tax_amount como base_amount, y recalculando total_included |
| l10n_ar_tax/models/account_move.py | Override de _prepare_product_base_line_for_taxes_computation para propagar el contexto l10n_ar_perception_invoice en facturas de venta argentinas (out_invoice y out_refund) |
| if base_amount: | ||
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) | ||
| else: | ||
| tax_data["tax_amount"] = 0.0 |
There was a problem hiding this comment.
La lógica de cálculo modifica el tax_amount multiplicándolo por el ratio (net_base / base_amount), lo cual es correcto cuando la base original ya tenía un tax_amount calculado. Sin embargo, esto asume que el tax_amount fue calculado sobre base_amount completo. En el escenario del ejemplo del PR: base_amount=351000, l10n_ar_non_taxable_amount=350000, net_base=1000, si tax_amount inicial era 8775 (351000*2.5%), el resultado será 8775 * (1000/351000) = 25, que es correcto.
Sin embargo, hay una situación edge case: si base_amount es exactamente 0 y l10n_ar_non_taxable_amount también es 0, entonces net_base será 0, y la condición if not net_base: establecerá ambos valores a 0. Pero también existe la rama else (líneas 158-163) que puede ejecutarse cuando net_base es positivo pero base_amount es 0, lo cual es matemáticamente imposible dado que net_base = max(0, base_amount - tax.l10n_ar_non_taxable_amount). Por lo tanto, si base_amount es 0, net_base también será 0 (asumiendo que l10n_ar_non_taxable_amount es positivo o cero).
La verificación en la línea 158 if base_amount: es redundante dentro de la rama else porque si net_base > 0 (condición para entrar al else), entonces necesariamente base_amount > l10n_ar_non_taxable_amount, lo que implica que base_amount > 0.
| if base_amount: | |
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) | |
| else: | |
| tax_data["tax_amount"] = 0.0 | |
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) |
| def _get_tax_details( | ||
| self, | ||
| price_unit, | ||
| quantity, | ||
| precision_rounding=0.01, | ||
| rounding_method="round_per_line", | ||
| product=None, | ||
| product_uom=None, | ||
| special_mode=False, | ||
| manual_tax_amounts=None, | ||
| filter_tax_function=None, | ||
| ): | ||
| """ Hacer cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto.""" | ||
| res = super()._get_tax_details( | ||
| price_unit, | ||
| quantity, | ||
| precision_rounding=precision_rounding, | ||
| rounding_method=rounding_method, | ||
| product=product, | ||
| product_uom=product_uom, | ||
| special_mode=special_mode, | ||
| manual_tax_amounts=manual_tax_amounts, | ||
| filter_tax_function=filter_tax_function, | ||
| ) | ||
| if not self._context.get("l10n_ar_perception_invoice"): | ||
| return res | ||
|
|
||
| if not res.get("taxes_data"): | ||
| return res | ||
|
|
||
| updated = False | ||
| for tax_data in res["taxes_data"]: | ||
| tax = tax_data["tax"] | ||
| if tax.country_code == "AR" and tax.type_tax_use == "sale" and tax.l10n_ar_non_taxable_amount: | ||
| base_amount = tax_data["base_amount"] | ||
| net_base = max(0, base_amount - tax.l10n_ar_non_taxable_amount) | ||
| if not net_base: | ||
| if tax_data["tax_amount"] or tax_data["base_amount"]: | ||
| tax_data["tax_amount"] = 0.0 | ||
| tax_data["base_amount"] = 0.0 | ||
| updated = True | ||
| else: | ||
| if base_amount: | ||
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) | ||
| else: | ||
| tax_data["tax_amount"] = 0.0 | ||
| tax_data["base_amount"] = net_base | ||
| updated = True | ||
|
|
||
| if updated: | ||
| res["total_included"] = res["total_excluded"] + sum( | ||
| tax_line["tax_amount"] for tax_line in res["taxes_data"] | ||
| ) | ||
|
|
||
| return res |
There was a problem hiding this comment.
Esta funcionalidad nueva de cálculo de percepciones con monto mínimo no imponible carece de cobertura de tests. El módulo ya tiene tests para percepciones en tests/test_payment_withholding_validation.py, por lo que sería conveniente agregar tests que verifiquen:
- Caso a) Base mayor que mínimo no imponible: verificar que solo se aplica percepción sobre el excedente
- Caso b) Base menor o igual que mínimo no imponible: verificar que la percepción es 0
- Caso con múltiples impuestos: algunos con mínimo no imponible y otros sin él
- Caso con
out_refund: verificar que el contexto también se propaga correctamente
| return recs | ||
|
|
||
| def _prepare_product_base_line_for_taxes_computation(self, product_line): | ||
| """ Si es factura de venta argentina mandamos l10n_ar_perception_invoice por contexto. Dicho contexto se usa en l10n_ar_tax/models/account_tax.py por el método _get_tax_details para el cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto. """ |
There was a problem hiding this comment.
La línea del docstring es extremadamente larga (más de 200 caracteres). Sería más legible dividirla en múltiples líneas siguiendo las convenciones de PEP 257 para docstrings de una línea o convertirla en un docstring multilínea.
| """ Si es factura de venta argentina mandamos l10n_ar_perception_invoice por contexto. Dicho contexto se usa en l10n_ar_tax/models/account_tax.py por el método _get_tax_details para el cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto. """ | |
| """Si es factura de venta argentina mandamos l10n_ar_perception_invoice por contexto. | |
| Dicho contexto se usa en l10n_ar_tax/models/account_tax.py por el método | |
| _get_tax_details para el cálculo de percepciones de venta, teniendo en | |
| cuenta el monto mínimo no imponible establecido en el impuesto. | |
| """ |
| if base_amount: | ||
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) | ||
| else: | ||
| tax_data["tax_amount"] = 0.0 | ||
| tax_data["base_amount"] = net_base | ||
| updated = True |
There was a problem hiding this comment.
La variable updated se establece en True siempre que net_base > 0 (línea 163), incluso cuando los valores de tax_amount y base_amount podrían no haber cambiado realmente. Esto causa que total_included se recalcule innecesariamente en casos donde ningún impuesto tenía l10n_ar_non_taxable_amount configurado, o cuando los valores ya eran los correctos.
Una optimización sería verificar si realmente hubo un cambio antes de establecer updated = True, por ejemplo:
- En las líneas 152-156: la condición ya verifica si hay valores que cambiar
- En las líneas 157-163: solo establecer
updated = Truesitax_data["base_amount"] != net_baseo si el tax_amount calculado es diferente del original
Esto es más relevante cuando hay múltiples líneas de impuestos y solo algunas tienen el monto mínimo no imponible configurado.
| if base_amount: | |
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) | |
| else: | |
| tax_data["tax_amount"] = 0.0 | |
| tax_data["base_amount"] = net_base | |
| updated = True | |
| original_tax_amount = tax_data["tax_amount"] | |
| original_base_amount = tax_data["base_amount"] | |
| if base_amount: | |
| new_tax_amount = original_tax_amount * (net_base / base_amount) | |
| else: | |
| new_tax_amount = 0.0 | |
| new_base_amount = net_base | |
| if ( | |
| original_tax_amount != new_tax_amount | |
| or original_base_amount != new_base_amount | |
| ): | |
| tax_data["tax_amount"] = new_tax_amount | |
| tax_data["base_amount"] = new_base_amount | |
| updated = True |

Ejemplo cálculo de percepción de venta argentina con impuesto que tiene alícuota 2.5%:
a) Si el mínimo no imponible es 350000 y la base es 351000 entonces el monto a percibir es 25 --> (351000-350000)*2.5%
b) S.i el mínimo no imponible es 350000 y la base es inferior a dicho monto entonces el monto a percibir es 0 independientemente de la alícuota aplicada.
Tarea: 64477