B2B-Features im Hyvä-Theme umsetzen
AI generated
Hyvä
phtml
Magento B2B · Hyvä-Theme · Alpine.js
B2B-Features im Hyvä-Theme umsetzen
Company Accounts, Freigaben und Requisition Lists ohne Knockout

Magento B2B liefert Company-Verwaltung, Freigabe-Workflows, Negotiable Quotes und Requisition Lists standardmäßig als Luma-Komponenten mit Knockout.js und UI-Components aus, die in einem Hyvä-Theme nicht funktionieren, weil Hyvä genau diese Schicht entfernt. Wer B2B-Funktionen im Frontend trotzdem sauber abbilden will, braucht ViewModels für den Company-Kontext, Alpine.js für interaktive Zustände und gezielte Layout-XML-Overrides der Luma-Templates.

18 Min. Lesezeit Company Accounts · Negotiable Quotes · Requisition Lists · Approval Workflows Magento 2.4.x B2B · Hyvä Themes

1. B2B-Features im Hyvä-Theme: Ausgangslage

Magento B2B (Commerce) bringt Company Accounts, Freigabe-Workflows für Bestellungen, Negotiable Quotes, Requisition Lists, Shared Catalogs und die Zuordnung von Sales Representatives mit. Alle diese Funktionen sind im Standard als Luma-Module gebaut: Grids und Formulare für die Company-Verwaltung laufen über UI-Components, der Freigabestatus einer Bestellung wird per Knockout.js-Binding gerendert, die Requisition-List-Verwaltung nutzt ui/grid-Komponenten mit XML-Deklaration. Genau diese Schicht entfernt Hyvä bewusst und vollständig, weil UI-Components und Knockout.js in Hyvä-Themes weder geladen noch initialisiert werden. Der Effekt: Ein Hyvä-Theme mit aktiviertem B2B-Modul zeigt in vielen Bereichen leere Container, fehlende Bindings oder JavaScript-Fehler in der Konsole, weil die referenzierten Knockout-Templates schlicht nicht existieren.

Für B2B-Features im Hyvä-Theme bedeutet das: Die zugrunde liegenden PHP-Service-Contracts, Repositories und GraphQL-Endpunkte des B2B-Moduls bleiben nutzbar und stabil, nur die Präsentationsschicht muss komplett neu gebaut werden. Das ist kein Nachteil, sondern folgt exakt dem Hyvä-Prinzip: Business-Logik bleibt in PHP und Service Contracts, das Rendering wandert in phtml-Templates mit Alpine.js. Wer diesen Umbau plant, sollte von Anfang an trennen zwischen Daten, die serverseitig per ViewModel geladen werden, und Zuständen, die client-seitig mit Alpine reagieren müssen, etwa das Ein- und Ausklappen einer Freigabe-Historie.

In der Praxis zeigt sich, dass die Umsetzung von Hyvä B2B-Features pro Anwendungsfall unterschiedlich aufwendig ist. Ein einfacher Company-Switcher lässt sich mit einer ViewModel-Methode und etwas Alpine-State in wenigen Stunden bauen. Negotiable Quotes mit ihrem mehrstufigen Verhandlungsprozess erfordern hingegen eine saubere Analyse des ursprünglichen Knockout-View-Models, um jeden Zustand korrekt in phtml und Alpine zu übersetzen.

2. ViewModels für den Company-Kontext

Der erste Baustein jeder Umsetzung ist ein ViewModel, das den aktuell eingeloggten Company-User erkennt und seinen Kontext bereitstellt. Statt Block-Klassen mit Geschäftslogik zu überladen, injiziert man Magento\Company\Api\CompanyManagementInterface und Magento\Customer\Api\CustomerRepositoryInterface in ein ViewModel, das ArgumentInterface implementiert. Die Company-Zugehörigkeit eines Kunden steckt nicht direkt im Customer-Objekt, sondern in dessen Extension Attributes, konkret in getExtensionAttributes()->getCompanyAttributes(). Von dort lässt sich die Company-ID auflösen und über CompanyManagementInterface::getById() das vollständige Company-Objekt laden, inklusive Struktur, Rollen und zugeordnetem Sales Representative.

