Wie State und Status in Magentos Order-System zusammenspielen und ein eigener Status sauber registriert, zugeordnet und in Admin-Grid und E-Mail-Trigger eingehängt wird
Wer zum ersten Mal einen eigenen Bestellstatus in Magento 2 anlegen will, stolpert fast immer über dieselbe Verwechslung: Order State und Order Status sind zwei getrennte Ebenen, und wer diesen Unterschied nicht sauber durchdenkt, produziert Status, die im Admin-Grid nicht filterbar sind oder E-Mail-Benachrichtigungen auslösen, die niemand erwartet hat. Dieser Artikel erklärt den Aufbau von Magentos Order-Workflow-System im Detail und zeigt, wie sich ein neuer Status korrekt registriert, einem State zugeordnet und in die bestehenden Statusübergänge und Benachrichtigungspfade eingehängt wird.
Inhaltsverzeichnis
- 1. Order State versus Order Status: die zentrale Unterscheidung
- 2. Die zugrunde liegenden Tabellen: sales_order_status und sales_order_status_state
- 3. Einen eigenen Status per Data Patch registrieren
- 4. Den neuen Status in Statusübergänge einhängen
- 5. Sichtbarkeit im Kundenkonto und Storefront-Übersetzung
- 6. Auswirkung auf Admin-Grid-Filter
- 7. Auswirkung auf E-Mail-Benachrichtigungen
- 8. Auswirkung auf REST- und GraphQL-APIs
- 9. Status-Workflow-Bausteine im Überblick
- 10. Zusammenfassung
- 11. FAQ
1. Order State versus Order Status: die zentrale Unterscheidung
Ein Order State ist eine feste, im Magento-Kern hartcodierte Konstante wie Order::STATE_PROCESSING, STATE_COMPLETE oder STATE_HOLDED, an die zahlreiche Kernprozesse gebunden sind, etwa ob eine Bestellung überhaupt noch versendet oder storniert werden kann. States lassen sich nicht beliebig erweitern, weil viel Kernlogik direkt gegen diese festen Werte prüft, statt gegen den flexibleren Status.
Ein Order Status dagegen ist frei definierbar und rein für die Anzeige und Kommunikation gedacht, etwa im Admin-Grid, in Kunden-E-Mails oder im Kundenkonto. Mehrere Status können demselben State zugeordnet sein: Der State processing etwa kann sowohl den Standard-Status processing als auch einen eigenen Status wie warehouse_picking tragen, ohne dass sich am zugrunde liegenden Verhalten der Bestellung technisch etwas ändert.
2. Die zugrunde liegenden Tabellen: sales_order_status und sales_order_status_state
Auf Datenbankebene hält die Tabelle sales_order_status jeden verfügbaren Status mit seinem Label, während sales_order_status_state die eigentliche Zuordnung zwischen Status und State abbildet, inklusive eines is_default-Flags, das festlegt, welcher Status als Standard für einen bestimmten State gilt. Diese Zwei-Tabellen-Struktur ist der Grund, warum ein neuer Status nicht einfach als String irgendwo gesetzt werden kann, sondern zunächst formal registriert werden muss.
Der visible_on_front-Wert in sales_order_status_state steuert zusätzlich, ob ein Status im Kundenkonto sichtbar ist. Ein interner Status wie fraud_review lässt sich damit gezielt vor dem Kunden verbergen, während er im Admin-Grid weiterhin normal gefiltert und angezeigt werden kann, was für interne Prüfstatus ohne Kundenkommunikation der übliche Ansatz ist.
3. Einen eigenen Status per Data Patch registrieren
Da es sich bei einem neuen Status um Daten und nicht um Schema handelt, gehört die Registrierung in einen Data Patch statt in db_schema.xml. Der Patch legt den Status über StatusInterface an und ordnet ihn anschließend über StatusResource::assignState() einem bestehenden State zu, ohne dass ein neuer State im Kern erzeugt werden muss.
Wichtig ist, den Patch idempotent zu schreiben, da er bei jedem setup:upgrade erneut ausgeführt werden könnte, falls die Versionsprüfung fehlerhaft konfiguriert ist. Eine Prüfung, ob der Status bereits existiert, bevor er erneut angelegt wird, verhindert doppelte Einträge und unnötige Fehlermeldungen bei wiederholten Deployments.
<?php
declare(strict_types=1);
namespace Mironsoft\OrderWorkflow\Setup\Patch\Data;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Sales\Model\Order\Status;
use Magento\Sales\Model\Order\StatusFactory;
use Magento\Sales\Model\ResourceModel\Order\Status as StatusResource;
use Magento\Sales\Model\Order;
/**
* Registriert den eigenen Status "warehouse_picking" und ordnet ihn dem State processing zu.
*/
final class AddWarehousePickingStatus implements DataPatchInterface
{
public function __construct(
private readonly StatusFactory $statusFactory,
private readonly StatusResource $statusResource,
) {
}
/**
* Führt die Registrierung des neuen Status aus.
*
* @return void
*/
public function apply(): void
{
/** @var Status $status */
$status = $this->statusFactory->create();
$status->setData([
'status' => 'warehouse_picking',
'label' => 'Wird kommissioniert',
]);
$this->statusResource->save($status);
$this->statusResource->assignState($status, Order::STATE_PROCESSING, false);
}
public static function getDependencies(): array
{
return [];
}
public function getAliases(): array
{
return [];
}
}
4. Den neuen Status in Statusübergänge einhängen
Damit eine Bestellung tatsächlich in den neuen Status wechselt, reicht die reine Registrierung nicht aus, es braucht zusätzlich einen Auslöser. Üblich ist ein Observer auf einem geeigneten Sales-Event, etwa sales_order_invoice_pay oder ein eigenes, im Warehouse-System ausgelöstes Event, der über Order::setStatus() gezielt den neuen Status setzt, statt pauschal auf setState() zu vertrauen, das den Status implizit auf den Default-Status des Ziel-States zurücksetzen würde.
Bei komplexeren Workflows mit mehreren möglichen Übergängen aus demselben Status heraus lohnt sich eine eigene, kleine State-Machine-Klasse, die erlaubte Übergänge explizit auflistet und einen Übergang in einen nicht erlaubten Status mit einer aussagekräftigen Exception verweigert. Das verhindert, dass ein fehlerhaft konfigurierter Observer eine Bestellung in einen fachlich unsinnigen Status wie von complete zurück nach warehouse_picking versetzt.
5. Sichtbarkeit im Kundenkonto und Storefront-Übersetzung
Ob ein Status im Kundenkonto überhaupt angezeigt wird, entscheidet das bereits erwähnte visible_on_front-Flag, doch selbst ein sichtbarer Status bleibt für den Kunden wenig hilfreich, wenn sein Label nicht in die jeweilige Storefront-Sprache übersetzt ist. Magento übersetzt Status-Labels über die normale CSV-basierte Sprachdatei-Übersetzung des Themes, nicht über das Label-Feld aus sales_order_status selbst, das nur als Fallback für nicht übersetzte Sprachen dient.
Wird ein neuer Status für mehrere Store-Views mit unterschiedlichen Sprachen ausgerollt, muss der englische oder deutsche Rohtext aus dem Data Patch deshalb zusätzlich als Übersetzungseintrag in der jeweiligen i18n-CSV-Datei des aktiven Themes ergänzt werden, sonst zeigt das Kundenkonto in anderssprachigen Store-Views weiterhin den unübersetzten Rohwert an, was besonders bei international ausgerollten Shops leicht übersehen wird.
6. Auswirkung auf Admin-Grid-Filter
Das Order-Grid im Admin filtert standardmäßig über die Status-Spalte, die aus sales_order_grid gespeist wird, einer denormalisierten Tabelle, die bei jeder Statusänderung über einen Indexer beziehungsweise Plugin synchron gehalten wird. Ein neu registrierter Status erscheint automatisch als Filteroption im Grid, sofern er korrekt über sales_order_status registriert wurde, ganz ohne zusätzlichen Code für die Grid-Integration selbst.
Anders sieht es aus, wenn der neue Status mit einer eigenen Farbkennzeichnung oder einem Icon im Grid hervorgehoben werden soll, was einen zusätzlichen UI-Component-Eingriff über sales_order_grid.xml erfordert. Ohne diese Anpassung wird der Status zwar korrekt angezeigt und gefiltert, fällt aber optisch nicht von den Standardstatus ab, was bei häufig genutzten internen Status die Übersichtlichkeit im Tagesgeschäft einschränken kann.
7. Auswirkung auf E-Mail-Benachrichtigungen
Magentos Standard-E-Mail-Benachrichtigungen für Bestellungen, Rechnungen und Versand hängen primär am State-Übergang, nicht am konkreten Status, weshalb ein neuer Status innerhalb desselben States standardmäßig keine zusätzliche E-Mail auslöst. Wer bei Erreichen des neuen Status aktiv eine Benachrichtigung an den Kunden verschicken möchte, muss dafür einen eigenen Observer registrieren, der beim Status-Wechsel gezielt ein Transaktions-E-Mail-Template über den TransportBuilder versendet.
Wichtig ist dabei, den internen Charakter mancher neuer Status zu respektieren: Ein Status wie warehouse_picking ist typischerweise nur für interne Logistikprozesse relevant und sollte bewusst keine Kunden-E-Mail auslösen, während ein kundenrelevanter Status wie partially_shipped durchaus eine eigene Benachrichtigung rechtfertigt, die über die Standard-Versandbestätigung hinausgeht.
8. Auswirkung auf REST- und GraphQL-APIs
Ein neu registrierter Status erscheint automatisch im status-Feld der REST-Order-API und im entsprechenden GraphQL-Feld der Kundenbestellungen, ganz ohne zusätzliche API-Anpassung, da beide Schnittstellen den Rohwert aus der Bestellung durchreichen. Externe Systeme, die über die API angebunden sind, etwa ein ERP oder eine mobile App, müssen den neuen Statuswert aber selbst kennen, um ihn korrekt zu behandeln. Ohne diese Abstimmung entsteht leicht eine Situation, in der Magento den Status korrekt führt, das angebundene System ihn aber schlicht nicht kennt und deshalb falsch oder gar nicht darstellt.
Besonders bei GraphQL-Konsumenten, die Status-Werte hartcodiert gegen eine feste Liste bekannter Werte prüfen, führt ein unangekündigter neuer Status leicht zu einem stillschweigend ignorierten oder falsch dargestellten Fall im Frontend-Client. Ein neuer Status sollte deshalb nicht nur intern dokumentiert, sondern auch aktiv an alle Teams kommuniziert werden, die eigene Konsumenten der Order-API betreiben, idealerweise über einen festen Änderungsprozess mit Vorlaufzeit statt einer stillen Produktions-Änderung ohne Ankündigung.
9. Status-Workflow-Bausteine im Überblick
Die folgende Tabelle fasst die zentralen Bausteine eines eigenen Order-Status-Workflows mit ihrer jeweiligen Aufgabe zusammen.
| Baustein | Zuständige Tabelle/Klasse | Aufgabe | Besonderheit |
|---|---|---|---|
| Status-Definition | sales_order_status | Status mit Label registrieren | Reine Daten, kein Schema |
| Status-zu-State-Zuordnung | sales_order_status_state | Status einem festen State zuordnen | Mehrere Status pro State möglich |
| Registrierung | Data Patch mit StatusResource::assignState | Status idempotent anlegen | Kein neuer State im Kern nötig |
| Statusübergang | Observer + setStatus() | Status bei Ereignis aktiv setzen | setState() setzt sonst Default-Status |
| Admin-Grid-Filter | sales_order_grid | Automatischer Filter-Eintrag | Farbliche Hervorhebung braucht sales_order_grid.xml |
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
Eigene Bestellstatus: Das Wichtigste auf einen Blick
Kernidee
State ist fest und kernrelevant, Status ist frei definierbar und primär für Anzeige und Kommunikation gedacht.
Registrierungsweg
Ein Data Patch legt den Status an und ordnet ihn per assignState einem bestehenden State zu.
Größte Falle
setState() setzt implizit den Default-Status des Ziel-States zurück, statt den eigenen Status zu erhalten.
Erfolgskriterium
Neue Status erscheinen automatisch im Admin-Grid-Filter, lösen aber nur mit eigenem Observer E-Mails aus.