Negotiable Quotes in Magento 2: Der B2B-Verhandlungs-Workflow im Detail
AI generated
M2
di.xml
Magento 2 · B2B Suite
Negotiable Quotes
Der B2B-Verhandlungs-Workflow von der Anfrage bis zum Auftrag

Negotiable Quotes verwandeln den Magento-Warenkorb in einen mehrstufigen Verhandlungsprozess zwischen Firmenkunde und Vertrieb, mit eigener Statusmaschine, eigenen Berechtigungen und eigenem Datenmodell. Wer das Feature erweitern will, muss verstehen, wie ein Quote durch NegotiableQuoteManagement wandert und wo sich eigene Statusübergänge und Benachrichtigungen sauber einhängen lassen.

13 Min. Lesezeit Negotiable Quotes · B2B Suite Magento 2.4.x Commerce

1. Negotiable Quotes im B2B-Modul einordnen

Negotiable Quotes ist Teil der B2B-Suite von Adobe Commerce und erweitert den regulären Warenkorb um einen mehrstufigen Verhandlungsprozess zwischen Firmenkunde und Vertrieb. Technisch baut das Feature auf der bestehenden Quote-Entity auf, ergänzt sie aber um eine eigene Tabelle für Verhandlungsdaten, sodass ein Angebot nicht nur ein Warenkorb-Snapshot ist, sondern ein Zustand mit eigener Historie und eigenem Lebenszyklus.

Sichtbar wird das Feature erst über die Company-Struktur: Nur Kunden mit aktiver Zuordnung zu einer Company und der passenden Berechtigung können eine Anfrage als Negotiable Quote einreichen. Wer eigene Erweiterungen für den Verhandlungsprozess bauen will, kommt an drei zentralen Bausteinen nicht vorbei: dem Datenmodell rund um die Tabelle negotiable_quote, der Statusmaschine in Magento\NegotiableQuote\Model\Quote\Status und dem Service Contract NegotiableQuoteManagementInterface, über den nahezu jede Statusänderung läuft.

Ein wichtiger Unterschied zum normalen Checkout: Der Kunde bestellt bei einem Negotiable Quote nicht direkt, sondern reicht eine Anfrage ein, die intern verändert werden kann, bevor überhaupt eine Bestellung entsteht. Diese Zwischenstufe ist der eigentliche Kern des Features und der Grund, warum es ein eigenes Datenmodell statt einer einfachen Erweiterung des Checkouts braucht.

2. Das Datenmodell hinter der Verhandlung

Ein Negotiable Quote ist kein eigenständiges Entity im klassischen Sinn, sondern eine Erweiterung der Sales-Quote um die Tabelle negotiable_quote. Diese Tabelle hält quote_id als Fremdschlüssel sowie verhandlungsspezifische Felder wie status, negotiated_price_type, negotiated_price_value, shipping_price, expiration_period und quote_name. Der eigentliche Warenkorb mit seinen Positionen bleibt vollständig in quote und quote_item, nur die Verhandlungsmetadaten liegen separat.

Diese Trennung hat einen praktischen Grund: Preisüberschreibungen auf Zeilenebene werden nicht in negotiable_quote gespeichert, sondern über zusätzliche Spalten an quote_item angehängt, etwa original_custom_price_amount und original_discount_amount, damit der ursprüngliche Katalogpreis neben dem verhandelten Preis erhalten bleibt. Wer eigene Reports oder Exporte über verhandelte Preise bauen will, muss beide Tabellen gemeinsam auswerten, nicht nur negotiable_quote isoliert betrachten.

3. Die Statusmaschine: Von created bis ordered

Der Kern des Verhandlungs-Workflows ist eine klar definierte Statusmaschine, deren Konstanten in Magento\NegotiableQuote\Model\Quote\Status liegen. Ein neuer Quote startet im Status created, sobald ein Firmenkunde eine Anfrage einreicht wechselt er zu submitted_by_customer, und sobald der Vertrieb reagiert zu processing_by_admin oder submitted_by_admin. Am Ende steht entweder ordered, wenn der Kunde das finale Angebot annimmt, oder declined beziehungsweise expired, wenn keine Einigung zustande kommt.

