Varianten-Preisaufschlag: Technische Details
Technische Details des Varianten-Preisaufschlags für Sylius: Ablauf der Preisberechnung, dekorierte Rechner, Datenmodell mit Überschreibungen pro Channel, Speichern im Formular, Plugin-Struktur und Routen.
Varianten-Preisaufschlag: Technische Details
Ablauf der Preisberechnung
graph TD
A["calculate() / calculateOriginal()"] --> B["Eigener Preis über den Sylius-Rechner"]
B --> C{"Globaler Schalter aktiv?"}
C -->|Nein| Z["Eigener Preis"]
C -->|Ja| D{"Aufschlag gespeichert und keine Basisvariante?"}
D -->|Nein| Z
D -->|Ja| H1{"Aktiv, Channel-Wert vor globalem Wert?"}
H1 -->|Nein| Z
H1 -->|Ja| E{"Basisvariante des Produkts vorhanden?"}
E -->|Nein| Z
E -->|Ja| F["Preis der Basisvariante über den Sylius-Rechner"]
F --> G["(Basispreis + fester Aufschlag) x Prozentsatz / 100"]
G --> H["Runden, mindestens 0"]
- Der eigene Preis der Variante wird immer zuerst abgefragt, auch wenn er danach verworfen wird.
- Der Channel kommt aus
$context['channel']. Aufschlag aktiv, Prozentsatz und fester Aufschlag nimmt der Rechner je Feld aus der Überschreibung dieses Channels, sonst aus den globalen Werten, sonst 100 beziehungsweise 0. - Die Abfragen von Schalter, Aufschlag und Basisvariante laufen in einem
try-Block. Jede Ausnahme dort führt zum eigenen Preis und zu einem Log-Eintrag der Stufeerror. Die Preisabfrage der Basisvariante liegt bewusst außerhalb dieses Blocks, damit fehlende Channel-Preise als Fehler sichtbar bleiben. - Gerechnet wird mit Gleitkommazahlen und dann mit PHP
round()auf ganze Einheiten gerundet. Negative Ergebnisse werden auf 0 gesetzt. calculateLowestPriceBeforeDiscount()reicht den Wert des dekorierten Rechners durch.- Ist der globale Schalter aus, entfällt die Abfrage des Aufschlags ganz.
- Das Repository lädt die Überschreibungen zusammen mit dem Aufschlag in einer Abfrage, damit die Preisberechnung keine zusätzlichen Abfragen pro Variante auslöst.
- Ohne gespeicherte Einstellungszeile gilt der Schalter als aktiv. Die Zeile wird beim Lesen nie geschrieben, nur beim Speichern der Einstellungsseite.
Dekorierte Rechner
| Service | Gilt für | Decorator |
|---|---|---|
sylius.calculator.product_variant_price |
Sylius 2.2 und 2.3, Warenkorb und Bestellung | SurchargeAwareProductVariantPriceCalculator |
sylius.calculator.product_variant_catalog_price |
ab Sylius 2.3, Produktseite, Listen, Variantenauswahl, API | SurchargeAwareCatalogPricesCalculator, registriert vom Compiler-Pass DecorateCatalogPricesCalculatorPass |
Datenmodell
| Tabelle | Spalte | Typ | Hinweis |
|---|---|---|---|
mmd_product_variant_price_surcharge |
id |
INT | Primärschlüssel |
product_variant_id |
INT | Eindeutig, Fremdschlüssel auf sylius_product_variant, ON DELETE CASCADE |
|
is_base |
Boolean | Basisvariante | |
is_enabled |
Boolean | Aufschlag aktiv | |
percentage |
Dezimal(10,2), nullable | Standard 100.00 | |
fixed_surcharge |
INT, nullable | Kleinste Währungseinheit, Standard 0 | |
mmd_product_variant_price_surcharge_channel |
id |
INT | Primärschlüssel |
surcharge_id |
INT | Fremdschlüssel auf mmd_product_variant_price_surcharge, ON DELETE CASCADE |
|
channel_code |
VARCHAR(255) | Code des Channels, kein Fremdschlüssel auf sylius_channel |
|
is_enabled |
Boolean, nullable | NULL bedeutet globalen Wert übernehmen |
|
percentage |
Dezimal(10,2), nullable | NULL bedeutet globalen Wert übernehmen |
|
fixed_surcharge |
INT, nullable | NULL bedeutet globalen Wert übernehmen |
|
mmd_variant_price_surcharge_settings |
id |
INT | Immer 1 |
active |
Boolean | Globaler Schalter, Standard an |
Die Kombination aus surcharge_id und channel_code ist eindeutig (Index mmd_surcharge_channel_idx). Der Aufschlag hängt über einen Fremdschlüssel an der Variante. Das Plugin ändert die Sylius-Klasse ProductVariant nicht.
Drei Migrationen legen die Tabellen an: Version20260905080038 (Aufschläge), Version20260905124014 (Einstellungen) und Version20261015135354 (Überschreibungen pro Channel).
Speichern im Variantenformular
ProductVariantTypeExtension erweitert den Formulartyp ProductVariantType um ein nicht gemapptes Unterformular. Beim Laden holt sie die gespeicherte Zeile der Variante oder legt ein leeres Objekt an. Das Formular zeigt immer einen Eintrag pro Sylius-Channel, auch wenn dafür noch keine Zeile existiert. Beim Absenden gilt:
- Bei einer Basisvariante entfernt sie alle Überschreibungen pro Channel.
- Einträge, in denen alle drei Felder auf "übernehmen" stehen, entfernt sie ebenfalls.
- Sie persistiert die Zeile nicht, wenn Basisvariante und Aufschlag anwenden aus sind und keine Überschreibung übrig bleibt.
- Eine bereits gespeicherte Zeile wird sonst auch bei abgeschalteten Feldern mit den neuen Werten aktualisiert.
Plugin-Struktur
Auszug der wichtigsten Verzeichnisse:
src/
Calculator/ SurchargeAwareProductVariantPriceCalculator, SurchargeAwareCatalogPricesCalculator
Command/ SetupCommand
Controller/ Admin/VariantPriceSurchargeSettingsController
DependencyInjection/ Extension, Configuration, Compiler/DecorateCatalogPricesCalculatorPass
Entity/ ProductVariantPriceSurcharge, Channel/ProductVariantPriceSurchargeChannel, Settings/VariantPriceSurchargeSettings
Form/ Extension/ProductVariantTypeExtension, Type/...
Menu/ AdminMenuListener, MenuGroup
Migrations/
Repository/
Twig/ PluginVersionExtension
config/
doctrine/ Mapping der Aufschlags-Entity
grids/ Grid der Übersicht
routes/ admin.yaml, shop.yaml (ohne Routen)
services/
twig_hooks/ Formularbereich und Einstellungsseite
templates/admin/
translations/ messages und flashes, de und en
Routen
| Route | Pfad | Zweck |
|---|---|---|
mmd_variant_price_surcharge_admin_settings |
/<Admin-Pfad>/variant-price-surcharge/settings |
Einstellungsseite, GET und POST |
| Ressourcen-Routen der Übersicht | /<Admin-Pfad>/variant-price-surcharges |
Liste und Löschen |
<Admin-Pfad> ist der Parameter sylius_admin.path_name, im Standard admin. Beide Routen liegen hinter der Admin-Firewall von Sylius.
Weitere Hinweise
- Die Einstellungsseite zeigt unter dem Titel die installierte Plugin-Version, die über Composer ermittelt wird. Ohne Tag erscheint der Branch mit dem kurzen Commit-Hash.
- Das Plugin sendet keine Daten an externe Dienste und verarbeitet keine personenbezogenen Daten.
- Die Übersetzungen liegen in
translations/für Deutsch und Englisch.