How-to: Komplexen Produktkonfigurator in Magento 2 umsetzen

How-to: Komplexen Produktkonfigurator in Magento 2 umsetzen

Wir haben im Kundenauftrag einen komplexen Produktkonfigurator für Palettenregale realisiert. Damit werden aus sehr vielen gruppierten Einzelprodukten zusammengestellte Produkte im Warenkorb schließlich als eine Einheit, nämlich als Bündelprodukt, dargestellt. In diesem How-to zeigen wir, wie sich die entsprechende Funktionalität in Magento 2 umsetzen lässt.

Die praktische Seite des Problems

Die Anforderung unseres Auftraggebers war klar definiert: Der Kunde soll im Shop anhand eines Konfigurators ein Regal, das aus diversen unterschiedlichen Einzelteilen besteht, an seine individuellen Bedürfnisse anpassen. Nach der Wahl der passenden Ausführung (leicht, mittelschwer oder schwer) für sein Palettenregal soll er die Anzahl der Regalfelder und die Anzahl der Regalfächer pro Regalfeld auswählen können. Hinzukommen soll dabei automatisch das zum Lieferumfang gehörende Zubehör wie Schrauben und Muttern und auf Wunsch noch optionales Zubehör wie Rammschutzecken.

Produktkonfigurator für Regale in Magento 2

Nach Abschluss der Konfiguration des Regals soll der Kunde das individuell zusammengestellte Produkt in den Warenkorb legen können und sein Regal dort als eine Einheit wiederfinden – und nicht etwa als unübersichtliches Sammelsurium von Einzelteilen. Denn wenn ein Kunde beispielsweise zwei oder mehr Regale konfiguriert und in den Warenkorb gelegt hat, sich dann aber dazu entschließt, eines doch wieder daraus zu entfernen, wäre es unzumutbar, ihn dafür die entsprechenden Einzelteile in der jeweils richtigen Stückzahl auswählen zu lassen. Und selbst wenn ein Kunde diese Fleißarbeit auf sich nehmen würde, bestünde die Gefahr, dass am Ende Teile im Warenkorb verbleiben, die nicht zueinanderpassen und kein vollständiges oder kein ausreichend tragfähiges und damit ein vollkommen unsicheres Regal ergeben. Genau solche Szenarien galt es zu vermeiden.

Die technische Seite des Problems

Da ein Produktkonfigurator zum individuellen Zusammenstellen von Produkten entwickelt werden sollte, war es keine Option, von Anfang an auf fest vordefinierte Bündelprodukte zu setzen. Im Hinblick auf die drei unterschiedlichen Ausführungen von Regalen gab es jedoch die Möglichkeit, für jede der drei Traglasten ein komplexes Gruppenprodukt als Grundlage des Konfigurators zu definieren. Auf den drei Produktseiten steht dann jeweils ein Konfigurator bereit. Bei einem Gruppenprodukt werden allerdings am Ende nur die einzelnen Bestandteile, also einfache Produkte in den Warenkorb gelegt. Im Falle eines individuell konfigurierten Palettenregals kommt dabei schnell eine ganze Menge zusammen. Und wenn zusätzlich noch andere Produkte hinzukommen, ist im Warenkorb nicht mehr ersichtlich, welche Produkte zum Regal gehören und welche nicht.

Einfache Produkte aus Produktkonfigurator im Warenkorb

Um im Warenkorb das fertig zusammengestellte Regal als Einheit darzustellen, war es daher nötig, aus den Bestandteilen des Gruppenprodukts aus dem Konfigurator automatisch ein Bündelprodukt zu erzeugen und mit den entsprechenden Optionen zu versehen, wenn der Kunde das Regal in den Warenkorb legt. Ein Kunde, der zwei unterschiedliche Regale konfiguriert hat, findet in seinem Warenkorb dann genau zwei Produkte, die technisch gesehen Bündelprodukte sind und genau die zum jeweiligen Regal gehörenden Einzelteile enthalten.

Bündelprodukte aus Produktkonfigurator im Warenkorb

Die Lösung

In den Magento-Tests gibt es eine php-Datei die ein Bündelprodukt erzeugt. Zu finden ist sie unter tests/integration/testsuite/Magento/Bundle/_files/product.php.
Diese Datei haben wir als Grundlage für die Erzeugung des Bündelprodukts in unserem Anwendungsfall genutzt.

Um das Gruppenprodukt an der richtigen Stelle, also beim In-den-Warenkorb-Legen, in ein Bündelprodukt umzuwandeln, muss Magento\Checkout\Model\Cart angepasst werden. Die Methode addProduct($productInfo, $requestInfo = null) muss um diese Abfrage erweitert werden:


