Requisition Lists in Magento 2: Wiederkehrende Bestelllisten technisch verstehen
AI generated
M2
di.xml
Magento 2 · B2B Suite
Requisition Lists
Wiederkehrende Bestelllisten technisch verstehen

Requisition Lists sind das B2B-Werkzeug für Stammkunden, die regelmäßig dieselben Positionen bestellen, mit eigenem Datenmodell, eigener API und einer klaren fachlichen Abgrenzung zur Wishlist. Wer eine eigene Frontend-Integration bauen will, muss wissen, wie das Datenmodell aufgebaut ist, welche API-Operationen zur Verfügung stehen und wo die Grenzen des Standardfeatures liegen.

12 Min. Lesezeit Requisition Lists · B2B Suite Magento 2.4.x Commerce

1. Requisition Lists im B2B-Modul einordnen

Requisition Lists sind Teil des Moduls Magento_RequisitionList aus der B2B-Suite und lösen ein sehr konkretes Problem: Firmenkunden bestellen häufig immer wieder dieselben oder sehr ähnliche Positionen, etwa Verbrauchsmaterial oder Ersatzteile, und wollen dafür nicht jedes Mal erneut im Katalog suchen. Eine Requisition List ist im Kern eine benannte, wiederverwendbare Sammlung von SKUs mit fest hinterlegten Mengen, die sich als Ganzes oder teilweise in den Warenkorb übernehmen lässt.

Anders als viele andere B2B-Features ist eine Requisition List nicht an eine Company gebunden, sondern an den einzelnen Kunden. Zwei Mitarbeiter derselben Firma sehen also standardmäßig nicht dieselben Listen, auch wenn sie über dieselbe Company verbunden sind, was für viele Projekte eine relevante Design-Entscheidung ist, sobald es um geteilte Bestellvorlagen im Team geht.

2. Das Datenmodell: requisition_list und requisition_list_item

Technisch besteht eine Requisition List aus zwei Tabellen: requisition_list mit den Feldern requisition_list_id, customer_id, name und description, sowie requisition_list_item mit sku, qty, requisition_list_id als Fremdschlüssel und einem serialisierten additional_options-Feld für Produktoptionen wie konfigurierbare Attribute oder individuelle Textfelder. Die eigentliche Produktreferenz erfolgt über die SKU, nicht über die interne Produkt-ID, was bei SKU-Änderungen im Katalog zu beachten ist.

Weil additional_options serialisiert gespeichert wird, lässt sich eine gespeicherte Position nicht einfach per einfachem SQL-Filter nach einer bestimmten Produktoption durchsuchen. Für eigene Auswertungen, etwa ein Report über die am häufigsten in Listen gespeicherten Konfigurationen, muss man die serialisierten Daten zunächst über die entsprechenden Serializer-Klassen deserialisieren, bevor man sie strukturiert auswerten kann.

3. Requisition List vs. Wishlist: der fachliche Unterschied

Auf den ersten Blick wirken Requisition Lists und Wishlists ähnlich, beide speichern eine benannte Liste von Produkten für einen Kunden. Der zentrale Unterschied liegt im Zweck: Eine Wishlist ist auf Merken und gegebenenfalls Teilen ausgelegt, mit optionalen Kommentaren und einer Sharing-Funktion über einen öffentlichen Link. Eine Requisition List hat dagegen einen rein operativen Zweck: schnelles, wiederholtes Bestellen mit fest hinterlegter Menge pro Position.

Praktisch zeigt sich das in den verfügbaren Aktionen. Requisition Lists erlauben das direkte Hinzufügen der gesamten Liste zum Warenkorb inklusive Mengenanpassung in einem Schritt, während eine Wishlist eher für das einzelne Hinzufügen einzelner Produkte gedacht ist. Zudem unterstützt eine Requisition List von Haus aus keine öffentliche Sichtbarkeit oder Freigabe an andere Nutzer, sie bleibt strikt an den anlegenden Kunden gebunden.

