← Voltar ao blog

MSI no Magento 2: sources, stocks e sales channels

Rodrigo Biazzi

O Multi-Source Inventory, geralmente chamado de MSI, é o sistema de inventário do Magento desde o 2.3. Muitas lojas ainda o rodam em modo single source sem nunca olhar para ele, e depois se surpreendem com a salable quantity, as reservas ou um estoque que não bate com o ERP. Este artigo explica o modelo por trás do MSI no Magento Open Source e no Adobe Commerce 2.4.8 e como trabalhar com ele por código.

Os três blocos de construção

Sources

Uma source é um lugar físico que guarda estoque: um armazém, uma loja, um drop shipper. Cada source tem um código, um endereço e um flag de ativada. A quantidade de um produto numa source é um source item, identificado pelo SKU e pelo código da source, com uma quantidade e um status de in stock ou out of stock.

Toda instalação tem uma source chamada default. Ela não pode ser apagada. Quando o MSI roda em modo single source, tudo fica nessa source e o formulário de produto do admin mostra o campo Quantity clássico.

Stocks

Um stock é um grupo virtual de sources. Ele responde à pergunta "quanto deste SKU este sales channel pode vender?". Um stock é ligado a uma ou mais sources, em ordem de prioridade, e a quantidade que ele oferece é a soma dos source items in stock das sources ligadas e ativadas.

O Default Stock, com ID 1, também não pode ser apagado. Ele é ligado à source default e, por padrão, atende o website principal.

Sales channels

Um sales channel liga um stock ao lugar onde os produtos são vendidos. No core, o único tipo de channel é o website. Cada website é atribuído a exatamente um stock, e um stock pode atender vários websites. O storefront e o checkout de um website checam a vendabilidade contra esse stock.

Então a cadeia é website, depois stock, depois sources, depois source items. O stock não é escolhido por store view nem por customer.

Quando várias sources fazem sentido

O MSI é útil quando você realmente envia de mais de um lugar, ou quando websites diferentes vendem de armazéns diferentes. Casos típicos são um site de país que envia de um armazém local, lojas físicas que também atendem pedidos online, ou um drop shipper cuja quantidade você recebe por feed.

Se você tem um armazém e um website, o modo single source é a escolha certa. Sources adicionais trazem complexidade no admin, nas importações e em toda integração, sem benefício.

Source selection e dedução

Fazer um pedido não deduz quantidade de nenhuma source. Cria uma reserva contra o stock, que diminui a salable quantity. A source é escolhida depois, quando o pedido é enviado.

Quando você cria um shipment num setup multi source, o Source Selection Algorithm sugere de quais sources enviar. O core traz um algoritmo baseado em prioridade, que usa a ordem das sources no stock, e um algoritmo baseado em distância, que pode usar o Google Maps ou dados de geocodificação offline que você importa. Você também pode escolher as sources na mão na página do shipment.

Quando o shipment é salvo, a quantidade é deduzida dos source items escolhidos e uma reserva compensatória é adicionada, para a salable quantity continuar correta. Todo o mecanismo de reservas, e como corrigi-lo quando desvia, está em salable quantity e reservas.

Tabelas por trás do MSI

TabelaConteúdo
inventory_sourceSources e seus endereços
inventory_stockStocks
inventory_source_stock_linkQuais sources pertencem a qual stock, com prioridade
inventory_stock_sales_channelQual website usa qual stock
inventory_source_itemQuantidade e status por SKU e source
inventory_reservationReservas por SKU e stock
inventory_stock_NQuantidade indexada e vendabilidade por stock

Para o default stock, o inventory_stock_1 é uma view do banco sobre a tabela legada cataloginventory_stock_status. Stocks adicionais recebem tabelas de índice de verdade, construídas pelo indexer inventory. É por isso que a tabela legada cataloginventory_stock_item continua sincronizada para a source default, e por que código que grava só nas tabelas legadas parece funcionar em lojas single source e quebra nas multi source.

Atualizando quantidades de source por código

Integrações devem gravar source items pelos service contracts, não pelo model legado de stock item e nunca com SQL direto. O serviço atualiza as tabelas legadas para a source default e dispara a reindexação certa.

<?php
declare(strict_types=1);

namespace Vendor\StockSync\Model;

use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory;
use Magento\InventoryApi\Api\SourceItemsSaveInterface;

class UpdateSourceQty
{
    public function __construct(
        private readonly SourceItemInterfaceFactory $sourceItemFactory,
        private readonly SourceItemsSaveInterface $sourceItemsSave
    ) {
    }