Wichtig für eigene Erweiterungen ist, dass nicht jeder Status von jedem anderen aus erreichbar ist. Die erlaubten Übergänge sind in der Verwaltungslogik fest verdrahtet, und ein Versuch, einen Status zu setzen, der von der aktuellen Position aus nicht erlaubt ist, führt zu einer LocalizedException. Eigene Automatisierungen, etwa ein automatischer Statuswechsel nach Ablauf einer Frist, müssen sich also an dieselben Übergangsregeln halten wie die Standardoberfläche.


<?php

declare(strict_types=1);

namespace Mironsoft\NegotiableQuoteExtension\Model;

use Magento\NegotiableQuote\Model\Quote\Status;

/**
 * Ermittelt die aus dem aktuellen Verhandlungsstatus heraus erlaubten Folgezustände.
 */
class AllowedTransitions
{
    private const TRANSITIONS = [
        Status::STATUS_CREATED => [Status::STATUS_SUBMITTED_BY_CUSTOMER],
        Status::STATUS_SUBMITTED_BY_CUSTOMER => [
            Status::STATUS_PROCESSING_BY_ADMIN,
            Status::STATUS_DECLINED,
        ],
        Status::STATUS_PROCESSING_BY_ADMIN => [
            Status::STATUS_SUBMITTED_BY_ADMIN,
            Status::STATUS_DECLINED,
        ],
        Status::STATUS_SUBMITTED_BY_ADMIN => [
            Status::STATUS_ORDERED,
            Status::STATUS_SUBMITTED_BY_CUSTOMER,
            Status::STATUS_EXPIRED,
        ],
    ];

    /**
     * Liefert die aus dem übergebenen Status heraus erlaubten Folgezustände.
     *
     * @param string $currentStatus
     * @return string[]
     */
    public function getAllowedNextStatuses(string $currentStatus): array
    {
        return self::TRANSITIONS[$currentStatus] ?? [];
    }
}

4. Rollen und Berechtigungen im Verhandlungsprozess

Wer welchen Statuswechsel auslösen darf, hängt von zwei Berechtigungsebenen ab: der klassischen Magento-ACL auf Adminseite und den Company-Rollen auf Kundenseite. Auf Adminseite steuert die Ressource Magento_NegotiableQuote::manage, ob ein Vertriebsmitarbeiter überhaupt Zugriff auf die Negotiable-Quotes-Verwaltung hat, während feinere Ressourcen einzelne Aktionen wie das Setzen eines finalen Preises absichern.

Auf Kundenseite bestimmt die Company-Rolle, ob ein Firmenmitglied überhaupt einen Negotiable Quote anlegen, kommentieren oder final bestellen darf. Ein reiner Einkäufer kann in vielen Setups Anfragen stellen, aber nicht die finale Bestellung auslösen, das bleibt einem genehmigenden Approver vorbehalten. Diese Trennung folgt demselben Berechtigungsmodell wie die allgemeine Company-Struktur, wird für Negotiable Quotes aber um eine eigene Ressource ergänzt, die getrennt von den allgemeinen Bestellrechten vergeben wird.

5. Verhandlungen über die GraphQL-API steuern

Für Storefront-Integrationen jenseits der Standardoberfläche stellt das Modul Magento_NegotiableQuoteGraphQl eigene Mutations bereit. Mit createNegotiableQuote lässt sich aus einem bestehenden Warenkorb ein neuer Verhandlungsprozess starten, updateNegotiableQuote passt Kommentare, Preise oder den Status an, und die Query negotiableQuote liefert den aktuellen Verhandlungsstand samt Historie zurück.

Für eigene Frontend-Erweiterungen, etwa ein individuelles Verhandlungs-Dashboard, ist wichtig, dass die GraphQL-Schicht dieselben Berechtigungsprüfungen durchläuft wie die Storefront-Oberfläche. Ein Kunde ohne die passende Company-Rolle bekommt bei einem Statuswechsel-Versuch über die API denselben Autorisierungsfehler wie im UI, was eigene Custom-Frontends vor inkonsistentem Verhalten schützt, aber auch bedeutet, dass Rollenprüfungen nicht auf Frontend-Seite dupliziert werden müssen.


mutation UpdateNegotiableQuoteStatus {
  updateNegotiableQuote(
    input: {
      quote_uid: "MTIz"
      comment: { comment: "Rabatt auf 12% angepasst, Freigabe erbeten." }
      status: SUBMITTED_BY_ADMIN
    }
  ) {
    quote {
      uid
      name
      status
      negotiated_price {
        value
      }
    }
  }
}

6. Eigene Statusübergänge per Plugin ergänzen