4. API-Zugriff für eigene Frontend-Integrationen

Für Custom-Frontends stehen sowohl Service Contracts wie RequisitionListRepositoryInterface und RequisitionListItemRepositoryInterface als auch eine GraphQL-Schicht zur Verfügung. Über die GraphQL-Query requisitionLists lassen sich die Listen eines eingeloggten Kunden inklusive ihrer Positionen abrufen, während Mutations wie createRequisitionList und addProductsToRequisitionList das Anlegen und Befüllen übernehmen.

Für das eigentliche Bestellen ist die Mutation addRequisitionListToCart relevant, die die komplette Liste oder eine ausgewählte Teilmenge an Positionen validiert und in den aktiven Warenkorb überführt. Die Validierung läuft dabei pro Position getrennt, sodass eine einzelne fehlerhafte Position, etwa ein inzwischen deaktiviertes Produkt, nicht zwangsläufig die gesamte Aktion blockiert, sondern als einzelner Fehler im Ergebnis zurückgemeldet wird.


mutation AddRequisitionListToCart {
  addRequisitionListToCart(
    cartId: "abc123cartid"
    requisitionListId: "42"
    requisitionListItemIds: ["101", "102", "103"]
  ) {
    cart {
      id
      total_quantity
    }
    userErrors {
      requisition_list_item_id
      message
      code
    }
  }
}

5. Eigene Mengenregeln per Plugin ergänzen

Ein häufiger Erweiterungsfall ist eine zusätzliche Mengenvalidierung, etwa wenn ein Produkt nur in Vielfachen einer Verpackungseinheit bestellt werden darf. Dafür bietet sich ein Plugin auf RequisitionListItemRepositoryInterface::save an, das vor dem eigentlichen Speichern prüft, ob die eingetragene Menge zur hinterlegten Packungsgröße des Produkts passt, und andernfalls eine LocalizedException wirft.

Diese Prüfung sollte konsequent an derselben Stelle greifen wie später beim Hinzufügen zum Warenkorb, da sonst ungültige Mengen zwar nicht in der Liste gespeichert, aber möglicherweise über einen anderen Pfad wie den direkten Produkt-Detailseiten-Zugriff dennoch bestellt werden könnten. Eine konsistente Regel gehört daher eher in einen zentralen, wiederverwendbaren Validator als in ein einzelnes Plugin.


<?php

declare(strict_types=1);

namespace Mironsoft\RequisitionListExtension\Plugin;

use Magento\Framework\Exception\LocalizedException;
use Magento\RequisitionList\Api\Data\RequisitionListItemInterface;
use Magento\RequisitionList\Api\RequisitionListItemRepositoryInterface;
use Mironsoft\RequisitionListExtension\Model\PackSizeValidator;

/**
 * Stellt sicher, dass Mengen in Requisition-List-Positionen ein Vielfaches
 * der hinterlegten Packungsgröße des Produkts sind.
 */
class ValidatePackSizePlugin
{
    /**
     * @param PackSizeValidator $packSizeValidator
     */
    public function __construct(private readonly PackSizeValidator $packSizeValidator)
    {
    }

    /**
     * @param RequisitionListItemRepositoryInterface $subject
     * @param RequisitionListItemInterface $item
     * @return array{RequisitionListItemInterface}
     * @throws LocalizedException
     */
    public function beforeSave(
        RequisitionListItemRepositoryInterface $subject,
        RequisitionListItemInterface $item,
    ): array {
        if (!$this->packSizeValidator->isValidQty($item->getSku(), (float) $item->getQty())) {
            throw new LocalizedException(__('Die Menge muss ein Vielfaches der Packungsgröße sein.'));
        }

        return [$item];
    }
}

6. Typische Anwendungsfälle für Stammkunden

