Repository navigation
Rendimiento: agrupar el cálculo de movimientos de stock en lotes - #50
Open
daniel89fg wants to merge 2 commits into
Open
daniel89fg wants to merge 2 commits into
daniel89fg wants to merge 2 commits into
Conversation
Aprobar un albarán, facturar o agrupar documentos podía superar el max_execution_time cuando el documento repetía la misma referencia en muchas líneas o los productos tenían un histórico largo de movimientos. Cada llamada a addLineBusinessDocument() recalculaba los saldos de toda la referencia, y se invocaba 4-5 veces por línea. Además el cálculo de la cantidad recorría las líneas del documento recargándolo de la base de datos en cada una, con un coste cuadrático. Cambios: - StockMovementManager: lotes reentrantes (beginBatch/flushBatch). Dentro de un lote sólo se anota el par (documento, referencia) afectado y al cerrarlo se calcula una sola vez cada movimiento y cada saldo. - StockMovementManager: caché por documento de líneas y transformaciones, que elimina el coste cuadrático y las recargas repetidas. - StockMovementManager: los saldos se recalculan sólo desde la fecha afectada, y no se recalculan si el movimiento no ha cambiado. - cleanResidualMovements(): las líneas se agrupan por referencia fuera del bucle y, dentro de un lote, delega la decisión en el cierre del lote. - Extensiones de BusinessDocument, BusinessDocumentLine, del generador de documentos y del controlador DocumentStitcher para abrir y cerrar el lote en cada flujo. - Worker UpdateStockMovements: recorre sólo los pares (almacén, referencia) con movimientos, en lugar de todas las variantes por todos los almacenes, y se envía un único evento por producto. - Índices en stocks_movimientos por (codalmacen, referencia), (docid, docmodel) e (idproducto, codalmacen, referencia). - Tests de rendimiento en DeliveryNoteTest: albarán de 200 líneas de la misma referencia, 50 productos con histórico y agrupación de 120 albaranes en una sola factura. La extensión del controlador necesita los pipes añadidos en el core. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problema
Aprobar un albarán, facturar o agrupar documentos podía superar el
max_execution_time(60 s) y cortar el proceso, en dos situaciones:Un cliente lo reprodujo agrupando 18 albaranes de proveedor (45 líneas, referencias repetidas) en una factura desde
DocumentStitcher: timeout de 60 s. Sin el plugin activo, la misma operación era instantánea.Causas encontradas en
Lib/StockMovementManager.php:addLineBusinessDocument()llamaba aupdateReferenceSaldos()en cada invocación, y ese método releía, reordenaba en PHP y regrababa todo el histórico de(almacén, referencia).cloneLine().getBusinessDocumentMovementQuantityForReference()recorría$doc->getLines()y por cada línea con la misma referencia recargaba el documento entero y consultabadoc_transformations: coste cuadrático.cleanResidualMovements()consultaba las líneas dentro del bucle de movimientos.stocks_movimientosno tenía índices compuestos, y el worker recalculaba todas las variantes por todos los almacenes, con un evento por línea guardada.Solución
StockMovementManager(beginBatch()/flushBatch()/inBatch()): dentro de un lote sólo se anota el par (documento, referencia) afectado y, al cerrarlo, se calcula una sola vez cada movimiento y una sola vez el saldo de cada (almacén, referencia). 200 líneas de la misma referencia colapsan en una entrada.cleanResidualMovements(): agrupa las líneas por referencia fuera del bucle y, dentro de un lote, delega la decisión en el cierre del lote en vez de borrar y recrear.saveBefore/saveUpdate/saveInsert/save), generador de documentos (generateBefore/generateTrue/generateFalse) y controladorDocumentStitcher, para que los cambios de estado de todos los documentos y la generación del nuevo compartan un único lote.UpdateStockMovements: recorre sólo los pares (almacén, referencia) que existen y se envía un único evento por producto en lugar de uno por línea.stocks_movimientos:(codalmacen, referencia),(docid, docmodel)e(idproducto, codalmacen, referencia).El cálculo de la cantidad de cada movimiento no cambia de fórmula: sólo cambia cuándo y cuántas veces se ejecuta, y cada cierre de lote recalcula a partir del estado ya persistido.
Tiempos
Medidos con los tests nuevos, sobre MySQL y PostgreSQL:
En el caso de la agrupación, el tiempo restante lo consume casi por completo el core al clonar las 2.971 líneas: el coste del propio plugin pasó a ser prácticamente cero (de 107 s recalculando saldos a un recálculo único por referencia al final).
Tests
Tres casos nuevos en
Test/main/DeliveryNoteTest.php, con umbral de tiempo configurable medianteSTOCKAVANZADO_PERF_BUDGETy aserciones de corrección (número exacto de movimientos, cantidades, saldos acumulados y stock final):DocumentStitcher.Suite completa en verde: 86 tests en PHP 8.1, 8.2, 8.3 y 8.4, con MySQL y PostgreSQL.
🤖 Generated with Claude Code