    /**
     * @param array<int, array{sku: string, source: string, qty: float}> $rows
     */
    public function execute(array $rows): void
    {
        $items = [];
        foreach ($rows as $row) {
            $item = $this->sourceItemFactory->create();
            $item->setSku($row['sku']);
            $item->setSourceCode($row['source']);
            $item->setQuantity($row['qty']);
            $item->setStatus(
                $row['qty'] > 0
                    ? SourceItemInterface::STATUS_IN_STOCK
                    : SourceItemInterface::STATUS_OUT_OF_STOCK
            );
            $items[] = $item;
        }

        $this->sourceItemsSave->execute($items);
    }
}

Envie os itens em lotes de algumas centenas em vez de uma chamada por SKU. O save aceita uma lista, e o lote mantém baixo o número de gatilhos de reindexação.

A mesma operação está disponível por REST, que é o caminho usual para um ERP:

curl -X POST https://example.test/rest/V1/inventory/source-items \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"sourceItems":[{"sku":"ABC-001","source_code":"warehouse_sp","quantity":25,"status":1}]}'

Para atualizações em massa a partir de um arquivo, a importação tem uma entidade Stock Sources com as colunas source_code, sku, status e quantity.

Lendo a salable quantity por código

Não leia a quantidade do source item quando quiser saber o que pode ser vendido. Resolva o stock do website e peça a salable quantity, que inclui as reservas e o out of stock threshold.

<?php
declare(strict_types=1);

namespace Vendor\StockSync\Model;

use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
use Magento\InventorySalesApi\Api\StockResolverInterface;

class SalableQty
{
    public function __construct(
        private readonly StockResolverInterface $stockResolver,
        private readonly GetProductSalableQtyInterface $getProductSalableQty
    ) {
    }

    public function forWebsite(string $sku, string $websiteCode): float
    {
        $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode);

        return $this->getProductSalableQty->execute($sku, (int)$stock->getStockId());
    }
}

Isso lança uma exceção para tipos de produto que não têm quantidade própria, como pais de configurable e de bundle, então chame nos filhos simples.

Erros comuns

  • Atribuir uma source nova mas não ligá-la a um stock. A quantidade existe mas nenhum website consegue vendê-la.
  • Deixar a source default atribuída com quantidade zero depois de migrar para novas sources. É inofensivo para a vendabilidade mas confuso no admin e nas importações. Desatribua dos produtos que você não envia mais de lá.
  • Gravar estoque com SQL. Isso pula a sincronização legada e os indexers. O storefront mostra algo diferente do admin até uma reindexação completa.
  • Não rodar os consumers da message queue. Várias operações de inventário, como atualizações de reserva depois de ações em massa, passam por consumers. Se o cron não os inicia, as coisas desviam. Veja consumers da message queue.

Posso desativar o MSI?

Algumas lojas single source desativam os módulos Magento_Inventory* para economizar overhead, o que volta ao comportamento legado do CatalogInventory. Funciona, mas precisa ser feito para o grupo inteiro de módulos e seus dependentes, afeta módulos de terceiros que esperam o MSI e precisa ser conferido de novo a cada upgrade. Eu só faço quando há um motivo medido. Se for fazer, use bin/magento module:disable com a lista completa e rode setup:upgrade, como descrito em removendo um módulo com segurança.

FAQ

Qual a diferença entre source e stock no Magento 2?

Uma source é um local físico que guarda quantidade. Um stock é um grupo virtual de sources do qual um website vende. Websites são atribuídos a stocks, nunca diretamente a sources.

Um website pode usar mais de um stock?

Não. Cada website é atribuído a exatamente um stock. Um stock pode ser compartilhado por vários websites e pode incluir várias sources.

Por que a quantidade não é deduzida quando um pedido é feito?

O MSI cria uma reserva na hora do pedido e só deduz da source quando o pedido é enviado. A salable quantity já considera a reserva, então o overselling é evitado.

Devo atualizar o estoque pelo cataloginventory_stock_item?

Não. Use o SourceItemsSaveInterface ou os endpoints REST de inventory. O stock item legado é mantido em sincronia só para a source default e gravar nele direto ignora o MSI.

Atendo como freelancer em Magento e Adobe Commerce, incluindo setup de MSI e integrações de estoque com ERP. Se você precisa de ajuda com isso, fale comigo.

Artigos relacionados