A cena é sempre a mesma: o lojista abre o grid de produtos, vê Quantity 55 e do lado Salable Quantity 0. Ninguém vendeu 55 unidades. Aí alguém entra no banco e dá um UPDATE no cataloginventory_stock_item, o número volta por dez minutos e depois cai de novo.
Não adianta mexer no estoque, porque o problema não está nele. Está na tabela de reservas do MSI, e o Magento tem dois comandos feitos exatamente para isso.
De onde sai esse número
A documentação da Adobe é direta na conta:
Salable Quantity = StockItem Quantity + Outstanding Reservations
Traduzindo: a quantidade física somada de todas as fontes atribuídas ao estoque, mais as reservas em aberto — e reserva é sempre valor negativo. Com 55 no físico e -15 reservados, o cliente consegue comprar até 40.
Cada linha de reserva vive na tabela inventory_reservation, que tem só cinco colunas: reservation_id, stock_id, sku, quantity e metadata. O ciclo normal é esse:
- pedido entra: grava uma linha negativa (
-2, por exemplo); - pedido é faturado e enviado: grava a linha positiva de compensação (
+2) e o estoque físico da fonte cai; - pedido é cancelado ou vira credit memo: mesma coisa, compensa com o positivo.
A regra que a Adobe repete: pedido em estado final (Complete, Canceled, Closed) tem que somar zero de reserva. Se não soma, sobrou lixo — e é esse lixo que come a sua Salable Quantity.
Ache as reservas órfãs
Primeiro comando, e ele só lê, não escreve nada:
php bin/magento inventory:reservation:list-inconsistenciesA saída vem legível, pedido por pedido:
Order 172:
- Product bike-123 should be compensated by +2.000000 for stock 1As opções que valem saber:
| Opção | O que faz |
|---|---|
-c, --complete-orders | só pedidos em estado final |
-i, --incomplete-orders | só pedidos ainda abertos |
-b, --bunch-size | quantos pedidos carrega por vez |
-r, --raw | saída crua, uma linha por SKU |
O -r devolve no formato <ORDER_ID>:<SKU>:<QUANTITY>:<STOCK-ID>, tipo 172:bike-123:+2.000000:1. Guarde essa saída num arquivo antes de qualquer coisa: é o seu registro do que estava errado.
php bin/magento inventory:reservation:list-inconsistencies -r > /tmp/reservas-quebradas.txt
wc -l /tmp/reservas-quebradas.txtEm loja grande esse comando demora, porque ele varre pedido por pedido. Rode fora do horário de pico e aumente o -b se a máquina aguentar.
Conserte com create-compensations
O segundo comando lê aquele formato cru e grava a reserva contrária, zerando o saldo:
php bin/magento inventory:reservation:list-inconsistencies -r | php bin/magento inventory:reservation:create-compensationsDá para passar uma linha só, se você quiser tratar um pedido de cada vez:
php bin/magento inventory:reservation:create-compensations 172:bike-123:+2.000000:1Repare no que ele não faz: não altera quantity de fonte nenhuma, não mexe no pedido, não reindexa por conta própria. Ele só insere linhas novas em inventory_reservation. Por isso é seguro rodar em produção — e por isso também não conserta estoque físico errado, que é outro problema.
Depois, rode o de listar de novo. Tem que voltar vazio. Se ainda sobrar coisa, é sinal de que algo continua criando reserva torta enquanto você conserta.
Backup do banco antes, sempre. Não porque o comando seja perigoso, mas porque você vai querer comparar depois.
Por que quebrou (e como não repetir)
Na minha experiência, quase toda reserva órfã vem de integração. Os suspeitos, em ordem de frequência:
- ERP mudando status de pedido por SQL. Um
UPDATE sales_order SET status='complete'pula todo o fluxo do Magento, e a compensação nunca acontece. - Módulo de marketplace cancelando pedido no braço, sem usar o serviço de cancelamento do core.
- Importador de estoque que grava direto em
cataloginventory_stock_itemem vez de usar a API desource-items. - Migração de 2.1/2.2 para 2.3 com pedidos em aberto. Esse é o caso que a própria Adobe manda tratar com esses dois comandos.
Para não repetir: qualquer coisa que mude estoque ou estado de pedido tem que passar pela API oficial. É o mesmo cuidado que eu descrevo no guia de integração com ERP e NF-e — o middleware pode ser burro, mas tem que ser educado com o Magento.
E confira se o cron inventory_cleanup_reservations está rodando. Ele limpa reserva já compensada de pedido finalizado; sem ele, a tabela cresce sem parar e a consulta de estoque vai ficando lenta.
Perguntas rápidas
Posso rodar esses comandos com a loja no ar?
Pode. O de listar só faz leitura e o de compensar apenas insere linhas novas na tabela de reservas. O cuidado é com o tempo: em catálogo grande a listagem varre todos os pedidos e pesa no banco, então prefira horário de pouco movimento.
Rodei os dois e a Salable Quantity continua errada. E agora?
Se a listagem voltou vazia e o número segue estranho, o problema é o estoque físico da fonte, não a reserva. Confira a quantidade por source no produto e o Out-of-Stock Threshold da configuração de inventário, que também entra na conta.
Dá para apagar a tabela inventory_reservation e começar do zero?
Não faça isso. As reservas de pedidos ainda abertos moram lá, e apagar tudo faz a loja vender estoque que já está comprometido. Use os comandos de compensação, que tratam pedido por pedido.
Pra conferir na fonte
- Inventory Management CLI reference — Adobe Experience League
- Source selection and reservations — Adobe Experience League
- InventoryReservations db_schema.xml — GitHub