public function addProduct($productInfo, $requestInfo = null)
{
if ($productInfo->getTypeId() === 'grouped') {
   		 $entityId = $this->_createBundleCopy($productInfo, $requestInfo);
   		 $requestInfoNew = $this->_changeRequestInfo
($entityId,$requestInfo);  
		 // ………

Anhand der Methode _createBundleCopy($productInfo, $requestInfo) wird zunächst ein Bündelprodukt mit den dazugehörigen Optionen erstellt:


protected function _createBundleCopy($productInfo, $requestInfo) {
	// letzte vergebene increment-id
   	 $entityId = intval($this->_helper->getEntityId()) + 1;
	// Optionen für Bündelprodukt erstellen
   	 $this->_createOptionsArray($requestInfo);
   	 
   	 $productRepository = $this->_objectManager
->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);

   	 $product = $this->_objectManager->create
(\Magento\Catalog\Model\Product::class);
   	 $product->setTypeId('bundle')
   		 ->setId($entityId)
   		 ->setAttributeSetId(4)
   		 ->setWebsiteIds([1])
   		 ->setName($productInfo->getName().'-'.$entityId)
   		 ->setSku($productInfo->getName().'-'.$entityId)->setVisibility
    (\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
   		 ->setStockData(['use_config_manage_stock' => 1, 'qty' => 1,
 				     'is_qty_decimal' => 0,'is_in_stock' => 1])
   		 ->setPriceView(1)
   		 ->setPriceType(0)
   		 ->setShipmentType(1)
   		 ->setPrice(0);
	//………

Die Optionen für das Bündelprodukt werden in _createOptionsArray($requestInfo) erzeugt. Um hier die richtigen Zuordnungen zu erstellen, muss der Request genauer betrachtet werden. Ein Request für ein Gruppenprodukt sieht folgendermaßen aus:


[uenc] => aHR0cDovL2Fja3J1dGF0LW1pY2hhLnNwbGVuZGlkLWRldi5kZS9yZWdhbC1sZWljaHRlLWF1c2Z1aHJ1bmcuaHRtbA,,
 => 29
[selected_configurable_option] =>
[related_product] =>
[form_key] => 0YYmuQDRUOKvb2v4
[super_group] => Array
(
	[1] => 10
      [2] => 5
      [3] => 4
	[4] => 0
)

Hier steht das Array super_group im Mittelpunkt, da hieraus die Optionen für das Bündelprodukt entstehen müssen. Der jeweilige Schlüssel im Array ist die Entity-ID des einfachen Produkts. Der Value ist die Stückzahl für das gewählte Produkt. Aus diesen Informationen werden mit der Methode _createOptionsArray($requestInfo) die Optionen erzeugt. Da durch die Konfiguration des Gruppenprodukts genau bekannt ist, welche einfachen Produkte und auch wie viele der Kunde davon jeweils gewählt hat, lassen sich daraus exakt die passenden Optionen für das Bündelprodukt erstellen.


protected function _createOptionsArray($requestInfo) {
foreach($requestInfo['super_group'] as $key => $value) {
   		if(intval($value) !== 0) {
   		$this->_bundleOptionsData[]=  
array('title' => 'Name',
   			      'default_title' => 'Name',
   			      'type' => 'select',
   			      'required' => 0,
   			      'delete' => '');
   		$this->_bundleSelectionsData[] =  
array(array('product_id' => $key,
   			            'selection_qty' => $value,
   	                      	'selection_can_change_qty' => 1,
   			            'delete' => ''));
   		 }   		 
   	 }
}

Nun können die Optionen an das Bündelprodukt angefügt werden. Das wird im zweiten Teil der Methode _createBundleCopy erledigt.


protected function _createBundleCopy($productInfo, $requestInfo) {
	// ………..


	// Optionen an das Bündelprodukt anfügen
   	 if (count($this->_bundleOptionsData) > 0) {
   		 $options = [];
   		 foreach ($this->_bundleOptionsData as $key => $optionData) {
   			 if (!(bool)$optionData['delete']) {
   				 $option = $this->_objectManager->create
(\Magento\Bundle\Api\Data\OptionInterfaceFactory::class)
   					 ->create(['data' => $optionData]);
   				 $option->setSku($product->getSku());
   				 $option->setOptionId(null);

   				 $links = [];
   				 $bundleLinks = $this->_bundleSelectionsData;
   				 if (!empty($bundleLinks[$key])) {
   					 foreach ($bundleLinks[$key] as $linkData) {
   						 if (!(bool)$linkData['delete']) {
   							 $link = $this->_objectManager->create(\Magento\Bundle\Api\Data\LinkInterfaceFactory::class)
   								 ->create(['data' => $linkData]);
   							 $linkProduct = $productRepository->getById($linkData['product_id']);
   							 $link->setSku($linkProduct->getSku());
   							 $link->setQty($linkData['selection_qty']);
   							 if (isset($linkData['selection_can_change_qty'])) {
   								 $link->setCanChangeQuantity($linkData['selection_can_change_qty']);
   							 }
   							 $links[] = $link;
   						 }
   					 }
   					 $option->setProductLinks($links);
   					 $options[] = $option;
   				 }
   			 }
   		 }
   		 $extension = $product->getExtensionAttributes();
   		 $extension->setBundleProductOptions($options);
   		 $product->setExtensionAttributes($extension);
   	 }
   	 $product->save();
   	 return $entityId;
}

Nachdem die Optionen angefügt sind, wird das Bündelprodukt gespeichert und ist jetzt im Shop verfügbar.

Als nächstes muss der Request angepasst werden. Der Request muss von dem ursprünglichen Request für das Gruppenprodukt in einen Request für ein Bündelprodukt verändert werden. Hierzu lohnt der Blick auf einen Request für ein Bündelprodukt:


[uenc] => aHR0cDovL2Fja3J1dGF0LW1pY2hhLnNwbGVuZGlkLWRldi5kZS9idW5kbGV0ZXN0Lmh0bWw,
 => 34
[selected_configurable_option] =>
[related_product] =>
[form_key] => 0YYmuQDRUOKvb2v4
[bundle_option] => Array
(
   [3] => 15
   [4] => 18
)
[qty] => 1

Hier stehen im Array [bundle_option] die zum Bündelprodukt gehörigen Optionen. Aus dem Array [super_group] wurden ja schon mithilfe der Methode _createOptionsArray($requestInfo) die Optionen für das Bündelprodukt erstellt. Mit der Methode _changeRequestInfo($entityId, $requestInfo) wird nun der Request so umgebaut, dass er für ein Bündelprodukt verwendet werden kann. Dazu holen wir die Optionen aus der Datenbank. Die Optionen für dieses Bündelprodukt finden sich in der Tabelle catalog_product_bundle_selection. Aus dieser Tabelle müssen diejenigen Einträge herausgesucht werden, die in der Spalte parent_product_id die Entity-ID des Bündelproduktes beinhalten. Da vorher die Optionen so erstellt wurden, wie sie für diese Konfiguration benötigt werden, können nun die Optionen an den Request angefügt werden:


protected function _changeRequestInfo($entityId, $requestInfo) 
{
	 // Die Optionen des Bündelproduktes aus der Datenbank holen
   	 $result = $this->_helper->getBundleOptions($entityId);
   	 foreach($result as $res) {
   		 $option[$res['option_id']] = $res['selection_id'];
   	 }
   	 $requestNew = array();
   	 $requestNew['uenc'] = $requestInfo['uenc'];
	 // Hier die Entity-Id des Bündelproduktes eintragen
   	 $requestNew['product'] = $entityId;
   	 $requestNew['selected_configurable_option'] = 
$requestInfo['selected_configurable_option'];
   	 $requestNew['related_product'] = $requestInfo['related_product'];
   	 $requestNew['form_key'] = $requestInfo['form_key'];
   	 $requestNew['bundle_option'] = $option;
   	 $requestNew['qty'] = 1;
   	 return $requestNew;
    }

Nachdem der Request für ein Gruppenprodukt in einen Request für ein Bündelprodukt umgewandelt worden ist, kann das Produkt nun ganz normal in den Warenkorb gelegt werden. Das geschieht dann wieder in der Methode addProduct($productInfo, $requestInfo = null):


public function addProduct($productInfo, $requestInfo = null)
	{
   	 if ($productInfo->getTypeId() === 'grouped') {
		 $entityId = $this->_createBundleCopy($productInfo, $requestInfo);
   		 $requestInfoNew = $this->_changeRequestInfo
($entityId, $requestInfo);
   		 $bundleProduct = $this->_helper->getProductById($entityId);

   		 $product = $this->_getProduct($bundleProduct);
   		 $request = $this->_getProductRequest($requestInfoNew);
   		 $productId = $product->getId();
   	 } else

	// ……

Die weitere Verarbeitung ist dann Magento Standard.

Eines gilt es dabei jedoch zu beachten: Mit dieser Lösung wird bei jedem Einkauf aus dem Gruppenprodukt immer wieder ein neues Bündelprodukt entstehen. Wir haben das so gelöst, dass das entsprechende Bündelprodukt nach jedem Kauf eines solchen Produktes automatisch wieder aus dem Shop gelöscht wird.

Newsletter abonnieren

Melden Sie sich für unseren Newsletter an und lassen Sie sich monatlich über unsere neuesten Beiträge informieren!

    Kontakt

    Genug über uns – lassen Sie uns darüber sprechen, wie wir Ihnen helfen können.