Skip to content

[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

Open
pablohmontenegro wants to merge 1 commit intoingadhoc:19.0from
adhoc-dev:19.0-t-64477-pam
Open

[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
pablohmontenegro wants to merge 1 commit intoingadhoc:19.0from
adhoc-dev:19.0-t-64477-pam

Conversation

@pablohmontenegro
Copy link
Copy Markdown
Contributor

@pablohmontenegro pablohmontenegro commented Feb 26, 2026

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

… 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
Copilot AI review requested due to automatic review settings February 26, 2026 19:37
@roboadhoc
Copy link
Copy Markdown
Contributor

Pull request status dashboard

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_amount en percepciones de venta y retenciones de compra con amount_type porcentual
  • Se implementa el cálculo de percepciones en _get_tax_details que resta el monto mínimo no imponible de la base antes de calcular el impuesto
  • Se propaga el contexto l10n_ar_perception_invoice desde account.move para 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)

Comment on lines +158 to +161
if base_amount:
tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount)
else:
tax_data["tax_amount"] = 0.0
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +170
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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Caso a) Base mayor que mínimo no imponible: verificar que solo se aplica percepción sobre el excedente
  2. Caso b) Base menor o igual que mínimo no imponible: verificar que la percepción es 0
  3. Caso con múltiples impuestos: algunos con mínimo no imponible y otros sin él
  4. Caso con out_refund: verificar que el contexto también se propaga correctamente

Copilot uses AI. Check for mistakes.
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. """
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
""" 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.
"""

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +163
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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = True si tax_data["base_amount"] != net_base o 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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants