SourceSelectionInterface jenseits von Priority und Distance
Magento liefert mit MSI zwei Source Selection Algorithmen mit: Priority und Distance. Für viele Betriebe reicht das nicht, wenn etwa die aktuelle Lagerauslastung, Versandkosten pro Spedition oder Kombinationen aus mehreren Kriterien die Auswahl bestimmen sollen. Dieser Artikel zeigt, wie ein eigener Algorithmus als vollwertiger Ersatz implementiert und sauber über di.xml registriert wird.
Inhaltsverzeichnis
- 1. Warum Priority und Distance oft nicht ausreichen
- 2. Das SourceSelectionInterface im Detail
- 3. Beispiel: Algorithmus nach Lagerauslastung
- 4. Registrierung in di.xml
- 5. Testbarkeit und Isolierung von Fremddaten
- 6. Performance bei vielen Sources und Positionen
- 7. Kombination mit bestehenden Kriterien statt Totalersatz
- 8. Konfigurierbarkeit über system.xml statt Hardcoding
- 9. Typische Fehler bei eigenen Source Selection Algorithmen
- 10. Zusammenfassung
- 11. FAQ
1. Warum Priority und Distance oft nicht ausreichen
Der Priority-Algorithmus wählt Sources strikt nach der im Stock zugeordneten Prioritätsreihenfolge aus und füllt eine Bestellung auf, sobald die nächste Source in der Liste an der Reihe ist. Der Distance-Algorithmus ergänzt das um eine geografische Komponente, indem die Entfernung zwischen Kundenadresse und Source-Koordinaten berechnet und die nächstgelegene Source bevorzugt wird. Beide Algorithmen sind für die häufigsten Fälle brauchbar, sie kennen aber keine betriebswirtschaftlichen Kriterien wie aktuelle Auslastung, Personalverfügbarkeit oder Versandkostenstruktur.
In der Praxis begegnet man regelmäßig Betrieben, die genau solche Kriterien brauchen: Ein Zentrallager soll bevorzugt beliefern, solange dessen Kapazität nicht ausgeschöpft ist, ein Außenlager soll erst greifen, wenn eine bestimmte Auslastungsschwelle überschritten wird. Diese Logik lässt sich mit den mitgelieferten Algorithmen nicht abbilden, weil beide ausschließlich auf statischen Konfigurationswerten arbeiten und keinen Bezug zu dynamischen Lagerkennzahlen haben. Ein eigener Algorithmus ist in diesem Fall keine Kür, sondern der einzige saubere Weg.
2. Das SourceSelectionInterface im Detail
Jeder Source Selection Algorithmus implementiert das Interface Magento\InventorySourceSelectionApi\Api\SourceSelectionInterface mit genau einer Methode: execute. Diese erhält ein InventoryRequestInterface, das den Stock und die angefragten Items mit ihren SKUs und Mengen enthält, und liefert ein SourceSelectionResultInterface zurück, das beschreibt, welche Menge aus welcher Source entnommen werden soll. Dabei kann eine Bestellposition auf mehrere Sources aufgeteilt werden, wenn eine einzelne Source die angefragte Menge nicht vollständig decken kann.
Wichtig ist, dass der Algorithmus selbst keine Bestandsbuchung vornimmt. Er trifft ausschließlich die Auswahl, welche Source welche Menge liefern soll, die eigentliche Reservierung und spätere Bestandsabschreibung übernimmt die nachgelagerte Inventory-Logik. Diese Trennung ist bewusst so gestaltet, damit ein Algorithmus zustandslos bleibt und beliebig oft für Preview-Zwecke aufgerufen werden kann, etwa im Checkout zur Anzeige der voraussichtlichen Lieferzeit, ohne dass dabei tatsächlich Bestand reserviert wird.
<?php
declare(strict_types=1);
namespace Mironsoft\InventoryUtilization\Model;
use Magento\InventorySourceSelectionApi\Api\SourceSelectionInterface;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
/**
* Contract for a custom source selection algorithm.
*/
interface UtilizationAwareSelectionInterface extends SourceSelectionInterface
{
/**
* Selects sources for the given inventory request, weighting current
* warehouse utilization over static priority values.
*
* @param InventoryRequestInterface $inventoryRequest
* @return SourceSelectionResultInterface
*/
public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface;
}
3. Beispiel: Algorithmus nach Lagerauslastung
Für das praktische Beispiel wird ein Algorithmus gebaut, der Sources nicht nach fester Priorität, sondern nach aktueller Auslastung sortiert. Die Auslastung wird über ein eigenes Custom-Attribut je Source gepflegt, das per Cron aus einem Warehouse-Management-System oder aus der Anzahl offener Picklisten berechnet wird. Der Algorithmus fragt für jede zulässige Source die Auslastung ab und bevorzugt konsequent die am wenigsten ausgelastete Source, solange diese die angefragte Menge bedienen kann.
Reicht der Bestand einer einzelnen Source nicht aus, wird die Restmenge an die nächste, am zweitwenigsten ausgelastete Source weitergereicht, bis die gesamte angefragte Menge gedeckt ist oder alle Sources erschöpft sind. Kann keine Kombination von Sources die volle Menge liefern, wird das im Ergebnis über isShippable an der jeweiligen Selection-Item-Zeile transparent gemacht, sodass nachgelagerte Prozesse wie der Checkout oder ein automatisches Backorder-Flag korrekt reagieren können.
<?php
declare(strict_types=1);
namespace Mironsoft\InventoryUtilization\Model;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionItemInterfaceFactory;
use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface;
/**
* Selects sources by current warehouse utilization instead of static priority.
*/
class UtilizationAwareSelection implements UtilizationAwareSelectionInterface
{
/**
* @param GetSourceItemsBySkuInterface $getSourceItemsBySku
* @param UtilizationRepository $utilizationRepository
* @param SourceSelectionResultInterfaceFactory $resultFactory
* @param SourceSelectionItemInterfaceFactory $itemFactory
*/
public function __construct(
private readonly GetSourceItemsBySkuInterface $getSourceItemsBySku,
private readonly UtilizationRepository $utilizationRepository,
private readonly SourceSelectionResultInterfaceFactory $resultFactory,
private readonly SourceSelectionItemInterfaceFactory $itemFactory
) {
}
/**
* Picks the least utilized source(s) able to cover the requested quantity.
*
* @param InventoryRequestInterface $inventoryRequest
* @return SourceSelectionResultInterface
*/
public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface
{
$selectionItems = [];
foreach ($inventoryRequest->getItems() as $item) {
$remaining = (float) $item->getQty();
$sourceItems = $this->getSourceItemsBySku->execute($item->getSku());
usort(
$sourceItems,
fn ($a, $b) => $this->utilizationRepository->get($a->getSourceCode())
<=> $this->utilizationRepository->get($b->getSourceCode())
);
foreach ($sourceItems as $sourceItem) {
if ($remaining <= 0) {
break;
}
$qtyToDeduct = min($remaining, (float) $sourceItem->getQuantity());
$selectionItems[] = $this->itemFactory->create([
'sourceCode' => $sourceItem->getSourceCode(),
'sku' => $item->getSku(),
'qtyToDeduct' => $qtyToDeduct,
'qtyAvailable' => (float) $sourceItem->getQuantity(),
]);
$remaining -= $qtyToDeduct;
}
}
return $this->resultFactory->create(['selectionItems' => $selectionItems]);
}
}
4. Registrierung in di.xml
Damit der neue Algorithmus im Backend als Option für den Stock wählbar ist, muss er in der di.xml als virtueller Typ auf sourceSelectionAlgorithmList registriert werden, zusammen mit einem sprechenden Code und Titel. Dieser Eintrag erweitert eine bereits vorhandene Liste aus dem InventorySourceSelectionApi-Modul per Argument-Merge, ein eigenes Preference-Overwrite des kompletten Providers ist dafür nicht nötig und würde bei Magento-Updates unnötig Konfliktpotenzial erzeugen.
Nach der Registrierung erscheint der Algorithmus in der Stock-Konfiguration unter Source Selection Algorithm als zusätzliche Auswahlmöglichkeit. Wichtig ist, dass setup:di:compile nach jeder Änderung an dieser Liste zwingend ausgeführt wird, da Magento die Provider-Liste im generierten Code cached und Änderungen sonst selbst im Developer-Modus stillschweigend ignoriert werden.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\InventorySourceSelectionApi\Model\SourceSelectionService">
<arguments>
<argument name="sourceSelectionAlgorithms" xsi:type="array">
<item name="utilization_aware" xsi:type="array">
<item name="code" xsi:type="string">utilization_aware</item>
<item name="title" xsi:type="string">Utilization Aware</item>
<item name="algorithm" xsi:type="object">
Mironsoft\InventoryUtilization\Model\UtilizationAwareSelection
</item>
</item>
</argument>
</arguments>
</type>
</config>
5. Testbarkeit und Isolierung von Fremddaten
Ein eigener Algorithmus sollte niemals direkt auf eine externe Datenquelle wie ein WMS zugreifen, sondern immer über ein Repository, das im Beispiel als UtilizationRepository injiziert wird. Das erlaubt in Unit-Tests, die Auslastungswerte über ein Mock-Objekt vollständig zu kontrollieren, ohne eine echte Datenbankverbindung oder API-Anfrage aufzubauen. Für den Algorithmus selbst reicht ein einfacher Integrationstest, der mehrere Sources mit unterschiedlicher Auslastung vorbereitet und prüft, ob die Reihenfolge der Selection-Items der erwarteten Sortierung entspricht.
Besonders wichtig ist ein Test für den Fall, dass keine Source die volle Menge liefern kann. Hier zeigt sich häufig ein Fehler in eigenen Implementierungen: Wird die Restmenge nicht korrekt zwischen den Sources weitergereicht, kann eine Bestellung fälschlich als vollständig lieferbar markiert werden, obwohl real ein Fehlbestand vorliegt. Ein solcher Fehler fällt im Testsystem oft nicht auf, weil dort selten realistische Engpasssituationen simuliert werden, und wird erst in Produktion sichtbar.
6. Performance bei vielen Sources und Positionen
Bei Bestellungen mit vielen Positionen und Stocks mit einer zweistelligen Anzahl an Sources kann die Auslastungsabfrage pro Item schnell zum Flaschenhals werden, wenn sie für jede SKU einzeln gegen die Datenbank oder ein externes System geht. Ein Caching der Auslastungswerte innerhalb eines einzelnen execute-Aufrufs ist deshalb sinnvoll, da sich die Auslastung einer Source während der Verarbeitung einer einzelnen Bestellung praktisch nie ändert. Ein einfaches Array als lokaler Cache im Repository reicht dafür meist aus.
Für den GetSourceItemsBySkuInterface-Aufruf gilt dasselbe Prinzip: Bei mehreren Positionen mit denselben SKUs sollte die Source-Item-Abfrage nicht redundant wiederholt werden. In der Praxis lohnt es sich, alle benötigten SKUs vor der eigentlichen Auswahl in einem Batch abzufragen, statt die Standard-Methode innerhalb der Schleife pro Item aufzurufen, wie es das vereinfachte Beispiel oben aus Gründen der Lesbarkeit tut.
7. Kombination mit bestehenden Kriterien statt Totalersatz
Ein häufiger Fehler bei eigenen Algorithmen ist es, alle bestehenden Kriterien vollständig zu ignorieren. Sinnvoller ist meist eine Kombination: Die Auslastung entscheidet nur dann, wenn mehrere Sources mit gleicher oder ähnlicher Priorität zur Auswahl stehen, während eine explizit gesetzte höhere Priorität weiterhin respektiert wird. Das verhindert, dass etwa ein bewusst als Notfall-Lager konfigurierter Standort plötzlich bevorzugt wird, nur weil er gerade wenig ausgelastet ist.
Praktisch lässt sich das umsetzen, indem der Algorithmus zunächst nach der in der Source-Konfiguration hinterlegten Priorität gruppiert und erst innerhalb einer Prioritätsgruppe nach Auslastung sortiert. So bleibt die vertraute Priority-Semantik als grober Rahmen erhalten, während die Auslastung als Tiebreaker innerhalb dieses Rahmens wirkt, was in den meisten Betrieben deutlich näher an der gewünschten Logik liegt als eine reine Auslastungssortierung über alle Sources hinweg.
8. Konfigurierbarkeit über system.xml statt Hardcoding
Damit der Algorithmus im Betrieb angepasst werden kann, ohne den Code zu ändern, gehört eine eigene system.xml-Sektion dazu, in der zumindest die Schwelle für eine als kritisch geltende Auslastung sowie der Cache-TTL für die Auslastungswerte konfigurierbar sind. Das entspricht dem CLAUDE.md-Grundsatz, dass jedes neue Modul eigene Einstellungen mitbringt, und verhindert, dass für jede Feinjustierung ein neues Deployment nötig wird.
Ebenso gehört eine acl.xml dazu, die den Zugriff auf diese Konfiguration auf eine eigene Berechtigung beschränkt, damit nicht jeder Admin-Nutzer mit allgemeinen Katalog-Rechten die Auslastungslogik verändern kann. Ein eigener Menüpunkt unter Stores Configuration rundet das Modul ab und macht die Einstellung für das Fulfillment-Team direkt auffindbar, statt sie in einer generischen Inventory-Sektion zu verstecken.
9. Typische Fehler bei eigenen Source Selection Algorithmen
Der häufigste Fehler ist, im Algorithmus selbst Bestand zu buchen oder Reservierungen anzulegen. Das widerspricht der Architektur von MSI, in der execute ausschließlich eine Auswahl trifft und die eigentliche Bestandsverarbeitung der nachgelagerten Order-Placement- beziehungsweise Shipment-Logik überlassen bleibt. Wird diese Trennung verletzt, entstehen doppelte Buchungen, sobald derselbe Algorithmus zusätzlich für eine reine Checkout-Vorschau aufgerufen wird.
Ein zweiter, oft übersehener Punkt ist das Fehlen von Tests für den Fall unzureichender Gesamtmenge über alle Sources hinweg. Wird dieser Fall nicht sauber behandelt, kann es zu falsch positiven Lieferzusagen im Checkout kommen. Ebenso wichtig: Der Algorithmus-Code muss über sourceSelectionAlgorithmList registriert werden, nicht als Preference auf den Standard-Provider, sonst verschwindet die eigene Option beim nächsten Magento-Minor-Update kommentarlos wieder.
| Algorithmus | Kriterium | Datenquelle | Typischer Einsatz |
|---|---|---|---|
| Priority | Feste Prioritätsreihenfolge je Stock | Source-Konfiguration | Ein klar priorisiertes Hauptlager mit Backup-Standorten |
| Distance | Geografische Entfernung zur Lieferadresse | Geocoding der Source-Adresse | Regional verteilte Lager mit Fokus auf Versanddauer |
| Utilization Aware (eigen) | Aktuelle Lagerauslastung | Custom-Attribut / WMS-Anbindung | Lastverteilung zwischen mehreren gleichwertigen Standorten |
| Kombiniert (Priority + Utilization) | Prioritätsgruppe, dann Auslastung | Source-Konfiguration + Custom-Attribut | Notfall-Lager soll trotz geringer Auslastung nachrangig bleiben |
| Kosten-basiert (denkbar) | Versandkosten pro Spedition und Source | Tarif-API der Spedition | Kostenoptimierung bei mehreren Versanddienstleistern |
Mironsoft
Magento-Entwicklung, Modul-Beratung und Systemarchitektur
Magento-Projekt, das eine zweite Meinung oder erfahrene Umsetzung braucht?
Wir entwickeln individuelle Magento-Module, beraten bei Architekturentscheidungen und übernehmen komplexe Umsetzungen, von der Service-Contract-Planung bis zum produktionsreifen Deployment.
Architektur-Beratung
Modul- und Systemarchitektur vor der Umsetzung fundiert durchdenken lassen.
Custom-Modul-Entwicklung
Individuelle Magento-Module nach Best Practices sauber umsetzen.
Code-Review & Audit
Bestehende Module auf Performance, Sicherheit und Wartbarkeit prüfen lassen.
10. Zusammenfassung
Eigener Source Selection Algorithmus: Das Wichtigste auf einen Blick
SourceSelectionInterface
Genau eine execute-Methode, die Sources auswählt, ohne selbst Bestand zu buchen.
Eigene Kriterien
Auslastung, Kosten oder Kombinationen lassen sich abbilden, wo Priority und Distance nicht reichen.
di.xml-Registrierung
Als Eintrag in sourceSelectionAlgorithmList, nicht als Preference, plus setup:di:compile.
Sauber trennen
Auswahl-Logik, Konfiguration und externe Datenquellen über Repository-Interfaces isolieren.