Entwickler-Übersicht
Diese Seite richtet sich an Plugin-Entwickler, die das SalesRepresentative-Plugin erweitern, seine Store-API-Routes verstehen oder die Caching- bzw. Kontext-Logik anpassen wollen.
Datenmodell
leoparden_sales_representative— Profil (Adresse, Kontakt,media), 1:1 zum Kunden übercustomer_id.…_affiliate_code— AffiliateCodes je Vertreter.…_customers— Junction mit zusammengesetztem PK (customer_id+leoparden_sales_representative_affiliate_code_id); realisiert die n:m-Beziehung Kunde ↔ AffiliateCode.…_order_extra— token-bezogener Datensatz mit Liefer-/Rechnungsadresse und aktivem AffiliateCode für „Als Kunde bestellen".
DAL-Definitionen unter src/Content/SalesRepresentative/ und …/Aggregate/<Name>/. Repository-IDs: leoparden_sales_representative.repository, …_affiliate_code.repository, …_customers.repository, …_order_extra.repository.
Entity-Extensions
src/Content/Extension/ hängt Assoziationen an Core-Entitäten (shopware.entity.extension):
Extension | Ziel | Assoziation |
|---|---|---|
|
|
|
|
| Order-Extra-Bezug |
|
| Vertreter-Extension (Custom-Products-Integration) |
|
| Vertreterzuordnung am Adress-Custom-Field |
SalesChannelContextSubscriber
Das Herzstück zur Laufzeit. Auf SalesChannelContextCreatedEvent hängt der Subscriber drei Extensions an den eingeloggten Kunden:
Vertreterprofil (
leoparden_sales_representative) — viaSalesRepresentativeRoute,aktiver AffiliateCode — via
SalesRepresentativeCustomersRoute(jüngster Code des Kunden),Order-Extra (Prio
-500) — sucht/erzeugt den token-gebundenen Datensatz, trägt Default-Adressen und den AffiliateCode nach und hängt ihn als Extension an.
Den „effektiven" Kunden löst der Subscriber durchgängig über getExtension('leoparden_current_company')?->getCompany() ?? $customer auf — ist ein Kunde Teil einer Firma, gilt die Firma als Vertreter/Kunde. Dieses Idiom wiederholt sich an mehreren Stellen und ist beim Erweitern beizubehalten.
Store-API-Routes
Jede Ressource folgt dem Paar Abstract<Name>Route → <Name>Route (Repository-Suche; getDecorated() wirft DecorationPatternException). Je Route zwei Events unter Content/Events/<Name>/: …CriteriaEvent (Kriterien vor der Suche) und …LoadedEvent (Ergebnis nachbearbeiten).
Route | Pfad |
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| (Attach-Route, nicht gecacht) |
Caching (6.7-Modell)
Seit dem 6.7-Caching-Rework gibt es keine Cached*Route-Decorators und keine Cache-Key-/Tags-Events mehr. Stattdessen:
Jede cachende Route trägt am
#[Route]-Attributdefaults: ['_entity' => '<entity>', '_httpCache' => true]. Shopware cacht die Response und invalidiert sie automatisch, sobald die eigene_entitygeschrieben wird.Services/CacheInvalidator(einreadonly-Subscriber aufEntityWrittenContainerEvent) deckt nur die entitätsübergreifenden Fälle ab, die das_entity-Auto-Handling nicht sieht — z. B. invalidiert eincustomer-Write die kundengebundenen Vertreter-Routes. Invalidiert wird über den_entity-Namen als Tag (CacheInvalidator::invalidate([...])).
Flow-Event: CustomerInviteEvent
CustomerInviteEvent (Core/Framework/Event/) ist ein Flow-Action-Event, registriert über getActionEventClasses() der Plugin-Klasse sowie Core/Flow/Storer/CustomerInviteStorer und Core/Flow/Subscriber/BusinessEventCollectorSubscriber. Es trägt Kunde, Vertreter und Accept-URL und treibt die Einladungs-Mail (sales_representative_mail).
Subscriber
Subscriber | Aufgabe |
|---|---|
| Hängt Vertreter/AffiliateCode/Order-Extra an den Kunden (s. o.) |
| Wertet den AffiliateCode-URL-Parameter aus (Erst-Zuordnung/Wechsel je nach Config) |
| Pflegt Zuordnungen beim Speichern des Kunden |
| Überträgt im Checkout gewählte Adressen/Status auf die Order |
| Adress-Dropdown im Checkout |
| Ergänzt Kriterien für die Vertreter-Routes |
| Entitätsübergreifende HTTP-Cache-Invalidierung |
| Integration in Gläserner Auftrag/BusinessCustomProducts |
Storefront-Layer
Controller/SalesRepresentativeController.php bündelt die Storefront-Routes unter /SalesRepresentative/* (Profil-Index, Kundenliste, Kunden-/Bestelldetail, Bild-Tausch, Einladung/Annahme, ChangeOrderContext, Adress-Modal). Die Content/Page/*PageLoader bauen die Page-Structs aus den Store-API-Routes.
Die TS-Plugins in src/Resources/app/storefront/src/ erweitern @leoparden/plugin-tools-storefront-Basisklassen (AbstractStoreApi.Plugin, AbstractEntityListing.Plugin) und liefern den Request-Body über einen get body()-Getter. Registrierung in main.ts, Bindung über data-sales-representative-*-Attribute.
Adress-Modal (6.7)
addressBook() rendert seit 6.7 das address-manager-Modal (@Storefront/storefront/component/address/address-manager-modal.html.twig, Vorbild: Core-AddressController::addressManager()) und übergibt zusätzlich redirectTo/redirectParameters. Das alte address-editor-modal wurde in 6.7 entfernt.
CMS-Elemente
Unter src/Resources/views/storefront/element/:
Vertreter-eigen:
…-sales-representative-customer,…-customers,…-customer-ordersIntegration:
…-transactions,…-processes,…-articles-on-order,…-recently-delivered,…-custom-products
Administration
Resources/app/administration/src/main.ts registriert das Modul leoparden-sales-representative (Navigation unter sw-customer, Entität leoparden_sales_representative, Suchtyp via searchTypeService) und überschreibt sw-customer-detail; per routeMiddleware wird der sales-representative-Tab in die Kundendetail-Route injiziert. Das Bundle wird in 6.7 über Vite gebaut (keine plugin-eigene Webpack-Config).
Testing
phpunit.xmlbootet den Kernel überTestBootstrap.php; die Testsuite decktsrc/Testrekursiv ab — Unit- und Integration-Tests laufen im selben Run.src/Test/Unit/— kernel-/DB-freie Unit-Tests (PHPUnit\Framework\TestCase,#[CoversClass]), z. B.CacheInvalidatorTest, der überEntityWrittenContainerEvent::createWithWrittenEvents()/…createWithDeletedEvents()die invalidierten Tags prüft.src/Test/*.php— Integration-Tests viaSalesRepresentativeTestBehaviour(IntegrationTestBehaviour+SalesChannelApiTestBehaviour).Tests laufen aus dem Plugin-Verzeichnis mit gesetztem
SHOPWARE_BUILD_DIR;composer testräumt Cache, fixt Code-Style und startet PHPUnit.
Erweiterungspunkte
Kriterien/Ergebnisse anpassen — auf die
…CriteriaEvent/…LoadedEventder jeweiligen Store-API-Route subscriben.Cache-Invalidierung — bei neuen entitätsübergreifenden Abhängigkeiten den
CacheInvalidatorerweitern (Tag =_entity-Name der Ziel-Route).Einladungs-Mail — Flow Builder auf
CustomerInviteEventoder eigener EventSubscriber.