Um zusätzliche Logik bei einem Statuswechsel auszuführen, etwa eine interne Freigabe ab einem bestimmten Rabattschwellenwert, bietet sich ein Plugin auf NegotiableQuoteManagementInterface an. Ein around-Plugin kann vor dem eigentlichen Statuswechsel prüfen, ob die Bedingungen für den Übergang erfüllt sind, und bei Bedarf eine eigene Exception werfen, die den Wechsel blockiert, bevor die Standardlogik überhaupt greift.

Wichtig ist, dass ein solches Plugin die ursprüngliche Methode weiterhin sauber aufruft, wenn die eigene Prüfung erfolgreich war, damit die Kernlogik von Magento intakt bleibt und spätere Updates des B2B-Moduls nicht durch übersprungene Schritte kollidieren. Für rein beobachtende Zwecke, etwa Logging, ist ein after-Plugin die robustere Wahl, weil es die Kernlogik nicht verändern kann.


<!-- app/code/Mironsoft/NegotiableQuoteExtension/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\NegotiableQuote\Api\NegotiableQuoteManagementInterface">
        <plugin name="mironsoft_approval_threshold"
                type="Mironsoft\NegotiableQuoteExtension\Plugin\ApprovalThresholdPlugin"
                sortOrder="10"/>
    </type>
</config>

7. Eigene Benachrichtigungen bei Statuswechseln

Die Standardbenachrichtigungen von Negotiable Quotes decken die üblichen Fälle ab, etwa eine E-Mail an den Kunden, wenn der Vertrieb ein Gegenangebot einreicht. Für projektspezifische Kanäle, etwa eine Nachricht an das Vertriebsteam über ein internes Tool oder einen Eintrag in ein CRM, reicht die Standardlogik nicht aus und lässt sich am saubersten über ein after-Plugin auf derselben Management-Methode nachrüsten.

Der Vorteil eines Plugins gegenüber einem eigenen Observer ist, dass der neue und der alte Status im selben Methodenaufruf bekannt sind, ohne dass man den vorherigen Zustand separat aus der Datenbank nachladen muss. Für asynchrone Zustellung, etwa wenn der externe Dienst gelegentlich nicht erreichbar ist, empfiehlt es sich, die eigentliche Benachrichtigung über eine Message Queue zu entkoppeln, statt sie synchron im Plugin auszuführen und damit den kompletten Statuswechsel zu verzögern.

8. Preisgrenzen und Verhandlungsspielraum absichern

Ohne zusätzliche Absicherung kann ein Vertriebsmitarbeiter im Prinzip jeden beliebigen Preis in ein Gegenangebot eintragen, was in größeren Teams schnell zu inkonsistenten Rabatten führt. Eine gängige Erweiterung ist eine eigene Validierung, die den negotiated_price_value gegen eine hinterlegte Untergrenze prüft, bevor der Statuswechsel zu submitted_by_admin überhaupt zugelassen wird.

Diese Prüfung lässt sich sauber in dasselbe around-Plugin integrieren, das bereits die Statusübergänge absichert, sollte fachlich aber getrennt bleiben: Eine Verletzung der Preisgrenze ist ein anderer Fehlerfall als ein ungültiger Statusübergang und verdient eine eigene, klar formulierte Fehlermeldung, damit der Vertrieb sofort erkennt, woran die Freigabe gescheitert ist, statt eine generische Exception zu erhalten.

9. Betrieb: Ablauf, Cron und Datenmengen

Negotiable Quotes, die nicht innerhalb der konfigurierten Frist beantwortet werden, laufen automatisch in den Status expired, gesteuert über einen Cron-Job, der abgelaufene Quotes regelmäßig durchsucht. Bei Shops mit vielen parallelen Verhandlungen lohnt sich ein Blick auf die Indizierung der negotiable_quote-Tabelle, insbesondere auf status und expiration_period, damit der Cron-Lauf auch bei wachsender Historie performant bleibt.

Da abgelehnte und abgelaufene Quotes standardmäßig nicht automatisch gelöscht werden, wächst die Tabelle über die Zeit kontinuierlich. Für Shops mit hohem Verhandlungsvolumen empfiehlt sich eine eigene Aufräumroutine, die alte, abgeschlossene Verhandlungen nach einer definierten Aufbewahrungsfrist archiviert oder entfernt, statt sie unbegrenzt in der produktiven Tabelle zu belassen und damit spätere Reports und Admin-Grids unnötig zu verlangsamen.