Dieses ViewModel wird per Layout-XML als Argument an genau die Blöcke gebunden, die Company-Informationen im Template benötigen, etwa den Header-Switcher oder die Freigabe-Banner auf der Bestellübersicht. So bleibt B2B im Hyvä-Theme konsistent mit dem restlichen Theme-Aufbau: keine Block-Klassen mit Fachlogik, sondern schlanke ViewModels, die im phtml einfach aufgerufen werden. Für Performance-kritische Seiten wie die Produktliste lohnt es sich, das ViewModel mit einem einfachen In-Memory-Cache pro Request zu versehen, damit die Company-Auflösung nicht bei jedem Blockaufruf erneut über die Repository-Schicht läuft.


<?php

declare(strict_types=1);

namespace Mironsoft\B2bTheme\ViewModel;

use Magento\Company\Api\CompanyManagementInterface;
use Magento\Company\Api\Data\CompanyInterface;
use Magento\Company\Model\Company;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\Session as CustomerSession;
use Magento\Framework\Authorization;
use Magento\Framework\View\Element\Block\ArgumentInterface;

/**
 * Supplies company context and permission flags to phtml templates instead of Knockout view models.
 */
class CompanyContextViewModel implements ArgumentInterface
{
    /**
     * @param CompanyManagementInterface $companyManagement Loads the full company entity by id
     * @param CustomerRepositoryInterface $customerRepository Reads the customer and its extension attributes
     * @param CustomerSession $customerSession Provides the currently logged in customer id
     * @param Authorization $authorization Evaluates Company::PERMISSION_* resources for the session
     */
    public function __construct(
        private readonly CompanyManagementInterface $companyManagement,
        private readonly CustomerRepositoryInterface $customerRepository,
        private readonly CustomerSession $customerSession,
        private readonly Authorization $authorization,
    ) {
    }

    /**
     * Returns the company entity for the current customer, or null for B2C guests.
     *
     * @return CompanyInterface|null
     */
    public function getCurrentCompany(): ?CompanyInterface
    {
        if (!$this->customerSession->isLoggedIn()) {
            return null;
        }

        $customer = $this->customerRepository->getById(
            (int) $this->customerSession->getCustomerId()
        );

        // Company data lives in extension attributes, never on the customer entity itself
        $companyAttributes = $customer->getExtensionAttributes()?->getCompanyAttributes();

        if ($companyAttributes === null || !$companyAttributes->getCompanyId()) {
            return null;
        }

        return $this->companyManagement->getById((int) $companyAttributes->getCompanyId());
    }

    /**
     * Checks whether the current customer may approve pending orders for the company.
     *
     * @return bool
     */
    public function canApproveOrders(): bool
    {
        return $this->getCurrentCompany() !== null
            && $this->authorization->isAllowed(Company::PERMISSION_ORDER_ALL);
    }
}

3. Alpine.js statt Knockout für Freigabe-Workflows

Der Freigabe-Workflow für Bestellungen ist der Bereich, in dem der Unterschied zwischen Luma und Hyvä B2B-Features am deutlichsten sichtbar wird. Im Luma-Standard bindet ein Knockout-View-Model den Freigabestatus reaktiv an ein Template, inklusive Observable-Ketten für Genehmiger, Historie und Kommentare. In Hyvä übernimmt Alpine.js diese Rolle, allerdings mit einem wichtigen Unterschied: Der initiale Zustand kommt nicht aus einem asynchronen Store, sondern wird direkt serverseitig vom ViewModel in das x-data-Objekt geschrieben. Das reduziert einen kompletten Request-Zyklus, weil der Freigabestatus schon beim ersten Rendern feststeht.

Für Zustände, die sich nach dem initialen Laden ändern können, etwa wenn ein Genehmiger die Bestellung während der Sitzung freigibt, kombiniert man das statische x-data mit einem Alpine.store, der per GraphQL-Mutation aktualisiert wird. Wichtig ist, niemals Knockout-typische Doppelklammer-Syntax in phtml zu verwenden, da Magento-Template-Direktiven dieselbe Zeichenfolge nutzen und es sonst zu Rendering-Konflikten kommt. Alpine löst das sauber über x-text und x-show, ganz ohne Mustache-Syntax.


