Präsentation von Dr. Nikolai Krambrock auf dem Developers ParadisePrä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 ab050100
€/St.0,45 €0,39 €0,37 €
+ Lasergravur5,00 €0,19 €0,15 €
+ Siebdruck4,50 €0,17 €0,13 €
+ Tampondruck4,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:

VarianteVorteileNachteile
Abbilden der Zuschläge über WarenkorbpreisregelnAußer ggf. Observern im Backend keine Änderungen im SystemDie Preisabschläge werden nur in Checkout und Rechnung und nur als eine Position angezeigt
Simple Configurable Products / Better Configurable ProductsGute Darstellung und Verwenden eines fertigen Moduls, um die Anforderungen umzusetzenFü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 PreisenGute Darstellung und lediglich moderate Eingriffe ins BasissystemBackend 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:Tier Price für individuale OptionenIm zweiten Schritt legen wir den Drucktyp als individuelle Option an:Tier Prices für KundenoptionenBeim Speichern wird daraus die folgende Tabelle von individuellen Optionen, in welche dann die Staffelpreise für die ursprünglichen individuellen Optionen eingegeben werden:Einstellung der KundenoptionenUnter 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:Anzeige der Tier Prices im Warenkorb

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!