Präsentation von Dr. Nikolai Krambrock auf dem Developers Paradise 2014
Magento erlaubt Preisstaffeln für Produkte, und Preise für individuelle Optionen. Preisstaffeln für individuelle Optionen werden dagegen nicht unterstützt. Diese werden vor allem dann benötigt, wenn eine Vielzahl von Produkten, wie Werbeartikel oder Textilien, bedruckt oder in anderer Form veredelt werden sollen. Die folgende Tabelle zeigt z. B. Preise für Kugelschreiber mit Bedruckung.
Stück ab | 0 | 50 | 100 |
€/St. | 0,45 € | 0,39 € | 0,37 € |
+ Lasergravur | 5,00 € | 0,19 € | 0,15 € |
+ Siebdruck | 4,50 € | 0,17 € | 0,13 € |
+ Tampondruck | 4,00 € | 0,16 € | 0,12 € |
Der Preis soll, abhängig von der bestellten Menge, 0,45€, 0,39€, 0,37€, 0,35€ oder 0,27€ betragen. Die Druckkosten für einen Schriftzug sollen ebenfalls mit der Menge sinken. Sie sollen nicht als prozentualer Zuschlag zum Basispreis, sondern als eigene Preise angeben werden.
Die Varianten
Bei der Umsetzung sind wir verschiedene Varianten durchgegangen. Die letzte (und aus unserer Sicht beste) Variante wird hier im Detail beschrieben – wir haben sie in einem Kundenprojekt umgesetzt. Folgende Varianten wurden ausprobiert:
Variante | Vorteile | Nachteile |
Abbilden der Zuschläge über Warenkorbpreisregeln | Außer ggf. Observern im Backend keine Änderungen im System | Die Preisabschläge werden nur in Checkout und Rechnung und nur als eine Position angezeigt |
Simple Configurable Products / Better Configurable Products | Gute Darstellung und Verwenden eines fertigen Moduls, um die Anforderungen umzusetzen | Für jede Attributkonfiguration muss ein einfaches Produkt angelegt werden; außerdem starke Eingriffe ins Basissystem durch zahlreiche Rewrites |
Generieren von zusätzlichen individuellen Optionen mit Preisen | Gute Darstellung und lediglich moderate Eingriffe ins Basissystem | Backend enthält viele zusätzliche individuelle Optionen – dies ist für die manuelle Pflege ein wenig ungünstig |
Abbilden der Zuschläge über Warenkorbpreisregeln
Das Abbilden der Zuschläge über Warenkorbpreisregeln ist die eleganteste Variante, wenn eine Import-Schnittstelle zu einer Warenwirtschaft verwendet wird. Dann können während, oder im Anschluss an den Import Warenkorbpreisregeln generiert werden, die Rabatte auf die jeweiligen Positionen liefern. Das Ganze hat allerdings einen entscheidenden Nachteil: Die Rabatte zu den Produkten werden im Checkout nur als eine Position angezeigt und lassen sich nicht unmittelbar zuordnen. Daher dürfte diese Variante in den seltensten Fällen ausreichen.
Zuhilfenahme von Simple Configurable Products / Better Configurable Products
Die vom Entwicklungsaufwand her einfachste Lösung ist die Nutzung von Simple Configurable Products (http://www.magentocommerce.com/magento-connect/simple-configurable-products.html) oder Better Configurable Products (http://www.magentocommerce.com/magento-connect/better-configurable-products.html). Letztlich wurde nur mit Better Configurable Products getestet, da hiervon eine aktuelle Version für Magento 1.7 zur Verfügung stand. Für jede Variante des Produkts, also z. B. Lasergravur, Siebdruck und Tampondruck, wird ein zusätzliches konfigurierbares Produkt angelegt. Simple Configurable Products sorgt dann dafür, dass die Preisstaffel aus dem einfachen Produkt übernommen wird. Bei vielen Produkten und vielen Varianten wird das aber schnell unübersichtlich. Gibt es neben den aufgeführten Druckarten rote, grüne und blaue Kugelschreiber und kann zwischen kleiner, mittlerer und großer Schrift gewählt werden, so ergibt dies — im Gegensatz zu drei — 27 einfache Produkte. Diese müssen dann alle einem konfigurierbaren Produkt zugeordnet werden. Zudem haben alle 27 Produkte einen eigenen Lagerbestand. Zu guter Letzt greifen die beiden Module durch zahlreiche Rewrites tief in das System ein und geraten leicht mit weiteren Modulen in Konflikt.
Generieren von zusätzlichen individuellen Optionen mit variablen Preisen
Das Generieren von zusätzlichen individuellen Optionen haben wir letztlich in einem Kundenprojekt umgesetzt. Die Grundidee ist einfach: Eine individuelle Option erlaubt nur einen Preiszuschlag, es werden aber fünf – einer je Staffel — benötigt. Also werden fünf individuelle Optionen mit verschiedenen Preisen angelegt. Vorteile dieser Lösung sind ein sinnvolles Erscheinungsbild und moderate Änderungen am System. Die von uns erstellte Lösung kommt mit zwei Observern aus – zusätzliche Rewrites von Blocks sorgen für ein besseres Erscheinungsbild. Da Magento bei dieser Lösung nach dem Warenkorb mit standardgetreuen individuellen Optionen arbeitet, ist im Folgenden auch nicht mit zusätzlichen Schwierigkeiten zu rechnen – also in Rechnungen, Lieferungen, Gutschriften, Export, etc. Der Nachteil dieser Lösung ist eine gewisse Unordnung in den individuellen Optionen im Backend.
Eingabe von Staffelpreisen für individuelle Optionen
Zur Nutzung des Moduls müssen im Backend sowohl eine Preisstaffel, als auch individuelle Optionen angelegt werden. Schließlich werden die Preise der individuellen Optionen, für jede Preisstaffel, in neu generierte Optionen eingetragen. In diesem Beispiel zeigen wir, wie man die Preisstaffel für ein Produkt anlegt:Im zweiten Schritt legen wir den Drucktyp als individuelle Option an:
Beim Speichern wird daraus die folgende Tabelle von individuellen Optionen, in welche dann die Staffelpreise für die ursprünglichen individuellen Optionen eingegeben werden:
Unter dem Titel „preis-lasergravur-100“ versteckt sich also der Preis für eine Lasergravur, bei einer Mindestabnahme von 100 Kugelschreibern. Entsprechend werden die anderen Felder ausgefüllt. Werden alle diese Felder ausgefüllt, so ist das Ergebnis im Checkout erwartungsgemäß. Bei 120 bestellten Kugelschreibern mit Lasergravur, haben diese einen Preis von 0,37€ + 0,15€, also insgesamt 0,52€ pro Stück:
Magento-Entwicklung der Staffelpreise für individuelle Optionen
Die wesentliche Arbeit in dem Modul übernehmen zwei Observer. Der erste Observer generiert die individuellen Optionen im Backend. Der zweite Observer wählt die korrekte Option bei Änderungen im Warenkorb. Die vereinfache config.xml sieht also wie folgt aus:
<config> <modules> <C4B_Configurableprices> <version>0.0.1</version> </C4B_Configurableprices> </modules> <global> <events> <catalog_product_prepare_save> <observers> <c4b_configurableprices_catalog_product_observer> <type>singleton</type> <class>C4B_Configurableprices_Model_Catalog_Product_Option</class> <method>updateProductCustomOptions</method> </c4b_configurableprices_catalog_product_observer> </observers> </catalog_product_prepare_save> <checkout_cart_save_before> <observers> <c4b_configurableprices_cart_observer> <type>singleton</type> <class>C4B_Configurableprices_Model_Checkout_Observer</class> <method>applyConfigurablePrices</method> </c4b_configurableprices_cart_observer> </observers> </checkout_cart_save_before> </events> </global> </config>
Observer beim Speichern von Produkten im Backend
Wird ein Produkt im Backend gesichert, so wird die Methode updateProductCustomOptions der Klasse C4B_Configurableprices_Model_Catalog_Product_Option ausgeführt. Diese sorgt dafür, dass zusätzliche individuelle Optionen angelegt werden. Bereits angelegte werden aber nicht überschrieben – die schon eingegebenen Preise sollen nicht entfernt werden. Dies übernimmt folgende stark vereinfachte Methode:
public function updateProductCustomOptions($observer) { /* @var $product Mage_Catalog_Model_Product */ $product = $observer['product']; $newOptionList = $this->_getNewOptionsList($product); foreach($product->getOptions() as $option) { foreach($option->getValues() as $value) { $key = array_search(strtolower(trim($value->getSku())), $newOptionList); if($key === false) { $this->_deleteOptionValue($product, $option, $value->getSku()); } else { unset($newOptionList[$key]); } } } foreach($newOptionList as $newOptionValue) { $this->addNewOptionValue($product, 0, $newOptionValue); } }
Zunächst ermittelt die Methode _getNewOptionsList alle Werte von generierten individuellen Optionen, die ein Produkt haben sollte, wie z. B. „preis-lasergravur-100“ und legt diese in das Array newOptionList ab. Dann geht die Methode alle Optionen (und deren Werte) des aktuellen Produkts durch. Nicht mehr benötigte Optionen werden gelöscht, bereits vorhandene aus der newOptionList gestrichen. Die verbleibenden Optionen werden im Anschluss mit der Methode addNewOptionValue erstellt.
Observer beim Ändern des Warenkorbs
Vor dem Sichern des Warenkorbs werden von der Methode applyConfigurablePrices der Klasse C4B_Configurableprices_Model_Checkout_Observer die zur aktuellen Preisstaffel gehörenden Optionen gewählt. Hier ebenfalls die stark vereinfachte Methode:
public function applyConfigurablePrices($observer) { $items = Mage::getSingleton('checkout/session')->getQuote()->getAllItems(); foreach($items as $item) { $itemQty = $this->_getQuantityBoundary($item); $options_ids = $this->_getNotGeneratedCustomOptions($item); foreach ($options_ids as $optionId) { $valueId = $item->getOptionByCode('option_' . $optionId)->getValue(); $productOption = $item->getProduct()->getOptionById($optionId); $optionTitle = $productOption->getTitle(); $valueTitle = $productOption->getValueById($valueId)->getTitle(); $option = $this->_getOptionByTitle($item->getProduct(), self::PREFIX . $optionTitle); $valueSku = self::PREFIX . $optionTitle . "-" . $valueTitle . "-" . $itemQty; $this->_addOrUpdateOptionValue($item, 'option_' . $option->getId(), $this->_getOptionValueIdBySku($option,$valueSku)); $options_ids .= strlen($options_ids) == 0 ? $option->getId() : "," . $option->getId(); } $this->_addOrUpdateOptionValue($item, 'option_ids', $options_ids); } }
In dieser Methode werden alle Positionen des Warenkorbs bearbeitet. Die Methode _getQuantityBoundary gibt die Mengengrenze zurück, also z. B. 100 für eine Menge von 120. Die Methode _getNotGeneratedCustomOptions gibt alle Werte ursprünglicher individueller Optionen zurück, die der Kunde gewählt hat. „lasergravur“ ist so ein ursprünglicher Wert, „preis-lasergravur-100“ wäre dagegen nicht in der Liste. Im Folgenden wird für jede Option (z. B. „lasergravur“ ) mit der Mengengrenze die generierte Option ermittelt und ausgewählt (z. B. „preis-lasergravur-100“), die letztlich zu dem Preiszuschlag führt. Im Anschluss (hier nicht gezeigt) muss die Preisberechnung neu angestoßen werden.
Fazit
Das Thema Preisstaffeln bei individuellen Optionen ist in Magento aufwendig, und nur über Umwege umzusetzen. Zu allem Überfluss gibt es zahlreiche verlockende Optionen der Umsetzung, die letztlich nicht zufriedenstellend funktionieren. Der hier vorgestellte Ansatz ist bereits im Einsatz beim Kunden und führt im Frontend zu durchweg positiven Resultaten. Diese Variante kann im eigenen Projekt ebenfalls eingesetzt werden. Wer dazu das Modul als Vorlage nutzen möchte, kann es gerne kostenlos (aber auch ohne Gewähr und Support) hier anfragen: https://www.code4business.de/kontakt-impressum/. Ohne tiefgehende Magento-Kenntnisse lässt es sich allerdings nicht im eigenen Magento-Projekt integrieren. Über eine rege Diskussion und Verbesserungsvorschläge freue ich mich natürlich sehr!
Das Fehlen von Staffelpreisen für Produktoptionen ist wohl ein Problem, dass wohl jeder Werbeartikelverkäufer der Magento nutzt hat. Hier habe ich endlich eine brauchbare Lösung gefunden, die, etwas angepasst, nun auf http://www.stressball-shop.de zu bewundern ist.
Ich danke für die Unterstützung und freundliche Kommunikation.
Viele Grüße aus Dresden
Mittlerweile habe ich eine Extension gefunden, die diese Anforderung zumindest teilweise ebenfalls erfüllt: http://www.mageworx.com/advanced-product-options-magento-extension.html. Die Extension erweitert die individuellen Optionen auf sehr umfangreiche Art. Das hat natürlich auch Kosten: Umfangreiche Rewrites und zusätzliche Funktionen, die nicht benötigt werden. Bei einem kleinen Shop mit wenigen weiteren Änderungen wäre mein Rat, die Extension anzuschauen.