<?php
/** @var Mironsoft\B2bTheme\ViewModel\CompanyContextViewModel $companyViewModel */
$companyViewModel = $block->getCompanyContext();
/** @var Mironsoft\B2bTheme\ViewModel\OrderApprovalViewModel $approvalViewModel */
$approvalViewModel = $block->getApprovalContext();
?>
<div x-data="{
        isPendingApproval: <?= (int) $approvalViewModel->isPendingApproval() ?> === 1,
        approverName: '<?= $escaper->escapeJs($approvalViewModel->getApproverName()) ?>'
     }"
     x-show="isPendingApproval"
     class="rounded-xl border border-amber-300 bg-amber-50 p-4 mb-6"
>
    <p class="font-semibold text-amber-800">Order pending approval</p>
    <p class="text-sm text-amber-700" x-text="'Waiting for release by ' + approverName"></p>

    <?php if ($companyViewModel->canApproveOrders()): ?>
        <button type="button"
                class="mt-3 bg-amber-600 text-white text-sm font-semibold px-4 py-2 rounded-lg"
                @click="$dispatch('b2b-approve-order')"
        >
            Approve now
        </button>
    <?php endif; ?>
</div>

4. Layout-XML-Overrides der B2B-Templates

Magento B2B deklariert seine Frontend-Blöcke wie jedes andere Modul über Layout-XML, das erleichtert die Integration in Hyvä erheblich, weil man keine Blöcke neu registrieren muss, sondern nur bestehende Referenzen überschreibt. Der zuverlässigste Ansatz ist ein eigenes Modul, etwa Mironsoft_B2bTheme, das per referenceBlock auf die vorhandenen B2B-Blocknamen zugreift und mit setTemplate das Luma/Knockout-Template durch ein eigenes Hyvä-phtml ersetzt. Die zugehörigen ViewModels werden im selben Layout-Handle als Argument injiziert, sodass Block und Template weiterhin lose gekoppelt bleiben.

Für Bereiche, die im Luma-Standard komplett auf UI-Components setzen, etwa die Requisition-List-Übersicht, reicht ein reiner Template-Tausch oft nicht aus, weil der komplette Block-Baum auf Grid-Komponenten aufbaut. Hier ist es sauberer, den ursprünglichen Block per remove="true" zu entfernen und einen neuen, schlanken Block mit eigenem Hyvä-Template im gleichen Container zu platzieren. Diese Vorgehensweise für B2B-Features im Hyvä-Theme hält sich strikt an die deklarative Natur von Magento und vermeidet Hardcoding im PHP-Code.


<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="negotiable_quote_view">
            <!-- Replace the Luma/Knockout template with an Alpine.js based Hyvä template -->
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Mironsoft_B2bTheme::quote/view.phtml</argument>
            </action>
            <arguments>
                <argument name="company_context" xsi:type="object">Mironsoft\B2bTheme\ViewModel\CompanyContextViewModel</argument>
                <argument name="quote_context" xsi:type="object">Mironsoft\B2bTheme\ViewModel\NegotiableQuoteViewModel</argument>
            </arguments>
        </referenceBlock>

        <!-- Remove the UI-Components grid entirely, it has no Knockout runtime to bind to -->
        <referenceBlock name="requisition-list-view" remove="true" />

        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="mironsoft.requisition.list.view"
                   template="Mironsoft_B2bTheme::requisition/list.phtml">
                <arguments>
                    <argument name="requisition_context" xsi:type="object">Mironsoft\B2bTheme\ViewModel\RequisitionListViewModel</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

5. Requisition Lists im Hyvä-Frontend

Requisition Lists erlauben es B2B-Kunden, wiederkehrende Warenkörbe als Vorlage zu speichern und später mit einem Klick erneut zu bestellen. Im Luma-Standard läuft die komplette Verwaltung, Anlegen, Umbenennen, Artikel hinzufügen, über ein Grid mit UI-Components und AJAX-Aufrufe, die von Knockout-Bindings orchestriert werden. Für B2B-Features im Hyvä-Theme ersetzt man dieses Grid durch eine serverseitig gerenderte Liste, deren Zustand, etwa welche Liste gerade aufgeklappt ist, komplett von Alpine übernommen wird. Die eigentlichen Schreiboperationen, also das Hinzufügen von Artikeln zu einer Liste, laufen über die vorhandenen GraphQL-Mutationen des B2B-Moduls, die unverändert nutzbar bleiben.

