Wie es ist: Configurable-Products in Magento
Im Normalfall sind Configurable-Products in Magento Produkte die rein virtuell weitere untergeordnete Produkte, die Simple-Products, beinhalten. Wird ein solches Produkt in den Warenkorb gelegt sind, rein technisch, zwei Produkte im Warenkorb: Einmal das übergeordnete Configurable und dann die jeweils ausgewählte Variation, also z.B. genau das T-Shirt in Schwarz und M, als Simple-Product. Configurable-Products besitzt eigene Beschreibungen, eigene Attribute und auch eigene Preise. In der Regel muss das Simple also einfach nur da sein um Warenbestand zu verwalten.
In der Praxis hat sich gerade die fehlende Möglichkeit alle Attribute durch einen Wechsel der Variante einfach zu tauschen als relativ zäh erwiesen. Parallel haben viele Kunden ihre Artikel im ERP auch nicht über einen übergeordneten Artikel gruppiert sondern die Möglichkeit einer Artikel-Gruppe genutzt. In diesem Fall sind alle Varianten eines Artikels einfach in der selben Gruppe. Im Idealfall besitzt die Gruppe zudem noch eine Angabe welche Attribute sich unterscheiden.
Für den Normalfall bedeutet dies das wir manuell ein Configurable-Product erstellen müssen, dieses mit den Daten eines Simples füllen und im nächsten Schritt noch eine Methode implementieren welches alle relevanten Attribute beim Wechsel der Dropdown tauscht. Das klappt zwar ganz gut aber Ideal ist es doch nicht unbedingt, oder?
Die Idee: Basisartikelnr
Daher kam mir die Idee das Verhalten, welches die meisten ERP-Systeme bereits an den Tag legen, in Magento nachzubilden.
Keine Configurable-Products!
Um nun keine Informationen im Simple und Configurable doppelt zu speichern gehen wir einen einfachen Weg: Wir verzichten vollständig auf Configurable-Products. Die Gruppierung der einzelnen Artikel übernimmt ein neues Attribute mit dem Code 'Basisartikelnr'. Alle Artikel welche die selbe Basis-Artikel-Nummer besitzen müssen als ein Produkt in Magento angezeigt. Der User wechselt mittels Dropdown zwischen den Produkten.
Umsetzung
Nun stand das Konzept der kleinen Idee aus dem die folgende kleine To-Do-Liste entstanden ist:
- Per Installer das Attribute Basisartikelnr erstellen
- Bei Simple-Products prüfen ob weitere Simple-Products vorhanden sind, wenn ja Unterschiedliche Attribute per Dropdown verfügbar machen. Ändern der Werte führt, genau wie in Magento, zum Produktwechsel. Im Anfang erst mal ohne Ajax.
- Im Warenkorb ausgewählte Attribute darstellen
- In der Kategorie-Ansicht nur ein Produkt je Basisartikel-Nummer anzeigen
Leider bin ich nicht diszipliniert genug mich ordentlich an meine To-Do-Listen zu halten sodass dort schnell ein "Cherry-Picking" entsteht. Ich suche mir also erst die Features raus die ich als möglichst komplex identifiziert habe. Einen Vorteil hat das ganze jedoch: Ich laufe nicht Gefahr ganz viel Fleißarbeit bei Basis-Dingen zu leisten um im Nachgang festzustellen das mein Vorgehen doch eigentlich falsch war.
Anpassungen in Catalog_Product_View
Wie erwähnt muss, für den Fall das ein Simple-Product in mehreren Varianten verfügbar ist, dieses über die Basis-Artikel-Nummer identifiziert werden. Ein Wechsel der Varianten erfolgt über Dropdowns. Wir benötigen also drei Komponenten
- Ein Java-Script welches beim Dropdown-Wechsel das korrekte Simple-Product läd
- Einen Block der das Java-Script initialisiert
- Einen Block der unsere Dropdowns darstellt.
Der Java-Script Methode zum Wechsel des Dropdowns stellen wir ein JSON-Array bereits welches für alle Dropdown-Attribute (Farbe, Größe, ..) den Wert und den Link zum jeweiligen Produkt enthält. In der Praxis sieht das dann z.B. wie folgt aus:
[
{
"url":"http:\/\/localhost.magento\/magento-extensions\/htdocs\/index.php\/test\/t-shirt-in-rot.html",
"color":"3"
},
{
"url":"http:\/\/localhost.magento\/magento-extensions\/htdocs\/index.php\/test\/t-shirt-in-schwarz.html",
"color":"4"
}
]
Anhand der Werte des Schlüssels color weiß unser Java-Script unter welcher URL das T-Shirt in der jeweils anderen Farbe zu finden ist. Dabei spielen die Anzahl der Attribute übrigens keine Rolle denn ein Treffer zählt nur wenn alle Ausprägungen gefunden wurden.
Das Java-Script erhält nun über eine Init-Methode wie folgt die JSON-Daten:
<script type="text/javascript">
var basisartikelnr = new Basisartikelnr( ..json-data.. );
</script>
Nun gilt es über den üblichen Magento-Weg unsere Blöcke für Dropdowns und Init hinzufügen. Auch hier bedienen wir uns einem bereits vorhanden Block-Container welcher bereits automatisch alle Kind-Elemente darstellt:
<?xml version="1.0"?>
color
color
(/code)
Wie man an der Methode setCode bzw. setCodes erkennt wird hier z.Z. ausschließlich fest verdrahtet eine Dropdown für das Attribute Color hinzugefügt. Gleiches lässt sich natürlich auch für Größe und ähnliches umsetzen. Im nächsten Schritt werde ich mich dann darum kümmern das alle konfigurierten Attribute dort automatisch angezeigt werden - das aber erst später.
Nun haben wir eine Dropdown für Simples-Products erstellt welche automatisch alle Ausprägungen des jeweiligen Attributes mit der selben Basis-Artikel-Nr. anzeigt. (Der kompletten Source befindet sich im ZIP-Archiv)
Anpassungen in Catalog_Category_View
Im zweiten Schritt müssen wir nun dafür sorgen dass die Artikel mit identischer Artikel-Basis-Nummer nicht doppelt in einer Produkt-Liste angezeigt werden. MySQL bietet zur Gruppierung jedoch schon ein tolles Basis-Feature an: GROUP BY. Logisch wäre es also unsere Product-Collection einfach um ein Group-By zu erweitern.
Dazu müssen wir jedoch zuerst dafür sorgen dass das Attribute Basisartikelnummer auch in der Produkt-Auflistung verfügbar ist. Aber nichts leichter als das: Schnell den Magento-Admin geöffnet, das Attribute herausgesucht und den Wert von "Used in Product Listing" auf "Yes" geändert. Danach einmal die Indizes neu erstellen und schon ist das Attribute in den Flat-Tables verfügbar - einen Group-BY steht nichts mehr im Wege.
Dazu erstellen wir im ersten Schritt einen Rewrite auf die Product-Resource-Collection:
<global>
<models>
<codex_basisartikelnr>
<class>Codex_Basisartikelnr_Model</class>
</codex_basisartikelnr>
<catalog_resource>
<rewrite>
<product_collection>Codex_Basisartikelnr_Model_Productcollection</product_collection>
</rewrite>
</catalog_resource>
</models>
</global>
Und implementieren die Methoden _beforeLoad und getSelectCountSql neu:
<?php
class Codex_Basisartikelnr_Model_Productcollection extends Mage_Catalog_Model_Resource_Product_Collection
{
protected $_disabled = false;
protected function isEnabled()
{
return !Mage::app()->getStore()->isAdmin() && !$this->_disabled;
}
public function setDisableGroupBy()
{
$this->_disabled = true;
}
// Basis-Artikel-Nummer läuft nur mit Flat-Tables stabil, also immer aktivieren
public function isEnabledFlat()
{
if (Mage::app()->getStore()->isAdmin()) {
return false;
}
return true;
}
protected function _beforeLoad()
{
if ($this->isEnabled()) {
if ($this->isEnabledFlat()) {
$this->getSelect()->group('basisartikelnr');
$this->getSelect()->columns(array('basisartikelnr_color_ids' => new Zend_Db_Expr("GROUP_CONCAT(DISTINCT color ORDER BY color_value DESC SEPARATOR ',')")));
} else {
$this->joinAttribute('basisartikelnr', 'catalog_product/basisartikelnr', 'entity_id');
$this->joinAttribute('color', 'catalog_product/color', 'entity_id');
$this->getSelect()->group('basisartikelnr');
$this->getSelect()->columns(array('basisartikelnr_color_ids' => new Zend_Db_Expr("GROUP_CONCAT(DISTINCT at_color.value ORDER BY at_color.value DESC SEPARATOR ',')")));
}
$this->getSelect()->columns(array('basisartikelnr_count' => new Zend_Db_Expr('COUNT(*)')));
}
return parent::_beforeLoad();
}
public function getSelectCountSql()
{
if ($this->isEnabled()) {
$this->_renderFilters();
if ($this->isEnabledFlat()) {
$countSelect = $this->_getClearSelect()
->columns('COUNT(DISTINCT basisartikelnr) AS cnt')
->resetJoinLeft();
} else {
$countSelect = $this->_getClearSelect()
->columns('COUNT(DISTINCT at_basisartikelnr.value) AS cnt')
->resetJoinLeft();
}
$countSelect->reset(Zend_Db_Select::GROUP);
return $countSelect;
}
return parent::getSelectCountSql();
}
}
Die Methode lief im Anfang bereits ohne ein überschreiben von getSelectCountSql sehr gut. Leider viel mir später auf das der Pager nicht mehr korrekt funktionierte. Das lag ganz einfach daran das sich Count(*) in Kombination mit GROUP-By anders verhält. Abhilfe schaffte es komplett auf das Group-By zu verzichten und anstelle ein COUNT( DISTINCT ..) zu verwenden: Hier werden nur die unterschiedlichen Basis-Artikel-Nummern gezählt.
Ausblick
Auch wenn das Modul noch eine frühe Alpha-Version darstellt und nicht unbedingt Produktiv eingesetzt werden kann war ich doch erstaunt mit wie wenig Source man doch ein komplett neues Produkt-Verhalten in Magento implementieren kann. Spannend waren auch die Seiteneffekte: So war es möglich ohne weiteres zutun in der Layered-Navigation nach ROT zu filtern und im selben Zuge auch ausschließlich Rote-Produktbilder zu sehen. Mittels GROUP_CONCAT(color) lässt sich sogar ohne weiteres eine Farb-Vorschau für das jeweilige Produkt einer Basis-Artikel-Nummer erstellen.
Dennoch bleibt einiges zu tun:
- Basis-Artikel-Nummer Attribute per Setup-Script erstellen
- Die Attribute-Dropdowns automatisch aufbauen, nicht manuell per Layout-XML
- Dafür sorgen das Produkte ohne Artikel-Basis-Nummer nicht zu einem großen Produkt zusammengefasst werden
- Testen, testen, testen