In der Praxis lohnen sich Requisition Lists vor allem dort, wo Bestellungen regelmäßig und mit hoher Vorhersehbarkeit anfallen. Ein klassisches Beispiel sind Wartungsteams, die monatlich dieselben Ersatzteile für eine bestimmte Maschine bestellen, oder Filialen, die eine standardisierte Grundausstattung an Verbrauchsmaterial in festgelegten Mengen regelmäßig nachbestellen, ohne jedes Mal die passenden Produkte neu zusammensuchen zu müssen.

Ein weiterer verbreiteter Anwendungsfall ist die schnelle Wiederholung einer früheren Bestellung: Statt eine vergangene Order manuell zu duplizieren, legt der Kunde die relevanten Positionen einmalig als Requisition List an und passt bei jeder weiteren Bestellung nur noch die Mengen an, was den Bestellprozess spürbar beschleunigt und Tippfehler bei SKUs reduziert, die bei manueller Suche im Katalog leichter passieren.

7. Berechtigungen: kein Team-Sharing ab Werk

Eine wichtige Einschränkung, die bei der Planung eigener Erweiterungen oft übersehen wird: Requisition Lists sind im Standardfunktionsumfang strikt an den anlegenden Kunden gebunden und nicht automatisch für andere Mitglieder derselben Company sichtbar. Wer eine geteilte Bestellvorlage im Team möchte, muss diese Funktionalität explizit ergänzen, etwa über ein zusätzliches Attribut für Sichtbarkeit auf Company-Ebene und eine entsprechend erweiterte Repository-Abfrage.

Für eine solche Erweiterung bietet sich an, die bestehende Tabelle requisition_list um eine Spalte für die Sichtbarkeit zu ergänzen und die Standard-Collection über ein Plugin um einen zusätzlichen Filter zu ergänzen, statt die komplette Repository-Implementierung zu ersetzen. So bleiben Kompatibilität mit zukünftigen Magento-Updates und die vorhandene GraphQL-Schicht weitgehend erhalten.

8. Performance bei großen Listen

Bei Listen mit mehreren hundert Positionen wirkt sich die Validierung beim Hinzufügen zum Warenkorb spürbar auf die Antwortzeit aus, weil für jede Position Preis, Verfügbarkeit und Konfigurationsoptionen einzeln geprüft werden. Für sehr große Listen empfiehlt sich, die addRequisitionListToCart-Operation nicht mit der kompletten Liste auf einmal, sondern in kleineren Chargen aufzurufen, um sowohl die Antwortzeit als auch das Risiko eines Timeouts bei langsamen externen Preisquellen zu begrenzen.

Für wiederkehrende, planbare Nutzung, etwa eine automatisierte nächtliche Übernahme bestimmter Requisition Lists in Bestellungen über ein externes Beschaffungssystem, ist außerdem sinnvoll, denselben Validierungspfad wie im Frontend zu verwenden, statt eine eigene, abweichende Logik zu implementieren, damit sich Frontend- und Automatisierungsverhalten nicht auseinanderentwickeln.

9. Pflege veralteter Listen

Da Requisition Lists dauerhaft gespeichert bleiben und nicht automatisch ablaufen, sammeln sich über Jahre hinweg Listen an, die Positionen mit inzwischen deaktivierten oder aus dem Sortiment genommenen Produkten enthalten. Ohne eigene Pflege bleiben solche verwaisten Einträge unbegrenzt bestehen und führen beim Versuch, die Liste zum Warenkorb hinzuzufügen, zu Validierungsfehlern, die für den Kunden ohne Kontext schwer nachvollziehbar sind.

Eine sinnvolle Ergänzung ist ein regelmäßiger Batch-Job, der Positionen mit nicht mehr existierenden oder dauerhaft deaktivierten SKUs identifiziert und entweder automatisch entfernt oder dem Kunden beim nächsten Login als veraltete Position markiert, damit die Liste ihren praktischen Nutzen als verlässliche Bestellvorlage behält.