Wichtig ist, dass das Requisition-List-ViewModel nicht nur die Liste selbst lädt, sondern auch die Berechtigung des aktuellen Nutzers prüft, denn nicht jeder Company-User darf fremde Requisition Lists sehen oder bearbeiten. Diese Prüfung gehört ins ViewModel und nicht ins Template, damit sie testbar bleibt und nicht versehentlich in mehreren phtml-Dateien dupliziert wird. Erst wenn die Berechtigung im ViewModel feststeht, entscheidet das Template über x-show, ob der "Zur Liste hinzufügen"-Button überhaupt sichtbar ist.

6. Negotiable Quotes: Preisverhandlung abbilden

Negotiable Quotes bilden den komplexesten Anwendungsfall unter den Hyvä B2B-Features, weil ein Angebot mehrere Zustände durchläuft: angefragt, in Verhandlung, vom Händler beantwortet, vom Kunden akzeptiert oder abgelehnt, und schließlich in eine Bestellung umgewandelt. Jeder dieser Zustände hat im Luma-Standard ein eigenes Set an sichtbaren Aktionen, die über Knockout-Computed-Properties gesteuert werden. Bei der Portierung nach Hyvä überträgt man diese Zustandsmaschine in ein ViewModel, das für jeden Quote-Status eine klare, testbare Methode bereitstellt, etwa isAwaitingMerchantResponse() oder canAcceptOffer(), statt eine einzelne generische Statusabfrage zu bauen.

Für die Kommentar- und Verhandlungshistorie, die im Original per Knockout-Liste live nachlädt, reicht in den meisten Fällen serverseitiges Rendering beim Seitenaufruf plus ein einfacher Alpine-Toggle für das Ein- und Ausklappen älterer Nachrichten. Nur wenn der Kunde während der Sitzung tatsächlich eine neue Nachricht sendet, lohnt sich eine gezielte GraphQL-Mutation mit anschließendem Alpine-Update des betroffenen Listenelements, statt die komplette Seite neu zu laden.

7. Permission-Checks als UI-States

Company-Berechtigungen in Magento B2B sind granular: Ein Nutzer kann Bestellungen für sein eigenes Team freigeben dürfen, aber nicht für die gesamte Company, oder Requisition Lists anlegen, aber keine Shared Catalogs einsehen. Diese Berechtigungen werden intern über Magento\Company\Model\Company::PERMISSION_*-Konstanten und die Authorization-Schicht abgebildet. Für B2B im Hyvä-Theme ist entscheidend, dass jede Berechtigungsprüfung genau einmal im ViewModel steht und von dort als einfacher Boolean an das Template durchgereicht wird, nie als verstreute if-Abfrage über mehrere phtml-Dateien hinweg.

Im Template selbst übersetzt sich die Berechtigung in einen konkreten UI-Zustand: ein deaktivierter Button mit disabled-Attribut und reduzierter Opazität, ein komplett ausgeblendetes CTA per x-show, oder ein Hinweistext, der erklärt, warum eine Aktion aktuell nicht verfügbar ist. Diese drei Muster, deaktiviert, versteckt, erklärt, sollten projektweit konsistent verwendet werden, damit B2B-Nutzer nachvollziehen können, ob eine fehlende Aktion an ihrer Rolle liegt oder an einem echten Fehler.


{
  "data": {
    "customer": {
      "company": {
        "id": "42",
        "role": {
          "name": "Buyer",
          "permissions": [
            "Magento_NegotiableQuote::negotiable_quote",
            "Magento_Company::requisition_list_view",
            "Magento_Company::order_actions_place_order"
          ]
        },
        "structure": {
          "items": [
            { "id": "1001", "name": "Sales Rep Team", "roleName": "Approver" }
          ]
        }
      }
    }
  }
}

8. CSP und registerInlineScript bei B2B-Komponenten

Hyvä-Themes laufen standardmäßig mit einer strikten Content Security Policy, die Inline-Skripte ohne Registrierung blockiert. Jede Alpine-Komponente, die für B2B-Features im Hyvä-Theme zusätzliche Inline-Logik benötigt, etwa den serverseitig vorbefüllten x-data-Block einer Freigabe-Banner-Komponente, muss deshalb über den $hyvaCsp-ViewModel registriert werden. Ohne diese Registrierung meldet der Browser einen CSP-Verstoß, und die Komponente bleibt funktionslos, obwohl der Code syntaktisch korrekt ist.

