Integrações

Salable Quantity errada no Magento 2: conserte as reservas

A coluna Salable Quantity não é estoque: é estoque menos reserva. Quando uma reserva não é compensada, ela fica lá para sempre e some com a sua venda.

Por Roger Takemiya · Publicado em · 5 min de leitura

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-inconsistencies

A saída vem legível, pedido por pedido:

Order 172:
 - Product bike-123 should be compensated by +2.000000 for stock 1

As opções que valem saber:

OpçãoO que faz
-c, --complete-orderssó pedidos em estado final
-i, --incomplete-orderssó pedidos ainda abertos
-b, --bunch-sizequantos pedidos carrega por vez
-r, --rawsaí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.txt

Em 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-compensations

Dá 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:1

Repare 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_item em vez de usar a API de source-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

  1. Inventory Management CLI reference — Adobe Experience League
  2. Source selection and reservations — Adobe Experience League
  3. InventoryReservations db_schema.xml — GitHub

estoque msi cli

Precisa de um orçamento? Ficarei feliz em ajudar. Clique Aqui