addons-cm/stock_account_avco_negative_fix
2026-08-03 13:11:10 +02:00
..
models add stock_account_avco_negative_fix 2026-08-03 13:11:10 +02:00
tests add stock_account_avco_negative_fix 2026-08-03 13:11:10 +02:00
__init__.py add stock_account_avco_negative_fix 2026-08-03 13:11:10 +02:00
__manifest__.py add stock_account_avco_negative_fix 2026-08-03 13:11:10 +02:00
README.md add stock_account_avco_negative_fix 2026-08-03 13:11:10 +02:00

Stock Account AVCO Negative Fix

Corrige un bug del core de Odoo 16 en la valoración de coste promedio (AVCO) que puede dejar el standard_price de un producto en un valor negativo (o cero) cuando existe stock negativo (quantity_svl < 0) y se recibe una compra que compensa solo parcialmente ese stock.

Descripción del problema

El método product_price_update_before_done() de stock_account/models/stock_move.py calcula el nuevo coste medio ponderado de un producto AVCO con la fórmula:

new_std_price = (amount_unit * qty_svl + price_unit_in * qty) / (qty_svl + qty)

Cuando (qty_svl + qty) es un valor positivo muy pequeño (el stock negativo casi se compensa con la compra recibida, pero no del todo), el numerador puede resultar negativo y el standard_price calculado se vuelve negativo, corrompiendo la valoración de inventario en los recálculos posteriores.

Bug reportado en upstream sin corrección oficial: https://github.com/odoo/odoo/issues/187169

Solución

Este módulo no copia ni sobrescribe el algoritmo del core. En su lugar, hereda stock.move y aplica una estrategia de post-procesado con super() sobre _action_done(), basada en una precondición evaluada antes de que nada más intervenga:

  1. Antes de llamar a super(), para cada movimiento entrante AVCO relevante (move._is_in() y cost_method == 'average'), comprueba si el producto tenía quantity_svl <= 0 antes de este movimiento (la precondición bajo la cual el core puede degenerar a un coste negativo o cero). Si se cumple, captura el price_unit entrante (move._get_price_unit()).
  2. Llama a super()._action_done() para que el core ejecute su lógica completa sin modificaciones (media ponderada AVCO, rama FIFO y el _run_fifo_vacuum() final).
  3. Después de super(), para cada producto marcado como "en riesgo" en el paso 1, reescribe incondicionalmente su standard_price con el price_unit capturado —sin comprobar el valor que haya quedado escrito—, replicando el mismo patrón de escritura del core (disable_auto_svl=True, sudo()) para no generar SVLs de revaluación espurios.

¿Por qué precondición incondicional y no comprobar el signo final? Una primera versión de este fix comprobaba el signo de standard_price después de super(). Se detectó que ese criterio es insuficiente: otros módulos que reaccionan durante _action_done() (por ejemplo, el OCA product_cost_price_avco_sync, que sincroniza el coste AVCO al reaccionar a stock.valuation.layer.write() disparado por _run_fifo_vacuum()) pueden reescribir standard_price a un valor positivo pero distinto del esperado, haciendo que la comprobación <= 0 nunca se cumpla aunque la situación de riesgo sí se haya dado. Al decidir antes de que empiece la cadena y aplicar la corrección de forma incondicional, el fix es robusto frente a cualquier módulo que reescriba standard_price durante el proceso, ahora o en el futuro.

Este enfoque evita duplicar el algoritmo del core (resiliente a cambios de upstream) y es compatible con cualquier otro módulo, presente o futuro, que también herede product_price_update_before_done/_action_done sobre stock.move, ya que la cadena de super() se respeta íntegramente.

Alcance

  • Solo actúa sobre productos con cost_method = 'average'.
  • Productos FIFO o de coste estándar no se ven afectados.
  • No introduce modelos, vistas ni campos nuevos.

Créditos

Autores

  • Criptomart