Product Cross-Selling

This section describes how the module maps product cross-selling to Shopware.

The data models of Shopware and synQup are very different regarding cross-selling. That's why this page provides some background information before giving you an example on how to create a cross-selling.

Quick Reference

  • The field Product::relations is a collection that contains zero to many documents of type Relation.
  • Each Relation will be converted to a product_cross_selling in Shopware.
  • Each Relation is identified by a combination of its parent product identifier and its position in the Product::relations collection
  • The field Relation::products contains references to Product documents
  • Each referenced Product of that relation will be assigned to the cross-selling in Shopware (= converted to an entry in product_cross_selling_assigned_products)
  • There are two different types of cross-selling available in Shopware, but only the type productList (= "manual selection" in the Shopware backend) is supported by the module

Subsections

The following subsections are involved in mapping cross-selling:

output
└───product-cross-selling
    └───product-cross-selling-build-cache
    └───product-cross-selling-validate
    └───product-cross-selling-upsert

Configuration

{
  "subsections": {
    "productCrossSelling": {
      "enabled": true
    }
  }
}

Mapping Table

The actual source document is a Elio\CommerceBundle\Document\Product\Relation that is part of the Product::relations collection.

Target Field Source Path
*T name label
position position
active active
* productId mapped automatically - determined from the product of which the Relation was extracted from
* type mapped automatically - static value productList (= "manual selection" in the Shopware backend)
assignedProducts products
Each referenced Product of that relation will be assigned to the cross selling

Cross-Selling in Shopware

  • In Shopware every cross-selling is stored in the table product_cross_selling (and product_cross_selling_translation).
  • The products assigned to that cross-selling are stored in the junction-table product_cross_selling_assigned_products.
  • There are two different types of cross-selling available in Shopware, but only the type productList (= "manual selection" in the Shopware backend) is supported by the module.

Cross-Selling in synQup

  • The embedded document Relation is used to generate product_cross_sellings in Shopware.
  • Every Relation that is part of the Product::relations collection will be converted to a cross-selling assigned to that specific product
  • All referenced products of that relation (see Relation::products collection) will be assigned to the product_cross_selling.

Cross-Selling in the Output-Module

Due to differences of the data models the module has to perform several steps in order to map a Relation to a ProductCrossSellingEntity:

  • All Relations are extracted from their owning Products in the extract-embedded subsections
  • Since a Relation does not provide an identifier the module has to generate custom identifiers (see next section)
  • The module searches the corresponding product_cross_selling entries in Shopware by the help of the previously generated identifier
  • The module converts the Relation to ProductCrossSellingEntity-objects and sends them to Shopware

Identifying Cross-Sellings

The source of a product_cross_selling in Shopware is the document Relation. However, the Relation does not have an identifier. That's why the module generates an identifier with the following pattern for every Relation: {product-identifier}-{collection-key* of Relation::products}}
* the collection-key is the index/position in the collection

Example: The third Relation that is embedded in a product with the identifier "12345" gets the identifier 12345-3.

The assigned products are not identified at all. This means that the assigned products (= entries of product_cross_selling_assigned_products) are deleted and recreated on each update of the cross-selling.

Translations

There is a bug in Shopware 6 that prevents translatable cross-sellings from being created. Therefore, translations for the non-system language appear only after an update.

Example - How to Create a Cross-Selling

In this example we are going to generate three products:

  • Product A (Notebook) is the product whose detail page should display a cross-selling
  • Product B (Keyboard) and C (Mouse) are the products that should be shown as accessory articles on the page of Product A

class ProductCrossSellingsGenerator
{
    public function generate(ModuleJobDispatchContext $context): void
    {
        // create products
        $notebook = $this->createProduct('notebook', 'A very good notebook', 'Ein wirklich gutes Notebook', 2400);
        $similarNotebook = $this->createProduct('similar-notebook', 'Another very good notebook', 'Ein anderes wirklich gutes Notebook', 2400);
        $keyboard = $this->createProduct('keyboard', 'A pretty good keyboard', 'Eine ganz gute Tastatur', 15);
        $mouse = $this->createProduct('mouse', 'A mouse', 'Eine Maus', 5);

        // create cross sellings / relations
        $accessoryItems = new Relation(
            "accessory",
            TranslationCollection::create([Locale::en_GB, 'Notebook Accessory Items'], [Locale::de_DE, 'Notebook Zubehör']),
            new ArrayCollection([$keyboard, $mouse])
        );

        $similarItems = new Relation(
            "similar",        
            TranslationCollection::create([Locale::en_GB, 'Similar Items'], [Locale::de_DE, 'Ähnliche Gegenstände']),
            new ArrayCollection([$similarNotebook])
        );

        $notebook->setRelations(new ArrayCollection([$accessoryItems, $similarItems]));

        // persist and flush
        // ...
    }

    private function createProduct(string $identifier, string $englishName, string $germanName, int $netPrice): Product
    {
        $product = $this->findOrCreate($identifier);
        $taxGroup = $this->documentManager->getRepository(TaxGroup::class)->findOneBy(['identifier' => 'default tax group']);
        $product->setTaxGroup($taxGroup);
        $name = TranslationCollection::create([Locale::en_GB, $englishName], [Locale::de_DE, $germanName]);
        $product->setGeneralInformation(new GeneralInformation($name));
        $product->setDefaultPrice(new SellingPrice($netPrice));
        return $product;
    }

}

This is the result in the Shopware administration panel:

Cross Selling Result 1

Cross Selling Result 2