Merkmal Wishlist Requisition List
Primärer Zweck Merken und Teilen Wiederholtes, schnelles Bestellen
Team-Sharing Öffentlicher Link möglich Nicht ohne eigene Erweiterung
Menge pro Position Meist 1, editierbar Fest hinterlegt, für Wiederbestellung gedacht
Kompletter Warenkorb-Übernahme Nicht standardmäßig vorgesehen Über addRequisitionListToCart
Bindung An den Kunden An den Kunden, nicht an die Company
Typischer Nutzer Endkunde im B2C-Kontext Firmenkunde mit wiederkehrendem Bedarf

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

Requisition Lists

Datenmodell

requisition_list und requisition_list_item speichern Name, Kunde, SKU, Menge und serialisierte Produktoptionen.

Abgrenzung

Anders als die Wishlist ist die Requisition List auf schnelles Wiederbestellen statt auf Merken und Teilen ausgelegt.

API

GraphQL-Mutations wie addRequisitionListToCart validieren jede Position einzeln und melden Fehler granular zurück.

Grenzen

Kein Team-Sharing ab Werk, keine automatische Bereinigung veralteter Positionen ohne eigene Erweiterung.

11. FAQ: Requisition Lists

1Woraus besteht das Datenmodell einer Requisition List?
Aus den Tabellen requisition_list mit Name, Kunde und Beschreibung sowie requisition_list_item mit SKU, Menge und serialisierten Produktoptionen pro Position.
2Können mehrere Mitarbeiter derselben Company dieselbe Requisition List sehen?
Nicht im Standardfunktionsumfang. Requisition Lists sind an den einzelnen Kunden gebunden, nicht an die Company, ein Team-Sharing muss eigens ergänzt werden.
3Was unterscheidet eine Requisition List fachlich von einer Wishlist?
Die Wishlist ist auf Merken und Teilen ausgelegt, die Requisition List auf schnelles, wiederholtes Bestellen mit fest hinterlegten Mengen pro Position.
4Wie lässt sich eine ganze Liste zum Warenkorb hinzufügen?
Über die GraphQL-Mutation addRequisitionListToCart, die die gesamte Liste oder eine ausgewählte Teilmenge an Positionen validiert und übernimmt.
5Was passiert, wenn eine Position in der Liste nicht mehr bestellbar ist?
Die Validierung läuft pro Position getrennt, sodass eine fehlerhafte Position als einzelner Fehler zurückgemeldet wird, statt die gesamte Aktion zu blockieren.
6Wie lassen sich eigene Mengenregeln, etwa Packungsgrößen, durchsetzen?
Über ein Plugin auf RequisitionListItemRepositoryInterface::save, das die Menge vor dem Speichern gegen die hinterlegte Packungsgröße prüft und bei Verstoß eine LocalizedException wirft.
7Für welche Anwendungsfälle eignen sich Requisition Lists besonders?
Für wiederkehrende Bestellungen mit hoher Vorhersehbarkeit, etwa regelmäßige Ersatzteilbestellungen oder standardisierte Nachbestellungen von Verbrauchsmaterial.
8Werden veraltete Positionen automatisch entfernt?
Nein, Requisition Lists laufen nicht automatisch ab. Eine eigene Bereinigungsroutine für Positionen mit deaktivierten SKUs ist empfehlenswert.
9Wie wirken sich sehr große Listen auf die Performance aus?
Die Validierung beim Hinzufügen zum Warenkorb prüft jede Position einzeln, weshalb sich bei mehreren hundert Positionen eine Aufteilung in kleinere Chargen empfiehlt.
10Erfolgt die Produktreferenz über die interne ID oder die SKU?
Über die SKU. Das ist bei SKU-Änderungen im Katalog zu beachten, weil sich gespeicherte Positionen sonst nicht mehr korrekt auflösen lassen.