von der Tier-Price bis zum unsichtbaren B2B-Katalog
Kundengruppen sind in Magento 2 der zentrale Mechanismus, um Preise und Sichtbarkeit gezielt nach Kundensegment zu steuern: von Großhandelspreisen über verborgene B2B-Kataloge bis zu individuellen Staffelpreisen pro Gruppe. Wer Catalog Price Rules, Tier Pricing, Sichtbarkeits-Plugins und Shared Catalog richtig kombiniert, kann komplexe B2B- und B2C-Preislogik ohne Custom-Checkout-Hacks abbilden, muss dabei aber Reindex-Zyklen, Scoping-Fehler und die Sonderrolle der Gruppe NOT LOGGED IN im Griff behalten.
Inhaltsverzeichnis
- 1. Kundengruppen als Steuerungsebene verstehen
- 2. Kundengruppen anlegen und Steuerklassen zuweisen
- 3. Catalog Price Rules pro Kundengruppe scopen
- 4. Tier Pricing und Special Price je Kundengruppe
- 5. Sichtbarkeitssteuerung per Plugin auf der Produktkollektion
- 6. Kunden programmatisch Gruppen zuweisen
- 7. B2B-Szenarien: Großhandel und versteckte Kataloge
- 8. Shared Catalog vs. reine Kundengruppen
- 9. Performance, Reindex und typische Fallstricke
- 10. Zusammenfassung
- 11. FAQ
1. Kundengruppen als Steuerungsebene verstehen
Kundengruppen sind in Magento 2 kein reines Segmentierungsmerkmal für Marketing-Reports, sondern eine vollwertige Steuerungsebene, die tief in Preisberechnung, Steuerklassenlogik und Sichtbarkeit eingreift. Jeder Kunde und jeder Gast gehört genau einer Kundengruppe an, referenziert über das Attribut group_id auf dem Kunden- beziehungsweise Quote-Objekt. Diese ID fließt in praktisch jede preisrelevante Berechnung ein: in die Tier-Price-Ermittlung, in die Anwendung von Catalog Price Rules, in den Preisindex und in die Steuerklassenzuordnung über die Kunden-Steuerklasse.
Der entscheidende architektonische Vorteil von Kundengruppen gegenüber individuellen Sonderkonditionen pro Kunde ist die Skalierbarkeit. Statt für jeden B2B-Kunden eine eigene Preisliste zu pflegen, definiert man eine begrenzte Anzahl an Gruppen wie Großhandel, Vertragspartner oder Premium-Reseller und weist Kunden dieser Gruppe zu. Preisregeln, Tier Prices und Sichtbarkeitsregeln werden dann einmal pro Gruppe gepflegt und wirken auf alle Mitglieder gleichzeitig. Diese Indirektion ist der Kern jeder skalierbaren Kundengruppen-Preisregeln-Strategie in mittleren und großen Magento-Installationen.
Dieser Artikel konzentriert sich bewusst auf die operative Anwendung: wie man Kundengruppen anlegt, wie Preisregeln und Tier Prices sie referenzieren, wie Sichtbarkeit über einen Plugin-Ansatz zusätzlich zur Preislogik gesteuert wird und welche Reindex-Mechanik im Hintergrund läuft. Architektur- und Designpattern-Grundlagen zu Magento 2 werden hier vorausgesetzt und nicht wiederholt.
2. Kundengruppen anlegen und Steuerklassen zuweisen
Neue Kundengruppen werden im Backend unter Stores > Customers > Customer Groups angelegt. Jede Gruppe besteht aus einem Namen und einer zugewiesenen Steuerklasse (Tax Class), die über Magento\Tax\Model\ClassModel verwaltet wird. Diese Zuordnung ist der erste Preis-Hebel: Eine B2B-Gruppe kann auf eine Steuerklasse ohne Umsatzsteuerausweis gemappt werden, während die Standard-Retail-Gruppe die reguläre Steuerklasse mit Bruttopreisen nutzt. Die Steuerklasse wirkt zusammen mit der Tax Rule, die Kundensteuerklasse und Produktsteuerklasse zu einem konkreten Steuersatz kombiniert.
Vier Kundengruppen existieren in jeder frischen Magento-Installation als Systemgruppen: NOT LOGGED IN (ID 0), General (ID 1), Wholesale (ID 2) und Retailer (ID 3). Die Gruppe mit ID 0 hat eine Sonderrolle, die im Abschnitt zu Fallstricken vertieft wird: Sie gilt für jeden nicht eingeloggten Besucher und damit auch für Preisanfragen über die Storefront-GraphQL-API ohne Customer-Token. Neue Gruppen erhalten automatisch die nächste freie ID, wichtig ist dabei, diese IDs in Deployment-Skripten niemals hart zu kodieren, sondern per CustomerGroupRepositoryInterface über den Gruppennamen aufzulösen, da IDs zwischen Umgebungen (Dev, Staging, Live) abweichen können, wenn Gruppen in unterschiedlicher Reihenfolge angelegt wurden.
Für die programmatische Anlage neuer Kundengruppen, etwa in einem Setup-Script eines eigenen Moduls, nutzt man Magento\Customer\Api\GroupRepositoryInterface mit einem GroupInterface-Datenobjekt. Dabei wird der Gruppenname, die Steuerklassen-ID und optional das Attribut tax_class_id für die B2B-relevante Steuerlogik gesetzt. Wichtig bei der Dual-Vendor-Pflege eigener Erweiterungen: Wird eine Kundengruppe als Teil eines eigenen Moduls (etwa Mironsoft_CustomerGroupTools) via Data-Patch angelegt, sollte der Patch idempotent sein und vor dem Anlegen prüfen, ob eine Gruppe mit demselben Namen bereits existiert, um Duplikate bei mehrfacher Ausführung zu verhindern.
3. Catalog Price Rules pro Kundengruppe scopen
Catalog Price Rules (Marketing > Catalog Price Rule) sind der wichtigste Mechanismus, um Rabatte oder Aufschläge automatisiert auf Produktebene für bestimmte Kundengruppen anzuwenden, ohne jedes Produkt einzeln zu bearbeiten. Jede Regel besteht aus Bedingungen (Conditions), die auf Produktattribute, Kategorien oder Kundengruppe matchen, und Aktionen (Actions), die einen prozentualen Rabatt, einen Fixbetrag oder einen Festpreis setzen. Das Feld Customer Groups im Regel-Formular ist eine Mehrfachauswahl und definiert, für welche Gruppen die Regel überhaupt in Betracht gezogen wird, unabhängig von den übrigen Conditions.
Der entscheidende technische Punkt: Catalog Price Rules werden nicht zur Laufzeit auf jede Preisabfrage angewendet, sondern in den Preisindex vorgerechnet. Der Indexer catalog_rule_price berechnet für jede Kombination aus Produkt, Website, Kundengruppe und Datum den effektiven Regelpreis und schreibt ihn in die Tabelle catalogrule_product_price. Erst danach übernimmt der Indexer catalog_product_price diesen Wert in den finalen Preisindex catalog_product_index_price. Wird eine Regel nur für die Gruppe Wholesale angelegt, aber der Cronjob catalog_rule_price läuft nicht rechtzeitig, sehen Wholesale-Kunden weiterhin den alten Preis, bis der nächste Reindex-Zyklus durchläuft.
Ein häufiger Scoping-Fehler bei Kundengruppen-Preisregeln entsteht, wenn eine Regel versehentlich für "All Customer Groups" statt für die konkret gewünschte Gruppe angelegt wird. Da Catalog Price Rules nach Priorität sortiert angewendet werden und die Option "Discard subsequent rules" existiert, kann eine breit gescopte Regel eine spätere, enger gescopte Regel für eine spezifische Gruppe überschreiben oder verhindern, dass sie überhaupt zur Anwendung kommt. Die Priorität sollte daher immer so gesetzt werden, dass spezifischere, gruppenbezogene Regeln vor generischen Regeln ausgewertet werden, und "Discard subsequent rules" nur bewusst und dokumentiert eingesetzt werden.
# Nach jeder Änderung an Catalog Price Rules oder deren
# Kundengruppen-Zuordnung: Regel-Preisindex und finalen Preisindex neu aufbauen
bin/magento indexer:reindex catalogrule_rule
bin/magento indexer:reindex catalogrule_product
bin/magento indexer:reindex catalog_product_price
# Status aller preisrelevanten Indexer prüfen
bin/magento indexer:status
# Cron-Job, der catalogrule_price im Hintergrund aktualisiert, manuell anstoßen
bin/magento cron:run --group index
4. Tier Pricing und Special Price je Kundengruppe
Tier Pricing erlaubt mengenabhängige Staffelpreise, die zusätzlich pro Kundengruppe unterschiedlich definiert werden können. Über den Service Contract Magento\Catalog\Api\Data\ProductInterface in Kombination mit Magento\Catalog\Api\Data\ProductTierPriceInterface wird jeder Tier-Price-Eintrag mit Menge (qty), Preis oder Prozent-Rabatt (percentage_value), Kundengruppen-ID und optional Website-ID gespeichert. Die spezielle Kundengruppen-ID 0 in diesem Kontext bedeutet "All Groups" und ist von der Kundengruppe NOT LOGGED IN (ebenfalls ID 0 im customer_group-Kontext) semantisch zu unterscheiden, was in der Praxis für Verwirrung sorgt und beim Debugging beachtet werden muss.
Programmatisch setzt man Tier Prices über ProductRepositoryInterface::save() nach dem Befüllen der tier_prices-Property des Produkts, oder granularer über den seit Magento 2.2 verfügbaren Magento\Catalog\Api\ProductTierPriceManagementInterface, der gezielt einzelne Tier-Price-Einträge lesen, hinzufügen und entfernen kann, ohne das komplette Produkt neu zu speichern. Das ist insbesondere bei Massenoperationen über viele Produkte hinweg deutlich performanter als ein vollständiges ProductRepositoryInterface::save() je Produkt, da Letzteres den kompletten Produkt-Save-Zyklus inklusive aller Plugins und Observer durchläuft.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerGroupTools\Service;
use Magento\Catalog\Api\Data\ProductTierPriceInterface;
use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory;
use Magento\Catalog\Api\ProductTierPriceManagementInterface;
use Magento\Customer\Api\GroupRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
/**
* Assigns wholesale tier pricing to a product for a specific customer group.
*/
final class WholesaleTierPriceAssigner
{
/**
* @param ProductTierPriceManagementInterface $tierPriceManagement Service contract for granular tier price CRUD
* @param ProductTierPriceInterfaceFactory $tierPriceFactory Factory for tier price data objects
* @param GroupRepositoryInterface $groupRepository Resolves customer group id by name
*/
public function __construct(
private readonly ProductTierPriceManagementInterface $tierPriceManagement,
private readonly ProductTierPriceInterfaceFactory $tierPriceFactory,
private readonly GroupRepositoryInterface $groupRepository,
) {
}
/**
* Adds a quantity-based tier price for the "Wholesale" customer group without
* touching any other tier price entry on the product.
*
* @param string $sku Target product SKU
* @param int $qty Minimum quantity for the tier
* @param float $price Absolute tier price
* @return void
* @throws NoSuchEntityException if the wholesale group does not exist
*/
public function assignWholesaleTier(string $sku, int $qty, float $price): void
{
$groupId = $this->resolveGroupId('Wholesale');
/** @var ProductTierPriceInterface $tierPrice */
$tierPrice = $this->tierPriceFactory->create();
$tierPrice->setCustomerGroupId($groupId);
$tierPrice->setQty($qty);
$tierPrice->setValue($price);
$existing = $this->tierPriceManagement->getList($sku);
$existing[] = $tierPrice;
$this->tierPriceManagement->add($sku, $existing);
}
/**
* Resolves a customer group id by its label, avoiding hardcoded group ids
* that may differ between environments.
*
* @param string $groupName Human readable customer group name
* @return int Resolved group id
* @throws NoSuchEntityException if no matching group is found
*/
private function resolveGroupId(string $groupName): int
{
$searchResult = $this->groupRepository->getList(
$this->groupRepository->getList(
(new \Magento\Framework\Api\SearchCriteriaBuilder())->create()
)->getItems()
);
foreach ($searchResult as $group) {
if ($group->getCode() === $groupName) {
return (int) $group->getId();
}
}
throw new NoSuchEntityException(__('Customer group "%1" not found', $groupName));
}
}
Special Price (das Feld special_price mit optionalem Zeitfenster über special_from_date und special_to_date) ist im Gegensatz zu Tier Pricing nicht direkt an Kundengruppen gebunden, kann aber in Kombination mit einer Catalog Price Rule gruppenspezifisch überschrieben werden. In der Praxis empfiehlt es sich, Special Price für zeitlich befristete, gruppen-unabhängige Aktionspreise zu nutzen und Tier Pricing beziehungsweise Catalog Price Rules für alles, was an eine Kundengruppe gebunden ist.
5. Sichtbarkeitssteuerung per Plugin auf der Produktkollektion
Die native Catalog Product Visibility in Magento 2 (Not Visible Individually, Catalog, Search, Catalog/Search) kennt von Haus aus keine Kundengruppen-Dimension. Für B2B-Szenarien, in denen bestimmte Produkte ausschließlich für eine Gruppe wie Wholesale sichtbar sein sollen, während sie für General und NOT LOGGED IN vollständig verborgen bleiben, reicht die Standard-Visibility nicht aus. Hier setzt eine gezielte Sichtbarkeitssteuerung per Plugin auf der Produktkollektion an, die die Preis- und Bestandsebene um eine Zugriffsebene ergänzt.
Der sauberste Ansatz ist ein Plugin auf Magento\Catalog\Model\ResourceModel\Product\Collection, das nach dem Laden der Kollektion (afterLoad) oder vor dem finalen Query-Aufbau (beforeLoad) einen Filter auf ein eigenes Produktattribut wie restricted_customer_groups anwendet. Alternativ, und für Suchergebnisse konsistenter, greift man in die Such- und Kategorie-Layer-Schicht ein, indem man einen Plugin auf Magento\CatalogSearch\Model\Layer\Filter\... oder direkter auf den Magento\Framework\Search\Request\Builder setzt, um Produkte mit unpassender Kundengruppen-Zuordnung bereits auf Elasticsearch/OpenSearch-Query-Ebene auszuschließen, statt sie nachträglich in PHP aus dem Resultset zu filtern.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerGroupTools\Plugin;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Customer\Model\Session as CustomerSession;
use Magento\Framework\App\ResourceConnection;
/**
* Restricts the visible product collection based on the current customer's
* group, so B2B-only products stay hidden from other customer groups.
*/
final class RestrictProductCollectionByCustomerGroup
{
/**
* @param CustomerSession $customerSession Provides the current customer group id
* @param ResourceConnection $resourceConnection Direct DB access for the EAV attribute join
*/
public function __construct(
private readonly CustomerSession $customerSession,
private readonly ResourceConnection $resourceConnection,
) {
}
/**
* Joins the restricted_customer_groups attribute and filters out products
* that explicitly exclude the current customer group.
*
* @param Collection $subject Product collection being loaded
* @return void
*/
public function beforeLoad(Collection $subject): void
{
if ($subject->isLoaded()) {
return;
}
$groupId = (int) $this->customerSession->getCustomerGroupId();
$attributeCode = 'restricted_customer_groups';
if (!$subject->getAttribute($attributeCode)) {
return;
}
$subject->addAttributeToSelect($attributeCode);
$subject->addFieldToFilter(
[$attributeCode, $attributeCode],
[
['null' => true],
['nfin' => [$groupId]],
]
);
}
}
Wichtig bei diesem Plugin-Ansatz zur Sichtbarkeitssteuerung: Er greift zusätzlich zur regulären Visibility, ersetzt sie aber nicht. Produkte müssen weiterhin korrekt auf Catalog/Search gesetzt sein, das Plugin entfernt lediglich zusätzlich Produkte, deren Kundengruppen-Restriktion nicht zur aktuellen Sitzung passt. Für Direktzugriffe über die Produkt-Detailseiten-URL sollte zusätzlich ein Plugin auf Magento\Catalog\Model\Product oder ein Observer auf catalog_controller_product_init_after eine 404-Weiterleitung auslösen, sonst bleibt die URL trotz versteckter Kategorien- und Suchergebnisse direkt aufrufbar.
6. Kunden programmatisch Gruppen zuweisen
Die programmatische Zuweisung von Kunden zu Kundengruppen läuft über Magento\Customer\Api\CustomerRepositoryInterface. Das CustomerInterface-Datenobjekt trägt das Attribut group_id direkt, sodass ein einfaches setGroupId() gefolgt von save() ausreicht, um einen Kunden neu zuzuordnen. Für Massenumzüge, etwa wenn hunderte Bestandskunden nach einer Vertragsänderung von General in eine neue B2B-Gruppe verschoben werden müssen, ist ein Aufruf pro Kunde über den Repository-Save-Zyklus jedoch ineffizient, da jeder Aufruf Events, Observer und Indexer-Invalidierungen triggert.
Für solche Bulk-Reassignments empfiehlt sich ein eigenes CLI-Command, das über Magento\Framework\Console\Cli beziehungsweise die Symfony-Console-Integration von Magento registriert wird und intern entweder in kontrollierten Batches über das Repository iteriert oder, bei sehr großen Mengen, einen direkten Bulk-Update auf die customer_entity-Tabelle ausführt, gefolgt von einem gezielten Reindex der betroffenen Kunden-IDs statt eines vollständigen Reindexes.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerGroupTools\Console\Command;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* CLI command for bulk-reassigning customers from one customer group to another,
* e.g. after a wholesale contract migration.
*/
final class ReassignCustomerGroupCommand extends Command
{
/**
* @param CustomerRepositoryInterface $customerRepository Service contract for customer read/write
* @param SearchCriteriaBuilder $searchCriteriaBuilder Builds the filter for the source group
*/
public function __construct(
private readonly CustomerRepositoryInterface $customerRepository,
private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
?string $name = null,
) {
parent::__construct($name);
}
/**
* Configures command name, description and required arguments.
*
* @return void
*/
protected function configure(): void
{
$this->setName('mironsoft:customer-group:reassign')
->setDescription('Bulk-reassigns customers from one customer group to another')
->addArgument('sourceGroupId', InputArgument::REQUIRED, 'Source customer group id')
->addArgument('targetGroupId', InputArgument::REQUIRED, 'Target customer group id');
}
/**
* Executes the reassignment, in batches of 200 customers, and reports progress.
*
* @param InputInterface $input Console input
* @param OutputInterface $output Console output
* @return int Exit code
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$sourceGroupId = (int) $input->getArgument('sourceGroupId');
$targetGroupId = (int) $input->getArgument('targetGroupId');
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('group_id', $sourceGroupId, 'eq')
->setPageSize(200)
->create();
$result = $this->customerRepository->getList($searchCriteria);
$total = $result->getTotalCount();
$output->writeln(sprintf('<info>Found %d customers in group %d</info>', $total, $sourceGroupId));
foreach ($result->getItems() as $customer) {
$customer->setGroupId($targetGroupId);
$this->customerRepository->save($customer);
}
$output->writeln('<info>Reassignment complete. Run indexer:reindex customer_grid and catalog_product_price.</info>');
return Command::SUCCESS;
}
}
Der Hinweis am Ende der Ausgabe ist kein Nebensatz: Nach einer Massenverschiebung zwischen Kundengruppen muss der Preisindex zwingend neu aufgebaut werden, da gruppenspezifische Tier Prices und Catalog Price Rules erst nach dem Reindex korrekt für die neu zugeordneten Kunden greifen. Dieser Schritt wird in der Praxis überraschend häufig vergessen, weil das Repository-Save selbst keinen Fehler wirft, wenn der Index veraltet ist, das Frontend zeigt dann einfach kommentarlos die alten Preise.
7. B2B-Szenarien: Großhandel und versteckte Kataloge
Das klassische B2B-Szenario mit Kundengruppen ist eine Wholesale-Gruppe mit ausgehandelten Nettopreisen, die über eine Kombination aus gruppenspezifischem Tier Pricing für Staffelmengen und einer Catalog Price Rule für pauschale Rabatte auf ganze Kategorien abgebildet wird. Der Vorteil dieser Kombination gegenüber reinem Tier Pricing: Neue Produkte, die in eine rabattierte Kategorie einsortiert werden, erhalten den Wholesale-Rabatt automatisch über die Regel-Condition auf die Kategorie, ohne dass für jedes neue Produkt manuell ein Tier-Price-Eintrag gepflegt werden muss.
Ein zweites verbreitetes Szenario ist der vollständig verborgene B2B-Katalog: Alle Preise und teilweise auch die Produkte selbst sind für die Gruppe NOT LOGGED IN unsichtbar, und erst nach Login mit einer freigeschalteten B2B-Kundengruppe erscheinen Preise und vollständiger Produktkatalog. Technisch kombiniert man dafür das Sichtbarkeits-Plugin aus Abschnitt 5 mit einer Layout-Anpassung, die den Preisblock (Magento\Catalog\Block\Product\Price beziehungsweise dessen Hyvä-ViewModel-Pendant) für NOT LOGGED IN durch einen "Preis auf Anfrage"- oder "Jetzt anmelden"-Hinweis ersetzt. Wichtig ist, diese Logik nicht nur im Frontend-Template zu verstecken, sondern auch API-seitig: Die GraphQL-Preisabfrage muss ebenfalls die Kundengruppe berücksichtigen, sonst lässt sich der versteckte Preis über einen direkten API-Call trotzdem auslesen.
Ein drittes Muster ist die gestaffelte Freischaltung: Eine neu registrierte B2B-Kundengruppe (etwa "B2B Pending") sieht zunächst gar keine Preise, bis ein Vertriebsmitarbeiter den Kunden nach Bonitätsprüfung manuell in die Gruppe "B2B Approved" mit vollem Preiszugriff verschiebt. Diese Freischaltung lässt sich über ein Admin-UI-Element im Kunden-Edit-Grid realisieren, das im Hintergrund exakt den in Abschnitt 6 gezeigten CustomerRepositoryInterface::save()-Aufruf nutzt, ergänzt um eine E-Mail-Benachrichtigung per Observer auf das customer_save_after-Event.
8. Shared Catalog vs. reine Kundengruppen
Shared Catalog ist eine Commerce-exklusive Funktion (nicht in Magento Open Source verfügbar), die auf Kundengruppen aufsetzt, aber zusätzlich eine granulare Katalogsicht mit individuellen Preislisten pro Firmenkonto bietet, ohne für jede Preisvariante eine eigene Kundengruppe anlegen zu müssen. Ein Shared Catalog referenziert intern trotzdem eine Kundengruppe, erweitert sie aber um eine eigene Preisliste (company_credit- und shared_catalog-Tabellen), die feingranularer editierbar ist als eine klassische Catalog Price Rule.
Für Magento Open Source, wo Shared Catalog nicht zur Verfügung steht, ist die Kombination aus mehreren spezifischen Kundengruppen plus einem ACL- und Plugin-Ansatz der pragmatische Ersatz: Für jede benötigte Preisstufe wird eine eigene Kundengruppe angelegt, die Sichtbarkeits- und Preislogik wird wie in den Abschnitten 3 bis 5 beschrieben über Catalog Price Rules, Tier Pricing und ein Sichtbarkeits-Plugin abgebildet. Der Nachteil dieses Ansatzes ist der höhere Pflegeaufwand bei sehr vielen individuellen Firmenkonditionen, da jede neue Preisstufe eine neue Kundengruppe mit eigener Regel-Pflege bedeutet, während Shared Catalog diese Flexibilität nativ mit einem Bruchteil des administrativen Aufwands bietet.
| Mechanismus | Einsatzzweck | Skalierbarkeit | Pflegeaufwand | Edition |
|---|---|---|---|---|
| Tier Pricing | Mengenstaffel je Kundengruppe | Gut bei wenigen Gruppen | Pro Produkt manuell oder per Service Contract | Open Source & Commerce |
| Catalog Price Rule | Kategorie- oder attributweite Rabatte | Sehr gut, wirkt auf viele Produkte gleichzeitig | Gering, zentral gepflegt | Open Source & Commerce |
| Shared Catalog | Individuelle Firmenpreislisten | Sehr gut bei vielen Firmenkonten | Gering dank nativer UI | Nur Commerce |
| Sichtbarkeits-Plugin | Produkte je Kundengruppe verbergen | Gut, erfordert Custom-Attribut | Mittel, Entwicklungsaufwand einmalig | Open Source & Commerce |
| Special Price | Zeitlich befristete Aktionspreise | Nicht gruppen-spezifisch nativ | Gering, pro Produkt | Open Source & Commerce |
Die Tabelle zeigt, dass die Wahl des Mechanismus stark vom konkreten Kundengruppen-Szenario abhängt: Wenige, klar definierte B2B-Gruppen mit stabilen Konditionen kommen mit Catalog Price Rules und Tier Pricing weit, während viele individuelle Firmenkonditionen ohne Commerce-Lizenz nur über einen deutlich höheren manuellen Pflegeaufwand mit reinen Kundengruppen abbildbar sind.
9. Performance, Reindex und typische Fallstricke
Jede zusätzliche Kundengruppe vervielfacht potenziell die Größe des Preisindex, da catalog_product_index_price pro Kombination aus Produkt, Website und Kundengruppe eine eigene Zeile führt. Bei einem Katalog mit 100.000 Produkten und 10 aktiven Kundengruppen über 2 Websites können das schnell mehrere Millionen Indexzeilen werden. Der Vollindex-Lauf (bin/magento indexer:reindex catalog_product_price) skaliert entsprechend mit der Anzahl der Gruppen, und bei sehr vielen Gruppen mit individuellen Tier Prices kann die Reindex-Zeit signifikant steigen. In der Praxis lohnt es sich, ungenutzte Kundengruppen konsequent zu deaktivieren oder zu löschen, statt sie "for future use" bestehen zu lassen, da jede Gruppe dauerhaft in jeden Preisindex-Lauf einfließt.
Der häufigste Fallstrick bei Kundengruppen-Preisregeln ist das Vergessen des Reindex nach einer Tier-Price-Änderung. Wird ein Tier Price über ProductRepositoryInterface::save() oder ProductTierPriceManagementInterface::add() gesetzt, während der Indexer-Modus auf "Update by Schedule" statt "Update on Save" steht, wird die Änderung nicht sofort im Preisindex sichtbar, sondern erst beim nächsten Cron-Lauf des indexer_reindex_all_invalid-Jobs. Für Massenimporte über CLI-Commands sollte man daher entweder den Reindex explizit am Ende des Scripts anstoßen oder, bei sehr großen Datenmengen, gezielt nur die betroffenen Produkt-IDs partiell reindexieren, statt einen kompletten Vollindex zu erzwingen.
Ein zweiter klassischer Fehler ist das falsche Scoping einer Catalog Price Rule, bei dem ein Rabatt versehentlich auch für die Gruppe NOT LOGGED IN aktiviert bleibt, obwohl er ausschließlich für Wholesale gedacht war. Da NOT LOGGED IN die Standardgruppe für Gäste und für nicht authentifizierte GraphQL-Anfragen ist, "leakt" ein falsch gescopter B2B-Rabatt in diesem Fall direkt auf die öffentlich sichtbare Storefront und kann zu erheblichen finanziellen Fehlkalkulationen führen. Ein regelmäßiger Audit aller aktiven Catalog Price Rules mit Fokus auf die zugewiesenen Kundengruppen sollte fester Bestandteil des Preispflege-Prozesses sein, insbesondere nach Regeländerungen durch Marketing-Teams ohne technischen Hintergrund.
10. Zusammenfassung
Kundengruppen sind in Magento 2 die zentrale Steuerungsebene für Preise und Sichtbarkeit: Über Steuerklassen, Catalog Price Rules, Tier Pricing und ein Sichtbarkeits-Plugin auf der Produktkollektion lassen sich B2B-Preislogik, versteckte Kataloge und gestaffelte Freischaltungen ohne Commerce-Lizenz abbilden. Programmatische Gruppenzuweisung über CustomerRepositoryInterface ermöglicht dabei sowohl Einzel- als auch Bulk-Reassignments, etwa bei einer Vertriebsfreigabe nach Bonitätsprüfung.
Wer ohne Commerce-Lizenz auf Shared Catalog verzichten muss, kombiniert stattdessen mehrere spezifische Kundengruppen mit Catalog Price Rules und Tier Pricing, akzeptiert dafür aber höheren Pflegeaufwand bei sehr vielen individuellen Firmenkonditionen. Entscheidend für den produktiven Betrieb sind zwei Disziplinen: konsequenter Reindex nach jeder Tier-Price-Änderung und ein regelmäßiger Audit aller Catalog Price Rules gegen versehentliches NOT-LOGGED-IN-Scoping.
Kundengruppen und Preisregeln, das Wichtigste auf einen Blick
Preislogik
Catalog Price Rules für kategorienweite Rabatte, Tier Pricing für Mengenstaffeln je Kundengruppe.
Sichtbarkeit
Plugin auf der Produktkollektion steuert Sichtbarkeit, GraphQL-Preisabfrage muss dieselbe Logik teilen.
Shared Catalog vs. Open Source
Shared Catalog nur in Commerce, sonst mehrere Kundengruppen als pragmatischer Ersatz.
Betrieb
Reindex nach Tier-Price-Änderungen nicht vergessen, Catalog Price Rules regelmäßig auf NOT-LOGGED-IN-Scoping prüfen.
11. FAQ: Kundengruppen und Preisregeln in Magento 2
1Was steuern Kundengruppen konkret?
2Catalog Price Rule oder Tier Pricing?
3Wie verstecke ich Preise für Gäste?
4Wie weise ich Kunden programmatisch zu?
5Was ist gestaffelte B2B-Freischaltung?
6Shared Catalog oder reine Kundengruppen?
7Warum wächst der Preisindex mit jeder Gruppe?
8Warum ist eine Tier-Price-Änderung nicht sofort sichtbar?
9Wie verhindere ich ein Rabatt-Leck an Gäste?
10Sollte ich ungenutzte Gruppen löschen?
Mironsoft
Magento 2 B2B-Preislogik, Kundengruppen und Sichtbarkeitssteuerung
Kundengruppen, die Ihre Preisstruktur wirklich abbilden?
Wir analysieren bestehende Kundengruppen-Setups, korrigieren fehlerhaftes Scoping bei Catalog Price Rules und implementieren Tier Pricing, Sichtbarkeits-Plugins und Reindex-Strategien für skalierbare B2B-Preislogik.
B2B-Preislogik
Tier Pricing und Catalog Price Rules sauber pro Kundengruppe gescopt und dokumentiert
Segmentierung
Sichtbarkeits-Plugins und versteckte Kataloge für B2B-only Bereiche und Freischaltungs-Workflows
Reindex-Strategie
Preisindex-Performance bei vielen Kundengruppen optimieren und Bulk-Reassignment-Tools bauen