Für wiederverwendbare Logik wie den Alpine-Store aus Abschnitt 3 lohnt sich eine ausgelagerte JavaScript-Datei statt eines Inline-Blocks, weil sie nur einmal pro Seite geladen und über den Hyvä-Watcher gebündelt wird. Inline bleibt reserviert für die dynamischen, pro Bestellung unterschiedlichen Werte, die serverseitig aus dem ViewModel kommen. Nach jedem verbleibenden Inline-Block muss unmittelbar $hyvaCsp->registerInlineScript() aufgerufen werden, sonst schlägt die Auslieferung im Produktivbetrieb mit aktivierter CSP fehl.


// Registered once via registerInlineScript, reused by every approval banner on the page
document.addEventListener('alpine:init', () => {
    Alpine.store('b2bApproval', {
        pending: [],

        // Populated server-side from the ViewModel, avoids an extra GraphQL round trip on load
        init(initialPending) {
            this.pending = initialPending;
        },

        isPending(orderId) {
            return this.pending.includes(orderId);
        },

        approve(orderId) {
            // Optimistic UI update, reconciled once the GraphQL mutation resolves
            this.pending = this.pending.filter((id) => id !== orderId);
        }
    });
});

9. B2B-Patterns im Vergleich

Die folgende Tabelle stellt die Standard-Umsetzung von Magento B2B unter Luma der empfohlenen Umsetzung für B2B im Hyvä-Theme gegenüber. Der Unterschied liegt nicht in der zugrunde liegenden Business-Logik, die bleibt identisch, sondern ausschließlich in der Rendering- und Zustandsschicht.

Bereich Luma B2B UI Hyvä B2B-Umsetzung Vorteil
Company-Switcher Knockout-Binding ViewModel + Alpine x-data Kein Knockout-Runtime nötig
Freigabe-Status Observable-Kette Serverseitig vorbefüllt, x-show Kein Flackern beim ersten Rendern
Requisition Lists UI-Components-Grid Serverseitige Liste + GraphQL-Mutation Weniger JS-Overhead, schnelleres TTI
Negotiable Quotes Computed Properties ViewModel-Statusmethoden Testbar, klar benannt, kein Observable-Overhead
Permission-Checks Verstreut in Templates Zentral im ViewModel Eine Quelle der Wahrheit pro Berechtigung
Inline-Skripte Keine CSP-Restriktion registerInlineScript() Pflicht CSP-konform, produktionssicher

In der Summe zeigt der Vergleich, dass eine saubere Portierung von B2B-Features im Hyvä-Theme nicht bedeutet, Funktionalität zu verlieren, sondern die gleiche Business-Logik über eine schlankere, serverseitig geprägte Architektur auszuliefern. Der größte Aufwand entsteht dort, wo Luma stark auf clientseitige Reaktivität setzt, etwa bei Negotiable Quotes, während einfache Sichtbarkeitslogik wie beim Company-Switcher mit wenig Aufwand migrierbar ist.

Mironsoft

Magento B2B, Hyvä-Themes und Alpine.js-Integrationen

B2B-Features im Hyvä-Theme, die wirklich funktionieren?

Wir portieren Company Accounts, Freigabe-Workflows, Negotiable Quotes und Requisition Lists aus dem Luma-Standard in euer Hyvä-Theme, mit ViewModels, Alpine.js und CSP-konformen Inline-Skripten.

B2B-Audit

Analyse, welche Luma-B2B-Komponenten in eurem Hyvä-Theme fehlen oder fehlerhaft sind

Umsetzung

ViewModels, Layout-XML-Overrides und Alpine-Komponenten für alle B2B-Workflows

CSP-Absicherung

registerInlineScript() sauber integriert, produktionssichere Auslieferung

10. Zusammenfassung

