Multi Source Inventory (MSI) to moduł wprowadzony w Magento 2.3, który całkowicie zmienia sposób zarządzania stanami magazynowymi. Zamiast jednego globalnego stanu dla każdego SKU, masz wiele źródeł (magazynów fizycznych, sklepów, dostawców) i algorytmy wyboru z którego źródła realizować zamówienie. Jeśli rozwijasz moduły Magento i jeszcze nie poznałeś MSI – ten wpis jest dla Ciebie.
Stary model vs MSI – czym się różnią
Przed MSI Magento miało jeden stan magazynowy per produkt per strona (website). Tabela cataloginventory_stock_item trzymała jedną ilość dla każdego SKU. Proste, ale nieelastyczne – nie dało się modelować wielu magazynów fizycznych ani różnych strategii realizacji.
MSI wprowadza trzy nowe pojęcia:
- Source (Źródło) – fizyczna lokalizacja zapasów (magazyn, sklep stacjonarny, dostawca)
- Stock (Zasób) – wirtualna pula zapasów agregująca wiele Sources, przypisana do kanałów sprzedaży (websites)
- Source Selection Algorithm – algorytm decydujący z którego Source realizować konkretne zamówienie
Konfiguracja Source i Stock
# Sprawdź dostępne source'y przez CLI bin/magento inventory:source:list # Sprawdź skonfigurowane stocki bin/magento inventory:stock:list
Przez kod możesz zarządzać Source i Stock programatycznie:
<?php
declare(strict_types=1);
use Magento\InventoryApi\Api\Data\SourceInterfaceFactory;
use Magento\InventoryApi\Api\SourceRepositoryInterface;
class SourceManager
{
public function __construct(
private SourceInterfaceFactory $sourceFactory,
private SourceRepositoryInterface $sourceRepository
) {}
public function createSource(
string $code,
string $name,
string $city,
string $countryId = 'PL'
): void {
$source = $this->sourceFactory->create();
$source->setSourceCode($code);
$source->setName($name);
$source->setEnabled(true);
$source->setCity($city);
$source->setCountryId($countryId);
$source->setPostcode('00-001');
$this->sourceRepository->save($source);
}
}
Zarządzanie ilościami w Source
<?php
declare(strict_types=1);
use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory;
use Magento\InventoryApi\Api\SourceItemsSaveInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
class InventoryManager
{
public function __construct(
private SourceItemInterfaceFactory $sourceItemFactory,
private SourceItemsSaveInterface $sourceItemsSave
) {}
public function updateQuantity(
string $sku,
string $sourceCode,
float $quantity
): void {
$sourceItem = $this->sourceItemFactory->create();
$sourceItem->setSku($sku);
$sourceItem->setSourceCode($sourceCode);
$sourceItem->setQuantity($quantity);
$sourceItem->setStatus(
$quantity > 0
? SourceItemInterface::STATUS_IN_STOCK
: SourceItemInterface::STATUS_OUT_OF_STOCK
);
// Zapis wielu source itemów naraz - bardziej wydajne
$this->sourceItemsSave->execute([$sourceItem]);
}
public function bulkUpdate(array $updates): void
{
// $updates = [['sku' => 'SKU-001', 'source' => 'warehouse-warsaw', 'qty' => 50], ...]
$sourceItems = [];
foreach ($updates as $update) {
$item = $this->sourceItemFactory->create();
$item->setSku($update['sku']);
$item->setSourceCode($update['source']);
$item->setQuantity((float) $update['qty']);
$item->setStatus(
$update['qty'] > 0
? SourceItemInterface::STATUS_IN_STOCK
: SourceItemInterface::STATUS_OUT_OF_STOCK
);
$sourceItems[] = $item;
}
$this->sourceItemsSave->execute($sourceItems);
}
}
Pobieranie dostępności produktu
<?php
declare(strict_types=1);
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
use Magento\InventorySalesApi\Api\IsProductSalableInterface;
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
use Magento\InventorySalesApi\Api\Data\SalesChannelInterfaceFactory;
use Magento\InventorySalesApi\Api\StockResolverInterface;
class AvailabilityChecker
{
public function __construct(
private GetProductSalableQtyInterface $getProductSalableQty,
private IsProductSalableInterface $isProductSalable,
private StockResolverInterface $stockResolver,
private SalesChannelInterfaceFactory $salesChannelFactory
) {}
public function getSalableQty(string $sku, string $websiteCode = 'base'): float
{
$salesChannel = $this->salesChannelFactory->create([
'data' => [
'type' => SalesChannelInterface::TYPE_WEBSITE,
'code' => $websiteCode,
],
]);
$stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode);
$stockId = (int) $stock->getStockId();
return $this->getProductSalableQty->execute($sku, $stockId);
}
public function isSalable(string $sku, string $websiteCode = 'base'): bool
{
$stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode);
$stockId = (int) $stock->getStockId();
return $this->isProductSalable->execute($sku, $stockId);
}
}
Source Selection Algorithm – wybór źródła realizacji
Gdy zamówienie jest składane, MSI musi zdecydować z którego Source pobrać towar. Dostępne algorytmy:
- Priority based – pobiera z Source o najwyższym priorytecie (domyślny)
- Distance based – pobiera z Source najbliższego adresu dostawy (wymaga Google Maps API)
Własny algorytm SSA:
<?php
declare(strict_types=1);
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\InventorySourceSelectionApi\Api\SourceSelectionServiceInterface;
use Magento\InventorySourceSelectionApi\Model\SourceSelectionInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionItemInterfaceFactory;
class CheapestSourceAlgorithm implements SourceSelectionInterface
{
// Unikalny kod algorytmu - rejestrowany w di.xml
public const CODE = 'vendor_cheapest_source';
public function __construct(
private SourceSelectionResultInterfaceFactory $sourceSelectionResultFactory,
private SourceSelectionItemInterfaceFactory $sourceSelectionItemFactory
) {}
public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface
{
$items = [];
foreach ($inventoryRequest->getItems() as $item) {
// Tu twoja logika wyboru najtańszego źródła
// np. na podstawie kosztu wysyłki z każdego source
$sourceCode = $this->findCheapestSource($item->getSku(), $item->getQty());
$items[] = $this->sourceSelectionItemFactory->create([
'sourceCode' => $sourceCode,
'sku' => $item->getSku(),
'qtyToDeduct' => $item->getQty(),
'qtyAvailable' => $this->getQtyAvailable($sourceCode, $item->getSku()),
]);
}
return $this->sourceSelectionResultFactory->create([
'sourceSelectionItems' => $items,
'isShippable' => !empty($items),
]);
}
private function findCheapestSource(string $sku, float $qty): string
{
// Logika biznesowa - np. porównanie kosztów wysyłki
return 'warehouse-warsaw'; // uproszczenie
}
private function getQtyAvailable(string $sourceCode, string $sku): float
{
return 100.0; // uproszczenie
}
}
Rejestracja algorytmu w di.xml:
<?xml version="1.0"?>
<config>
<type name="Magento\InventorySourceSelectionApi\Model\SourceSelectionService">
<arguments>
<argument name="sourceSelectionMethods" xsi:type="array">
<item name="vendor_cheapest_source" xsi:type="object">
Vendor\Module\Model\Algorithm\CheapestSourceAlgorithm
</item>
</argument>
</arguments>
</type>
</config>
Reindeksacja MSI
# MSI ma własne indeksery bin/magento indexer:reindex inventory # Sprawdź stan indekserów MSI bin/magento indexer:status | grep inventory # Przelicz rezerwacje (ważne po imporcie zamówień) bin/magento inventory:reservations:cleanup
Pułapki przy migracji na MSI
Jeśli masz stary moduł który bezpośrednio operuje na cataloginventory_stock_item przez Magento\CatalogInventory\Api\StockRegistryInterface – to nadal działa, ale operuje tylko na „Default Source”. Żeby w pełni korzystać z MSI musisz migrować do interfejsów z przestrzeni Magento\InventoryApi.
<?php
// STARY sposób - działa tylko z Default Source
use Magento\CatalogInventory\Api\StockRegistryInterface;
$stockItem = $stockRegistry->getStockItemBySku('SKU-001');
$qty = $stockItem->getQty(); // tylko Default Source!
// NOWY sposób - uwzględnia wszystkie Sources w Stocku
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface;
$salableQty = $getProductSalableQty->execute('SKU-001', $stockId); // sumuje wszystkie Sources
Podsumowanie
MSI to fundamentalna zmiana w zarządzaniu inwentarzem Magento – od prostego licznika do elastycznego systemu wielomagazynowego. Dla projektów które mają jeden magazyn fizyczny różnica jest nieodczuwalna. Prawdziwa wartość pojawia się przy wielu lokalizacjach, dropshippingu lub click-and-collect. Kluczowe przy pracy z MSI to używanie nowych interfejsów z Magento\InventoryApi zamiast starych z Magento\CatalogInventory.
