[IMP] l10n_ar_tax: Hacer cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto.#1317
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 de percepciones de venta argentinas considerando el monto mínimo no imponible configurado en el impuesto. Específicamente, cuando una percepción tiene un monto mínimo no imponible, el sistema ahora calcula el impuesto sobre la base menos ese mínimo (si la base supera el mínimo), o establece el impuesto en 0 si la base está por debajo del mínimo.
Changes:
- Se modifica la vista XML para hacer visible el campo
l10n_ar_non_taxable_amounten percepciones de venta (necesario para Santa Fe) - Se implementa la lógica de cálculo en
account.tax._get_tax_details()que ajusta el monto del impuesto según el mínimo no imponible - Se añade contexto
l10n_ar_perception_invoiceenaccount.movepara activar la lógica solo en facturas de venta argentinas
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| l10n_ar_tax/views/account_tax_view.xml | Hace visible el campo l10n_ar_non_taxable_amount para percepciones de venta argentinas con tipo porcentaje |
| l10n_ar_tax/models/account_tax.py | Implementa el método _get_tax_details() que calcula percepciones restando el monto mínimo no imponible de la base y recalculando el impuesto proporcionalmente |
| l10n_ar_tax/models/account_move.py | Añade el contexto l10n_ar_perception_invoice en facturas y notas de crédito de venta argentinas para activar el cálculo especial |
| updated = True | ||
| else: | ||
| if base_amount: | ||
| tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount) |
There was a problem hiding this comment.
El cálculo proporcional del tax_amount en la línea 148 podría generar problemas de precisión o redondeo. El método recibe un parámetro precision_rounding=0.01 que no se está utilizando al recalcular el monto del impuesto.
Cuando se calcula tax_data["tax_amount"] = tax_data["tax_amount"] * (net_base / base_amount), el resultado de la multiplicación podría tener más decimales de los esperados para una moneda. Esto podría causar diferencias de centavos en los totales.
Recomendación: Aplicar redondeo al resultado usando precision_rounding o la moneda correspondiente, por ejemplo usando float_round de odoo.tools o el método de redondeo de la moneda si está disponible en el contexto.
| 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.
El comentario del docstring es excesivamente largo para una sola línea (más de 200 caracteres). Esto dificulta la legibilidad del código.
Recomendación: Reformatear el docstring para usar múltiples líneas, por ejemplo:
\"\"\"
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. """ | |
| """ | |
| 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 tax_data["tax_amount"] or tax_data["base_amount"]: | ||
| tax_data["tax_amount"] = 0.0 | ||
| tax_data["base_amount"] = 0.0 | ||
| updated = True |
There was a problem hiding this comment.
La condición if not net_base y el bloque anidado if tax_data["tax_amount"] or tax_data["base_amount"] son redundantes. Cuando net_base es 0, siempre se debería actualizar tax_amount y base_amount a 0.0. La condición interna no agrega valor lógico porque:
- Si ambos valores ya son 0.0, asignarlos nuevamente a 0.0 es una operación sin efectos secundarios
- Si alguno no es 0.0, necesariamente deben ponerse en 0.0
Recomendación: Simplificar eliminando la condición interna y siempre ejecutar la actualización cuando net_base sea 0.
| if tax_data["tax_amount"] or tax_data["base_amount"]: | |
| tax_data["tax_amount"] = 0.0 | |
| tax_data["base_amount"] = 0.0 | |
| updated = True | |
| tax_data["tax_amount"] = 0.0 | |
| tax_data["base_amount"] = 0.0 | |
| updated = True |
| 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.""" |
There was a problem hiding this comment.
El comentario del docstring solo menciona "percepciones de venta" pero el método también se aplica a notas de crédito (out_refund) según se puede ver en account_move.py línea 72. Esto podría generar confusión sobre cuándo se aplica la lógica del monto mínimo no imponible.
Recomendación: Actualizar el docstring para mencionar explícitamente que aplica tanto a facturas de venta como notas de crédito de venta argentinas, por ejemplo: "Hacer cálculo de percepciones de venta (facturas y notas de crédito) teniendo en cuenta el monto mínimo no imponible establecido en el impuesto."
| """ Hacer cálculo de percepciones de venta teniendo en cuenta el monto mínimo no imponible establecido en el impuesto.""" | |
| """ Hacer cálculo de percepciones de venta (facturas y notas de crédito) teniendo en cuenta el monto mínimo no imponible establecido en el impuesto.""" |
| 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.
La nueva funcionalidad de cálculo de percepciones con monto mínimo no imponible carece de tests automatizados. Esto es importante porque:
- El módulo ya tiene tests en la carpeta
tests/ - La lógica introduce cálculos matemáticos no triviales (resta de monto no imponible, recálculo proporcional del tax_amount)
- Hay múltiples ramas condicionales que deberían validarse
Recomendación: Agregar tests que verifiquen:
- Caso a) Base > mínimo no imponible: verificar que el monto a percibir se calcula sobre (base - mínimo_no_imponible)
- Caso b) Base < mínimo no imponible: verificar que el monto a percibir es 0
- Caso c) Validar que funciona tanto para facturas como notas de crédito de venta
| 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.
El recálculo del tax_amount en la línea 148 usa división pero no verifica si base_amount podría ser exactamente 0. Aunque hay una condición if base_amount que previene la división por cero, la estructura del código podría ser más clara.
Sin embargo, la lógica tiene una inconsistencia: cuando base_amount es 0 pero net_base no es 0 (lo cual es matemáticamente imposible dado que net_base = max(0, base_amount - l10n_ar_non_taxable_amount)), se establece tax_amount = 0.0 en línea 150. Esta rama del else nunca debería ejecutarse porque si base_amount = 0, entonces net_base también será 0 (ya procesado en el bloque if not net_base).
Recomendación: Simplificar la lógica eliminando la verificación redundante en línea 147, ya que si llegamos al bloque else (línea 146), sabemos que net_base > 0, lo que implica que base_amount > l10n_ar_non_taxable_amount, por lo tanto base_amount > 0 y no hay riesgo de división por cero.
| 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) |

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