Die Umsetzung von B2B-Features im Hyvä-Theme löst immer dasselbe Grundproblem: Magento B2B liefert seine Frontend-Schicht als Luma-Komponenten mit Knockout.js und UI-Components aus, die Hyvä konsequent entfernt. ViewModels übernehmen die Rolle, den Company-Kontext, Berechtigungen und Quote-Status serverseitig aufzulösen. Alpine.js ersetzt Knockout für alles, was tatsächlich clientseitige Reaktivität braucht, etwa das Ein- und Ausklappen einer Freigabe-Historie. Layout-XML-Overrides tauschen die Luma-Templates gegen eigene Hyvä-phtml-Dateien, ohne die zugrunde liegenden Service Contracts anzufassen.

Der größte Hebel liegt darin, Permission-Checks konsequent im ViewModel zu bündeln und im Template nur noch als einfache Booleans für UI-Zustände zu verwenden, deaktiviert, versteckt oder erklärt. Wer zusätzlich jede Inline-Alpine-Komponente korrekt über registerInlineScript() registriert, liefert Hyvä B2B-Features aus, die auch mit aktivierter Content Security Policy in Produktion stabil laufen.

B2B-Features im Hyvä-Theme, das Wichtigste auf einen Blick

Company-Kontext

ViewModel mit CompanyManagementInterface und Customer-Extension-Attributes lädt Company, Rolle und Berechtigungen serverseitig.

Alpine statt Knockout

Freigabe-Status, Verhandlungszustand und Requisition-List-UI laufen über x-data, x-show und x-text, nie mit Doppelklammer-Syntax.

Layout-XML-Overrides

Bestehende B2B-Blöcke per referenceBlock und setTemplate auf eigene Hyvä-phtml-Dateien umleiten, UI-Components-Grids entfernen und ersetzen.

CSP-Konformität

Jede Inline-Alpine-Komponente mit dynamischen ViewModel-Werten braucht $hyvaCsp->registerInlineScript() direkt nach dem Script-Block.

11. FAQ: B2B-Features im Hyvä-Theme

1Was sind B2B-Features im Hyvä-Theme konkret?
Company Accounts, Freigabe-Workflows, Negotiable Quotes, Requisition Lists, Shared Catalogs und Sales-Rep-Zuordnung, im Standard Luma-Komponenten mit Knockout.js, die im Hyvä-Frontend neu gebaut werden müssen.
2Warum funktioniert Magento B2B nicht sofort in Hyvä?
Hyvä entfernt Knockout.js und UI-Components vollständig. Die B2B-Templates referenzieren genau diese Komponenten, deshalb bleiben Grids und Bindings ohne eigene Umsetzung leer oder werfen Fehler.
3Wie erkennt ein ViewModel den Company-Kontext?
Die Company-ID steckt in den Customer-Extension-Attributes unter getCompanyAttributes(). Mit dieser ID lädt CompanyManagementInterface::getById() das vollständige Company-Objekt.
4Was ersetzt Knockout beim Freigabe-Status?
Alpine.js. Der Status wird vom ViewModel in x-data geschrieben, x-show steuert Sichtbarkeit, x-text zeigt Texte, ohne Doppelklammer-Syntax.
5Wie werden Requisition Lists dargestellt?
Serverseitig gerendert über ViewModel, Zustände laufen über Alpine. Schreiboperationen nutzen die vorhandenen GraphQL-Mutationen des B2B-Moduls.
6Wie bildet man Negotiable Quotes ab?
Jeder Status bekommt eine eigene ViewModel-Methode wie isAwaitingMerchantResponse() oder canAcceptOffer(). Das Template fragt nur diese Booleans ab.
7Wie prüft man Company::PERMISSION_*?
Zentral im ViewModel über die Authorization-Schicht. Das Template erhält nur einen Boolean für deaktiviert, versteckt oder Hinweistext.
8Warum braucht Alpine registerInlineScript()?
Die strikte CSP von Hyvä blockiert unregistrierte Inline-Skripte. Ohne $hyvaCsp->registerInlineScript() bleibt die Komponente funktionslos.
9Bleiben Overrides bei B2B-Updates kompatibel?
Ja, solange referenceBlock stabile Blocknamen referenziert und nur Template und Argumente angepasst werden. Die Service Contracts bleiben unverändert.
10Wie viel Aufwand ist realistisch?
Ein Company-Switcher ist in Stunden fertig. Negotiable Quotes mit ihrer Zustandsmaschine erfordern gründliche Analyse und entsprechend mehr Zeit.