PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

MSI – Source, Stock, SSA, custom algorithm, migrating from legacy API

by Henryk Tews / Tuesday, 11 October 2022 / Published in Magento 2

Multi Source Inventory replaced the old single-stock Magento inventory system. MSI introduces Sources (physical locations), Stocks (virtual aggregations), and Source Selection Algorithms (how to fulfil orders). Most shops use only default configurations, but once you have multiple warehouses or need custom fulfilment logic, MSI requires deep understanding. I show the architecture and how to write a custom SSA.

MSI concepts

  • Source – a physical location with inventory: warehouse, shop, supplier
  • Source Item – quantity of a specific SKU at a specific Source
  • Stock – a virtual pool of Sources assigned to a sales channel (website)
  • Source Selection Algorithm (SSA) – decides which Sources to deduct from when an order is placed
  • Salable Quantity – aggregated available quantity for a SKU in a Stock

CLI – checking MSI state

# List all sources
bin/magento inventory:source:list

# List all stocks
bin/magento inventory:stock:list

# Assign source to stock
bin/magento inventory:stock-source-link:assign-stock-to-source \
    --stockId=1 --sourceCode=warehouse_wroclaw

# Check salable quantity for a SKU
bin/magento inventory:reservations:cleanup  # clean old reservations

Programmatic inventory management

<?php

declare(strict_types=1);

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

class InventoryUpdateService
{
    public function __construct(
        private SourceItemInterfaceFactory $sourceItemFactory,
        private SourceItemsSaveInterface $sourceItemsSave,
        private \Magento\InventoryApi\Api\GetSourceItemsBySkuInterface $getSourceItems
    ) {}

    public function updateStock(string $sku, string $sourceCode, float $qty): void
    {
        $sourceItem = $this->sourceItemFactory->create();
        $sourceItem->setSourceCode($sourceCode);
        $sourceItem->setSku($sku);
        $sourceItem->setQuantity($qty);
        $sourceItem->setStatus($qty > 0 ? 1 : 0);

        $this->sourceItemsSave->execute([$sourceItem]);
    }

    public function getTotalQty(string $sku): float
    {
        $total = 0.0;
        foreach ($this->getSourceItems->execute($sku) as $item) {
            if ($item->getStatus() === SourceItemInterface::STATUS_IN_STOCK) {
                $total += $item->getQuantity();
            }
        }
        return $total;
    }
}

Custom Source Selection Algorithm

<?php

declare(strict_types=1);

namespace Vendor\Module\Model\SourceSelection;

use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\InventorySourceSelectionApi\Api\SourceSelectionServiceInterface;
use Magento\InventorySourceSelectionApi\Model\SourceSelectionInterface;

// Custom SSA: prefer the source closest to the shipping address
class ClosestSourceAlgorithm implements SourceSelectionInterface
{
    public function __construct(
        private \Magento\InventoryApi\Api\GetSourcesAssignedToStockOrderedByPriorityInterface $getSourcesForStock,
        private \Vendor\Module\Model\DistanceCalculator $distanceCalc,
        private \Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterfaceFactory $resultFactory,
        private \Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionItemInterfaceFactory $itemFactory
    ) {}

    public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface
    {
        $stockId      = $inventoryRequest->getStockId();
        $sources      = $this->getSourcesForStock->execute($stockId);
        $shippingAddr = $inventoryRequest->getExtensionAttributes()?->getDestinationAddress();

        // Sort sources by distance to shipping address
        $sortedSources = $this->sortByDistance($sources, $shippingAddr);

        $selectionItems = [];
        $isShippable    = true;

        foreach ($inventoryRequest->getItems() as $requestItem) {
            $sku      = $requestItem->getSku();
            $qtyNeeded = $requestItem->getQty();
            $qtyLeft   = $qtyNeeded;

            foreach ($sortedSources as $source) {
                if ($qtyLeft <= 0) break;

                $available = $this->getAvailableQty($sku, $source->getSourceCode());
                $deduct    = min($available, $qtyLeft);

                if ($deduct > 0) {
                    $selectionItems[] = $this->itemFactory->create([
                        'sourceCode'    => $source->getSourceCode(),
                        'sku'           => $sku,
                        'qtyToDeduct'   => $deduct,
                        'qtyAvailable'  => $available,
                    ]);
                    $qtyLeft -= $deduct;
                }
            }

            if ($qtyLeft > 0) {
                $isShippable = false;
            }
        }

        return $this->resultFactory->create([
            'sourceSelectionItems' => $selectionItems,
            'isShippable'          => $isShippable,
        ]);
    }

    private function sortByDistance(array $sources, ?object $address): array
    {
        if ($address === null) return $sources;

        usort($sources, fn($a, $b) =>
            $this->distanceCalc->calculate($a, $address)
            <=>
            $this->distanceCalc->calculate($b, $address)
        );
        return $sources;
    }

    private function getAvailableQty(string $sku, string $sourceCode): float { return 0.0; }
}
<!-- Register the custom SSA in di.xml -->
<type name="Magento\InventorySourceSelectionApi\Model\SourceSelectionService">
    <arguments>
        <argument name="sourceSelectionMethods" xsi:type="array">
            <item name="closest_source" xsi:type="array">
                <item name="code"  xsi:type="string">closest_source</item>
                <item name="title" xsi:type="string" translatable="true">Closest Source</item>
                <item name="model" xsi:type="object">Vendor\Module\Model\SourceSelection\ClosestSourceAlgorithm</item>
            </item>
        </argument>
    </arguments>
</type>

Migrating from legacy stock API

<?php

// OLD API - still works but deprecated since Magento 2.3
use Magento\CatalogInventory\Api\StockRegistryInterface;
$stockItem = $this->stockRegistry->getStockItemBySku('SKU-001');
$qty = $stockItem->getQty();

// NEW MSI API - preferred
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
$salableQty = $this->getSalableQty->execute('SKU-001', $stockId);

// Check if product is salable for a specific stock
use Magento\InventorySalesApi\Api\IsProductSalableInterface;
$isSalable = $this->isProductSalable->execute('SKU-001', $stockId);

Summary

MSI is complex but the complexity maps to real business requirements. Single warehouse shops can use defaults and never touch it. Multi-warehouse operations need to understand Sources, Stocks, and SSA thoroughly. The custom SSA is the most powerful customisation point – it gives complete control over fulfilment logic. Use the new MSI API from the start; the legacy StockRegistry still works but will eventually be removed.

About Henryk Tews

What you can read next

Strategy pattern in PHP – and how Magento 2 uses it in pricing
Xdebug – configuration, PHPStorm, debugging Magento plugins

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}