Status Ausgelöst durch Typische Aktion Mögliche Folgezustände
created Kunde legt Anfrage an Warenkorb in Verhandlung umwandeln submitted_by_customer
submitted_by_customer Kunde reicht Anfrage ein Vertrieb prüft Anfrage processing_by_admin, declined
processing_by_admin Vertrieb bearbeitet Angebot Preise und Konditionen anpassen submitted_by_admin, declined
submitted_by_admin Vertrieb sendet Gegenangebot Kunde prüft Angebot ordered, submitted_by_customer, expired
ordered Kunde bestätigt Angebot Bestellung wird erzeugt Endzustand
expired Frist ohne Reaktion abgelaufen Verhandlung automatisch schließen Endzustand

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

Negotiable Quotes Workflow

Datenmodell

negotiable_quote erweitert die Sales-Quote um Verhandlungsmetadaten, Preisüberschreibungen liegen zusätzlich an quote_item.

Statusmaschine

Feste Übergänge von created über submitted_by_customer und submitted_by_admin bis ordered, declined oder expired.

Erweiterbarkeit

Plugins auf NegotiableQuoteManagementInterface für eigene Freigaben, Preisgrenzen und Benachrichtigungen.

Betrieb

Cron schließt abgelaufene Quotes automatisch, eine eigene Aufräumroutine für alte Historie ist empfehlenswert.

11. FAQ: Negotiable Quotes Workflow

1Was unterscheidet einen Negotiable Quote von einem normalen Warenkorb?
Ein Negotiable Quote führt keine direkte Bestellung aus, sondern startet einen mehrstufigen Verhandlungsprozess mit eigener Statusmaschine, in dem Kunde und Vertrieb Preise und Konditionen abgleichen, bevor überhaupt eine Bestellung entsteht.
2Welche Tabelle speichert die Verhandlungsdaten?
Die Tabelle negotiable_quote hält die Verhandlungsmetadaten zu einer Quote, während Preisüberschreibungen auf Zeilenebene über zusätzliche Spalten an quote_item gespeichert werden.
3Kann jeder Firmenkunde einen Negotiable Quote anlegen?
Nein, das setzt eine aktive Zuordnung zu einer Company sowie eine passende Company-Rolle mit der entsprechenden Berechtigung voraus, sonst ist die Funktion im Frontend gar nicht sichtbar.
4Wie lässt sich ein zusätzlicher Freigabeschritt vor dem Statuswechsel einbauen?
Am saubersten über ein around-Plugin auf NegotiableQuoteManagementInterface, das vor dem eigentlichen Statuswechsel eigene Bedingungen prüft und bei Verstoß eine eigene Exception wirft.
5Was passiert, wenn eine Verhandlungsfrist ohne Reaktion abläuft?
Ein Cron-Job durchsucht regelmäßig abgelaufene Quotes und setzt sie automatisch auf den Status expired, ohne dass ein manueller Eingriff nötig ist.
6Lässt sich der Verhandlungsprozess über GraphQL steuern?
Ja, das Modul Magento_NegotiableQuoteGraphQl stellt Mutations wie createNegotiableQuote und updateNegotiableQuote sowie die Query negotiableQuote für eigene Frontend-Integrationen bereit.
7Wie kann man eigene Benachrichtigungskanäle bei Statuswechseln ergänzen?
Über ein after-Plugin auf derselben Management-Methode, das den alten und neuen Status kennt, idealerweise mit asynchroner Zustellung über eine Message Queue für externe Dienste.
8Werden abgelehnte oder abgelaufene Quotes automatisch gelöscht?
Nein, sie bleiben standardmäßig in der Tabelle erhalten. Bei hohem Verhandlungsvolumen empfiehlt sich eine eigene Aufräumroutine mit definierter Aufbewahrungsfrist.
9Wie verhindert man unrealistisch niedrige Preise in einem Gegenangebot?
Über eine eigene Validierung im selben Plugin, die den negotiated_price_value gegen eine hinterlegte Untergrenze prüft, bevor der Statuswechsel zu submitted_by_admin zugelassen wird.
10Ist jeder Statusübergang von jedem beliebigen Status aus möglich?
Nein, die erlaubten Übergänge sind fest definiert. Ein ungültiger Statuswechsel führt zu einer LocalizedException, an die sich auch eigene Automatisierungen halten müssen.