hybris Software-End-to-End Multichannel Commerce

hybris unterstützt Unternehmen dabei, mit ihren Kunden über alle Kanäle und Interaktionspunkte hinweg – online und offline – durchgängig, einheitlich und effektiv zu kommunizieren und so mehr Produkte, Dienstleistungen und digitale Inhalte zu verkaufen. hybris liefert „OmniCommerce™“: modernes Master Data Management und einheitliche Commerce-Prozesse, die Unternehmen eine umfassende Sicht auf ihre Kunden, Produkte und Bestellungen ermöglichen. Gleichzeitig profitieren die Kunden von einem nahtlosen Einkaufserlebnis.

hybris software

Die hybris Omni-Channel Software basiert auf offenen Standards, ist flexibel erweiterbar, global einsetzbar und zeichnet sich durch geringe Betriebskosten aus. Von den führenden Analysten als „Leader“ positioniert, zählt hybris zu den Top 3 der Branche. hybris bietet seine Lösung als lokale Installation, On-Demand, sowie als Managed-/Hosted-Service an. Unternehmen jeder Größe profitieren dadurch von maximaler Flexibilität.

Die zunehmend vernetzte Welt mit ihren technischen Entwicklungen hat das Einkaufsverhalten maßgeblich geprägt: Der Kunde ist heute König auf allen Vertriebskanälen. Heute informiert sich der Kunde vor der Kaufentscheidung auf den unterschiedlichsten Kanälen, er vergleicht Preise, recherchiert und kauft häufig auch mal per Mausklick. Anbieter stehen daher heute mehr denn je vor der Herausforderung, ihre Vertriebsstrukturen auf Multichannel Commerce auszurichten, Prozesse anzupassen, über neue Geschäftsstrategien nachzudenken und aktiv zu werden.

Mit den hybris Lösungen können Unternehmen ihre Internetaktivitäten erweitern, ihre Daten zentral verwalten oder die mehrsprachige Auflage ihrer Produktkataloge automatisieren – hybris ist der einzige Anbieter, der diese Applikationen auf einer Plattform für alle Ausgabekanäle zusammenführen kann.

hybris Multichannel Suite: Alle Kanäle. Eine Plattform.

hybris hat sich ganz dem Thema Multichannel Commerce verschrieben. Alles baut auf die Multichannel Commerce Software auf, die Produktinhalte, Geschäftsabläufe und alle Absatzkanäle in eine einzige Plattform integriert. Unternehmen, die hybris einsetzen, können so ihren Kunden ein einheitliches und durchgängiges Einkaufserlebnis bieten. Dabei stellt hybris verschiedene Module zur Verfügung. Die Kunden selbst können ähnlich wie bei einem Baukastensystem sich die Module bedarfsgerecht zusammenstellen.

Das Modul hybris Product Content Management (PCM) konsolidiert die Verwaltung der Produktinhalte und Geschäftsdaten auf einer einzigen Plattform, um so die Konsistenz sicherzustellen und die Effizienz in allen Kanälen, Systemen und Sparten zu erhöhen. Mit hybris Mobile können Unternehmen mobile Geräte vollständig in ihre Vertriebsstrategie integrieren. Diese Lösung fungiert als Bindeglied zwischen dem stationären und Online-Handel. Sie fördert den Besuch von Webseiten und Läden, ermöglicht die Suche nach Produkt- und Preisinformationen und optimiert die Geschäftsprozesse zwischen Kunden, Partnern und Lieferanten.

hybris Web Content Management (WCMS) bietet ein intuitives graphisches Datenmanagement, um Produkte und Marken online einheitlich zu präsentieren, die Inhalte effizient zu verwalten sowie über alle Kanäle hinweg zu veröffentlichen. Da Druckerzeugnisse im Vertrieb immer noch eine wichtige Rolle spielen, unterstützt hybris Unternehmen auch bei diesen Aufgaben.

hybris Print sorgt für eine Automatisierung der Printprozesse und unterstützt bidirektionale Workflows für eine effiziente Erstellung und Verwaltung von Printmaterial. Diese Lösung ist mit hybris PCM synchronisiert, um die Konsistenz von Marken, Produktinformationen und Preisen mit Content in anderen Kanälen zu gewährleisten.

Um speziell auch auf die Bedürfnisse von B2C- und B2B-Kunden eingehen zu können, bietet hybris die Applikationen hybris B2C / B2B Commerce an, die zielgruppenspezifische Funktionen bereitstellen.

Alle genannten Lösungen können auch gebündelt als hybris Multichannel Suite erworben werden. Neben der Software bietet hybris seinen Kunden Multichannel Consulting, Professional Services, Schulungskurse, Zertifizierungen, Hosting & Managed Services und natürlich Wartung und Support.

hybris B2B Commerce: Verkaufen nach B2C-Vorbild

Während der Einzelhandel bereits die Möglichkeiten des Multichannel-Vertriebs sowohl für die Kommunikation als auch für den Verkauf erfolgreich nutzt, steckt die B2B-Branche hier noch in den Kinderschuhen. Natürlich stehen B2B-Unternehmen im Vergleich zu B2C-Unternehmen vor weit größeren Herausforderungen, allerdings gilt für beide: Die erfolgreiche Kundengewinnung und -bindung hängt davon ab, wie gut es den Unternehmen gelingt, Produkte und Dienstleistungen exakt auf die Kundenwünsche abgestimmt bereitzustellen. Angetrieben durch die Veränderungen im Kundenverhalten erfährt der B2B-Handel zunehmend eine „Consumerization“. Geschäftskunden verlangen nach mehr Komfort und Service.

B2B-Händler stehen dadurch vor der Herausforderung, ihre Strategie, Systeme und Prozesse an das B2C-Multichannel Commerce Modell schnellstmöglich anzupassen. hybris trägt mit hybris B2B Commerce der steigenden Nachfrage nach einer B2B Multichannel-Lösung Rechnung. Denn die Lösung bietet Unternehmen alle spezifischen B2B-Funktionen, die es ihnen ermöglicht, ihren Kunden ein Kauferlebnis wie im B2C-Handel zu bieten, den Umsatz zu steigern und sich Wettbewerbsvorteile zu verschaffen. Dazu zählen das Verwalten von Kostenstellen und Budgets, die Unterstützung von komplexen B2B-Preismodellen, Einkaufs-Workflows, sowie eine automatische Nachschubsplanung.

hybris ist weltweit im Einsatz

Vertreten mit 15 Niederlassungen in den Wirtschaftszentren von Nord- und Südamerika, Asien und Europa zählt hybris mehr als 400 Kunden weltweit, darunter bedeutende Marken wie Coca-Cola, Conrad, Douglas, Hornbach, Iomega, Kaiser+kraft, Levi‘s, Lufthansa, Migros, Nespresso, Nikon, P&G, Rexel und Toys”R”Us.

comspace und hybris

hybris setzt auf ein Netzwerk von Implementierungs- und Technologie-Partnern, um Kunden bei der Bereitstellung und dem Einsatz der hybris Multichannel Suite zu unterstützen. comspace ist als hybris Silber Partner Teil dieses Netzwerkes und verfügt mit Erfahrungen aus über 200 Kundenprojekten in verschiedenen Branchen über ein langjähriges fachliches Know-how bei der Umsetzung von Multi-Channel-Lösungen.

Weitere Informationen finden Sie unter hybris.com.

Sitecore Commerce Connect – Teil 5: Warenkorb verwalten

Bisher ging es in dieser Serie um die Verwaltung und Darstellung von Produkten und Preisen aus Hybris. Das Ziel dieses Artikels ist es auch erste Prozesse rund um den Warenkorb mittels Sitecore Commerce Connect abbilden zu können.

Warenkorb

Die zentrale Überlegung am Anfang eines Projektes ist, welches System zu welcher Zeit die Zuständigkeit für den Warenkorb hat. Wird der Warenkorb immer auf beiden Seiten synchron gehalten, das heißt jede Änderung am Warenkorb wird unmittelbar an das externe Shopsystem (kurz ECS) gemeldet, oder wird der Warenkorb im ECS nur zu definierten Zeitpunkten synchronisiert, also z.B. beim Checkout? Letzteres bedingt weniger Requests an das ECS, aber Sitecore-seitig ist dann z.B. eine Warenkorbwertermittlung erforderlich, die im schlechtesten Fall nicht mit der im ECS synchron ist.

Warenkorb-Verwaltung

Im Rahmen unserer Evaluierung ist das ECS für den Warenkorb zuständig und Sitecore meldet alle Änderungen unmittelbar an das ECS. Für die Darstellung auf der Webseite werden die Daten rund um den Warenkorb wiederum immer online vom ECS abgefragt.
Außerdem haben wir uns entschieden, den mitgelieferten Engagement Plan ‘Abandoned Carts’ gleich mit zu nutzen, zumal dafür scheinbar keine weitere Implementierung erforderlich ist.

Warenkorb anlegen/ abfragen

Für alle Funktionen rund um den Warenkorb ist der Service Provider ‚CartServiceProvider‘ des Cart Service Layers zuständig.

Beispiel Verwendung CartServiceProvider.CreateOrResumeCart

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
public const string TrackerAttachmentKeyCart = "Comspace.Sitecore.CommerceConnect_Cart";

public static Cart GetCart()
{
if (!Tracker.Current.Contact.Attachments.ContainsKey(TrackerAttachmentKeyCart))
{
//create cart
var createOrResumeCartRequest = new CreateOrResumeCartRequest(
"Website",
Context.User.Profile.Email,
"Default",
Context.User.Profile.Email); //NOTE FindCartInEaState: c.CustomerId == request.CustomerId
var cart = new CartServiceProvider().CreateOrResumeCart(createOrResumeCartRequest).Cart;

//persist cart
Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cart;
}
return Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] as Cart;
}
}

//…
}
[/csharp]

Pipeline loadCart

Die Anbindung des ECS erfolgt mittels Erweiterung der Pipeline ‚loadCart‘ der Sitecore.Commerce.Carts.config.
.config

[xml]
<!– LOAD CART
Gets the cart object that matches the specified criteria.
This pipeline is responsible for reading data for a specific cart that is managed by the commerce system.
This pipeline reads the cart data from the commerce system and converts that data into the Commerce format.
–>
<commerce.carts.loadCart>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.LoadCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.LoadCart.LoadCartFromEaState, Sitecore.Commerce‘]"/>
</commerce.carts.loadCart>
[/xml]

Processor LoadCart

[csharp]
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model.Cart;
using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Pipelines.Carts.Common;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
/// <summary>
/// commerce.carts.loadCart
/// </summary>
public class LoadCart : CartPipelineProcessor
{
public override void Process(ServicePipelineArgs args)
{
var loadCartRequest = args.Request as LoadCartRequest;

//get from ECS
ExternalCart externalCart = CartConnector.Load(global::Sitecore.Context.Language.Name, loadCartRequest.UserId);

//convert to commerce connect cart
Cart cart = new Cart();
cart.ExternalId = externalCart.Code;
cart.ShopName = loadCartRequest.ShopName;
cart.UserId = loadCartRequest.UserId;

//init totals
cart = CartManager.UpdateCartTotals(cart); //NOTE prevent multiple request

//persist for next processor
((CartResult) args.Result).Cart = cart;
}
}
}
[/csharp]

Produkt zum Warenkorb hinzufügen

Beispiel Verwendung CartServiceProvider.AddCartLines

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
//…

public static Cart AddLine(string externalId, string sitecoreItemId, uint quantitiy)
{
Cart cart = GetCart();

//create cartlines
List<CartLine> lines = new List<CartLine>
{
new CartLine
{
Product = new CartProduct
{
ProductId = externalId,
SitecoreProductItemId = new ID(sitecoreItemId),
Price = PriceManager.GetPrice(externalId, Context.Language)
},
Quantity = quantitiy,
LineNumber = (uint) (cart.Lines.Count + 1)
}
};

//add cart lines
AddCartLinesRequest addCartLinerequest = new AddCartLinesRequest(cart, new ReadOnlyCollection<CartLine>(lines));
CartResult cartResult = new CartServiceProvider().AddCartLines(addCartLinerequest);

return cartResult.Cart; //result contains the updated cart
}
}
}
[/csharp]

Pipeline addCartLines

.config

[xml]
<!– ADD CART LINES
This pipeline is responsible for adding a new line to the shopping cart and recording a corresponding page event in DMS.
This happens when a product is added to the cart.
–>
<commerce.carts.addCartLines>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.AddLineToCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.AddCartLines.AddLinesToCart, Sitecore.Commerce‘]"/>
</commerce.carts.addCartLines>
[/xml]

Processor AddCartLines

[csharp]
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
/// <summary>
/// commerce.carts.addCartLines
/// </summary>
public class AddLineToCart : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var cartLinesRequest = args.Request as CartLinesRequest;

//add to ECS
Cart cart = cartLinesRequest.Cart;
CartLine cartline = cartLinesRequest.Lines.First();
CartConnector.AddLineToCard(global::Sitecore.Context.Language.Name, cart, cartline);
}
}
}
[/csharp]

Warenkorb Summen

Damit die Summen im Warenkorb immer korrekt sind und keine Sitecore-seitige Berechnung stattfinden muss, werden auch die Warenkorb Summen immer online aus dem ECS geholt. Dafür muss die Pipeline ‘saveCart’ erweitert werden:

Pipeline saveCart

.config

[xml]
<!– SAVE CART
Saves the cart object to the external system and in Sitecore EA state.
–>
<commerce.carts.saveCart>
<processor type="Comspace.Sitecore.CommerceConnect.Pipelines.Carts.UpdateCartTotals, Comspace.Sitecore.CommerceConnect" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.Common.SaveCartToEaState, Sitecore.Commerce‘]"/>
</commerce.carts.saveCart>
[/xml]

Processor UpdateCartTotals

[csharp]
using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Pipelines.Carts
{
/// <summary>
/// commerce.carts.saveCart
/// </summary>
public class UpdateCartTotals : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var cartRequest = (CartRequestWithCart) args.Request;

//get from ECS
Cart cart = CartManager.UpdateCartTotals(cartRequest.Cart);

//persist for next processor
((CartResult) args.Result).Cart = cart;
}
}
}
[/csharp]

Beispiel Verwendung PricingServiceProvider.GetCartTotal

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
//…

public static Cart UpdateCartTotals(Cart cart)
{
var request = new GetCartTotalRequest();
request.Cart = cart;

//get from ECS
var cartTotalResult = new PricingServiceProvider().GetCartTotal(request); //result contains the updated cart

Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cartTotalResult.Cart; //refresh cache
return cartTotalResult.Cart;
}
}
}
[/csharp]

Stolpersteine

Der Processor ‘FindCartInEaState’ benötigt eine CustomerId, damit der Contact ermittelt werden kann. Dafür muss die eigentlich optionale CustomerId mit an den ‘CreateOrResumeCartRequest’ übergeben werden (siehe oben). Anderenfalls wird immer wieder ein neuer Warenkorb angelegt, obwohl für den Contact bereits einer existiert.

Resumé

Und auch diesen letzten Beitrag möchte ich mit ein paar Eindrücken und Erkenntnissen abschließen:

  • Der Cart Sercice Layer ist sehr umfangreich und im Rahmen der Evaluierung konnte nur ein sehr kleiner Ausschnitt evaluiert werden.
  • Eine Herausforderung in einem echten Projekt sind die Zuständigkeit und Synchronisation zwischen dem Sitecore Shop und dem ECS. Was wird wo zwischengespeichert und wann an wen gemeldet?
  • Wie auch schon unter Stolpersteine beschrieben, war der Zugriff auf die Contacts im Engangement Plan (automatisch im Hintergrund) immer wieder holprig. Eine der Ursachen war sicherlich die Entwicklungsumgebung: wechselnde Browser, plötzliche Neustarts etc.  Hier müsste in einem echten Projekt eine kritische Analyse erfolgen.
  • Auch dem Umgang mit Fehlern in der Verarbeitung muss in einem echten Projekt eine hohe Aufmerksamkeit gewidmet werden – Stichwort Transaktionssicherheit.

So, das war’s erstmal von meiner Seite zum Thema Commerce Connect. Ich freue mich über euer Feedback 🙂

Alle voran gegangenen Artikel dieser Serie findet ihr hier:
Teil 1: Ein Überblick
Teil 2: Produkte synchronisieren
Teil 3: Klassifizierungen synchronisieren
Teil 4: Preise darstellen

Sitecore Commerce Connect – Teil 4: Preise darstellen

In den letzten beiden Teilen dieser Serie ging es um die Synchronisation von Produkten und Klassifizierungen. Das Ziel dieses Artikels ist es auch die Preise aus dem externen Shopsystem, in unserem Fall Hybris, auf der Webseite anzeigen zu können.

ProduktDemo

Der Pricing Layer ist ein “klassischer” Layer im Sitecore Commerce Connect, d.h. die Preise werden online abgerufen und nicht synchronisiert. Der Grund: Preise ändern sich häufiger und die Preisfindung ist sehr komplex, hier wird die Hoheit im externen Shopsystem (kurz ECS) belassen.

Preisabfragen

Für die Abfrage von Preisen ist der Service Provider ‚PricingServiceProvider‘ zuständig.

Eine Beispiel:

[csharp]using System.Linq;
using Sitecore.Commerce.Entities.Prices;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Globalization;

namespace Comspace.Sitecore.CommerceConnect.Services.Prices
{
/// <summary>
/// Sitecore.Commerce.Prices.config
/// </summary>
public class PriceManager
{
public static Price GetPrice(string productId, Language language)
{
CustomGetProductPriceRequest request = new CustomGetProductPriceRequest(language.Name, productId);
GetProductPricesResult result = new PricingServiceProvider().GetProductPrices(request);

return result.Prices.Any() ? result.Prices.First().Value : null; //simplified
}
}
}[/csharp]

Um die Preise aus dem ECS abzurufen, muss die Pipeline ‚getProductPrices‘ der Sitecore.Commerce.Prices.config erweitert werden:

Pipeline getProductPrices

.config

[xml]<!– GET PRODUCT PRICES Gets the price object that matches the specified criteria.
This pipeline is responsible for reading pricing data from a commerce system.
This pipeline requests product pricing information from the commerce system and
then converts the output into the proper Commerce format.
–>
<commerce.prices.getProductPrices>
<processor type="Sitecore.Commerce.Pipelines.Prices.GetProductPrices.GetProductPrices, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Prices.GetProductPrices, Comspace.Sitecore.CommerceConnect.Hybris" />
</commerce.prices.getProductPrices>[/xml]

Processor GetProductPrices

[csharp]using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore.Commerce.Entities.Prices;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Prices;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Prices
{
/// <summary>
/// commerce.prices.getProductPrices
/// </summary>
public class GetProductPrices : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as CustomGetProductPriceRequest;

//get from ECS
ExternalProduct productSource = ProductConnector.Load(request.Language, request.ProductId);
if (productSource != null && productSource.Price != null)
{
//convert to commerce connect price
Price price = new Price
{
Amount = productSource.Price.Value,
CurrencyCode = productSource.Price.CurrencyIso
};

//persist for next processor
((GetProductPricesResult) args.Result).Prices.Add("List price", price); //simplified
}
}
}
}[/csharp]

Stolpersteine

Der ‚GetProductPricesRequest‘ enthielt überraschenderweise kein Attribut für die aktuell zu synchronisierende Sprache. Daher musste dieser überschrieben werden:

[csharp]using Sitecore.Commerce.Services.Prices;

namespace Comspace.Sitecore.CommerceConnect.Services.Prices
{
/// <summary>
/// Extendended GetProductPricesRequest.
/// </summary>
public class CustomGetProductPriceRequest : GetProductPricesRequest
{
public string Language { get; set; }

public CustomGetProductPriceRequest(string language, string productId, params string[] priceTypeIds)
: base(productId, priceTypeIds)
{
Language = language;
}
}
}[/csharp]

Resumé

Zuletzt möchte ich wieder ein paar Eindrücke und Erkenntnisse zusammenfassen:

  • Der Service Layer ist eher klein und schnell erfasst.
  • Was mir wirklich gut gefällt ist, dass die Preishoheit im ECS verbleibt.
  • In umfangreicheren Produklisten sollte zwecks Performance die Nutzung der Methode/Pipeline ‚getProductBulkPrices‘ bevorzugt werden.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 3: Klassifizierungen synchronisieren

Im letzten Teil dieser Serie ging es um die Synchronisation von Produkten. Das Ziel dieses Blog-Artikels ist es auch Klassifizierungen unidirektional aus dem Sitecore heraus mit dem externen Commerce System (kurz ECS, in unserem Fall Hybris) zu synchronisieren und den Produkten zuzuordnen.

Klassifizierungen sind im Projekt individuell definierbare Spezifikationen zur Beschreibung von Produkten. Die sog. Classifications in Sitecore können über eine Parent-Verknüpfung (ExternalParentID) beliebig geschachtelt werden, die erste Ebene ist aber immer eine Gruppe, eine sog. ClassificationGroup.
ClassificationRepo

Synchronisation Klassifizierungen

Um Klassifizierungen zu synchronisieren muss zunächst die Pipeline ’synchronizeClassifications‘ der Sitecore.Commerce.Products.config erweitert werden:

Pipeline synchronizeClassifications

.config

[xml]
<!– SYNCHRONIZE CLASSIFICATIONS –>
<commerce.synchronizeProducts.synchronizeClassifications>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadSitecoreClassifications, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ResolveClassificationsChanges, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.SaveClassificationsToExternalCommerceSystem, Sitecore.Commerce" >
<patch:delete />
</processor>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications.ReadClassifications, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadExternalCommerceSystemClassifications, Sitecore.Commerce‘]" />
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications.ReadClassificationGroups, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadExternalCommerceSystemClassifications, Sitecore.Commerce‘]" />
</commerce.synchronizeProducts.synchronizeClassifications>
[/xml]

Processor ReadClassificationGroups

[csharp]using System.Collections.Generic;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeClassifications
/// </summary>
public class ReadClassificationGroups : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizationRequest;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>; //integration guide (page 9)
groups = groups ?? new List<ClassificationGroup>();

//get groups from ECS
IEnumerable<ExternalCategoryGroup> categoryGroups = ClassificationConnector.Load(request.Language);

//convert and add to commerce connect list
groups.AddRange(from @group in categoryGroups
select new ClassificationGroup
{
ExternalId = @group.Code,
Name = @group.Name
});

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}
}
}[/csharp]

Processor ReadClassifications

[csharp]using System.Collections.Generic;
using System.Collections.ObjectModel;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeClassifications
/// </summary>
public class ReadClassifications : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizationRequest;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>; ////integration guide (page 9)
groups = groups != null ? groups : new List<ClassificationGroup>();

foreach (ClassificationGroup group in groups)
{
var classifications = new List<Classification>();

//get categories from ECS
foreach (ExternalCategory externalCategory in ClassificationConnector.Load(group.ExternalId, request.Language)) //TBD
{
//convert to commerce connect classification
var classification = new Classification();
classification.ExternalId = externalCategory.Code;
classification.Name = externalCategory.Name;
classifications.Add(classification);

//…with sub-classifications
HandleSubCategories(externalCategory, classifications);
}
group.Classifications = new ReadOnlyCollection<Classification>(classifications);
}

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}

private static void HandleSubCategories(ExternalCategory parentCategory, List<Classification> allClassifications)
{
if (parentCategory.Categories != null)
{
foreach (var category in parentCategory.Categories)
{
var classification = new Classification();
classification.ExternalId = category.Code;
classification.Name = category.Name;
classification.ExternalParentId = parentCategory.Code;

allClassifications.Add(classification);
HandleSubCategories(category, allClassifications);
}
}
}
}
}
[/csharp]

Klassifizierungen Produkten zuordnen

Anschließend müssen die Klassifizierungen nur noch den Produkten zugeordnet werden. Und auch dafür muss wieder eine Pipeline der Sitecore.Commerce.Products.config erweitert werden:

Pipeline synchronizeProductClassifications

.config

[xml]<!– SYNCHRONIZE PRODUCT CLASSIFICATIONS
This pipeline is responsible for synchronizing and updating the references between a given product and associated classifications and categories within.
It’s assumed that classifications are already synchronized and present in CMS.
The references to categories are stored directly on the main product item. –>
<commerce.synchronizeProducts.synchronizeProductClassifications>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProductClassifications, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeProductClassifications.ReadExternalCommerceSystemProductClassifications, Sitecore.Commerce‘]" />
</commerce.synchronizeProducts.synchronizeProductClassifications>[/xml]

Processor ReadProductClassifications

[csharp]using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeProductClassifications
/// </summary>
public class ReadProductClassifications : ReadProduct
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizeProductRequest;
var product = request.Properties["Product"] as Product;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>;
groups = groups != null ? groups : new List<ClassificationGroup>();

//get product from ECS
ExternalProduct externalProduct = ProductConnector.Load(request.Language, product.ExternalId); //tbd
if (externalProduct != null && IsProductValidForImport(externalProduct))
{
//convert to commerce connect product classification groups and classifications
foreach (ExternalCategoryGroup externalCategoryGroup in externalProduct.CategoryGroups)
{
ClassificationGroup group = new ClassificationGroup();
group.ExternalId = externalCategoryGroup.Code;
group.Name = externalCategoryGroup.Name;

IEnumerable<Classification> classifications = from externalCategory in externalCategoryGroup.Categories
select new Classification
{
ExternalId = externalCategory.Code,
Name = externalCategory.Name
};
group.Classifications = new ReadOnlyCollection<Classification>(classifications.ToList());

groups.Add(group);
}
}

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}
}
}[/csharp]

Resumé

Mein erster Eindruck:

  • Es gibt viel Dokumentation, aber z.B. zum Thema Zuordnung von Klassifikationen zu Produkten konnte ich nichts finden. Da habt ihr es jetzt ja besser 😉 Häufig war dann doch dotPeek mein bester Freund.
  • Der Differenzabgleich funktionierte problemlos, Mehrsprachigkeit war im Kontext der Zuordnung von Klassifizierungen zu Produkten noch etwas holprig, da sollte ein zweiter Blick erfolgen.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 2: Produkte synchronisieren

Im ersten Teil dieser Serie ging es darum einen kurzen Überblick über das Sitecore Commerce Connect Modul zu verschaffen. In diesem Beitrag geht es nun um die Synchronisation von Produkten zwischen einem externen Shopsystem (kurz ECS, in unserem PoC Hybris) und Sitecore.

Zunächst ist es wichtig zu verstehen, dass Preise und Bestand im Commerce Connect nicht als Produktstammdaten gelten. Der Bestand ändert sich ständig und DEN Preis gibt es ja auch nicht, Preisfindung ist ein sehr komplexes Thema. Sitecore geht davon aus, dass diese Informationen online vom ECS ermittelt und geliefert werden. Im Teil 4 dieser Serie wird dann beschrieben, wie ihr auch an diese Informationen kommt.

image00

Die Hoheit für die Produktdaten liegt sicherlich im ECS. Für die Synchronisation der Produktdaten schlägt Sitecore folgende Varianten vor:

  1. Produkte als Items in Sitecore Datenbank (optional bidirektional)
  2. Produkte in ECS, Zugriff über Sitecore Data Provider
  3. Produkte in ECS, Zugriff über Sitecore Index

Wir haben uns im Verlauf des PoC für die Variante 1 entschieden, da diese standardmäßig vom Sitecore so vorgesehen ist und den höchsten Komfort bietet. In der Sitecore Commerce Components Overview im Kapitel “Products” sind einige Argumente für oder gegen die unterschiedlichen Variante aufgeführt.

So, jetzt aber Butter bei die Fische 🙂

Installation Modul

Falls ihr das Modul noch nicht installiert habt, findet ihr wie gewohnt eine gute Installationsanleitung auf dev.sitecore.net.

Synchronisation Produkte

Das Ziel: Titel, Beschreibung und Bilder der Produkte aus dem externen Shopsystem mit Sitecore zu synchronisieren.

image01

In folgende Pipelines der Sitecore.Commerce.Products.config muss für die unidirektionale Synchronisation der Produkte eingegriffen werden:

Pipeline getExternalCommerceSystemProductList

.config

[xml]
&lt;!– GET EXTERNAL COMMERCE SYSTEM PRODUCT LIST
This pipeline is responsible for obtaining the list of product Ids to be synchronized from the external commerce system.–&gt;
&lt;commerce.synchronizeProducts.getExternalCommerceSystemProductList&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.GetExternalCommerceSystemProductList.GetExternalCommerceSystemProductList, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProducts, Comspace.Sitecore.CommerceConnect.Hybris" /&gt;
&lt;/commerce.synchronizeProducts.getExternalCommerceSystemProductList&gt;
[/xml]

Processor ReadProducts

[csharp]
using System.Collections.Generic;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// &lt;summary&gt;
/// commerce.synchronizeProducts.getExternalCommerceSystemProductList
/// &lt;/summary&gt;
public class ReadProducts : PipelineProcessor&lt;ServicePipelineArgs&gt;
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizeProductsRequest;
var productIds = args.Request.Properties["ExternalCommerceSystemProductIds"] as List&lt;string&gt;; //integration guide (page 12)
productIds = productIds ?? new List&lt;string&gt;();

//get products from ECS
IEnumerable&lt;ExternalProduct&gt; externalProducts = ProductConnector.Load(request.Language);

//convert and add to commerce connect list
productIds.AddRange(from product in externalProducts
where product.Code != null
select product.Code);

//persist for next processor
args.Request.Properties["ExternalCommerceSystemProductIds"] = productIds;
}
}
}
[/csharp]

Pipeline synchronizeProductEntity

.config

[xml]
&lt;!– SYNCHRONIZE PRODUCT ITEM –&gt;
&lt;commerce.synchronizeProducts.synchronizeProductEntity&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.ReadProductFromSitecore, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.SaveProductToExternalCommerceSystem, Sitecore.Commerce" &gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProduct, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.ReadExternalCommerceSystemProduct, Sitecore.Commerce‘]" /&gt;
&lt;/commerce.synchronizeProducts.synchronizeProductEntity&gt;
[/xml]

Processor ReadProduct

[csharp]
using Comspace.Sitecore.CommerceConnect.Entities.Products;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;
using Sitecore.Diagnostics;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// &lt;summary&gt;
/// commerce.synchronizeProducts.synchronizeProductEntity
/// &lt;/summary&gt;
public class ReadProduct : PipelineProcessor&lt;ServicePipelineArgs&gt;
{
public override void Process(ServicePipelineArgs args)
{
SynchronizeProductRequest syncProdRequest = args.Request as SynchronizeProductRequest;

//get product from ECS
ExternalProduct externalProduct = ProductConnector.Load(syncProdRequest.Language, syncProdRequest.ProductId); //TBD
if (externalProduct != null &amp;&amp; IsProductValidForImport(externalProduct))
{
CustomProduct product = new CustomProduct();
product.ExternalId = externalProduct.Code;
product.Name = externalProduct.Name;
product.FullDescription = externalProduct.Summary;
product.ImageUrl = HybrisSettings.Url + externalProduct.ImageUrl; //custom property

//persist for next processor
args.Request.Properties["Product"] = product; //integration guide (page 12)
}
}

protected bool IsProductValidForImport(ExternalProduct product)
{
var result = true;
if (string.IsNullOrEmpty(product.Name)) // = item.Name
{
result = false;
Log.Info("Skip product ‚" + product.Code + "‘: Name not valid.", product); //NOTE System.Messages
}
return result;
}
}
}
[/csharp]

CustomProduct

Wenn auch die Bilder synchronisiert werden sollen, muss das Produkt-Template des Commerce Connect erweitert werden. Konkret müssen dafür folgende Anpassungen vorgenommen werden:

Template
Neues Template “CustomProduct” anlegen, welches vom Standard Commerce Connect Template „Product“ erbt.

Klassen
Erweiterte Produkt-Klasse „CustomProduct“ definieren:

[csharp]
using Sitecore.Commerce.Entities.Products;
using Sitecore.Data.Items;

namespace Comspace.Sitecore.CommerceConnect.Entities.Products
{
/// &lt;summary&gt;
/// Custom product including image field.
/// &lt;/summary&gt;
public class CustomProduct : Product
{
public string ImageUrl { get; set; }

public MediaItem Image { get; set; }
}
}
[/csharp]

Die Klasse „CustomProductRepository“ definiert dann wie das erweiterte Produkt gelesen und geschrieben wird:

[csharp]
using Comspace.Sitecore.CommerceConnect.Entities.Products;
using Comspace.Sitecore.CommerceConnect.Model.sitecore.templates.User_Defined.Comspace.CommerceConnect;
using Sitecore.Commerce.Data.Products;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Product = Sitecore.Commerce.Entities.Products.Product;

namespace Comspace.Sitecore.CommerceConnect.Data.Products
{
/// &lt;summary&gt;
/// Custom product repository including image field.
/// &lt;/summary&gt;
public class CustomProductRepository : ProductRepository
{
/// &lt;summary&gt;
/// Save product data to related item.
/// &lt;/summary&gt;
/// &lt;param name="entityItem"&gt;&lt;/param&gt;
/// &lt;param name="product"&gt;&lt;/param&gt;
protected override void UpdateEntityItem(Item entityItem, Product product)
{
base.UpdateEntityItem(entityItem, product);

using (new EditContext(entityItem))
{
var url = (product as CustomProduct).ImageUrl;

var image = GetMediaItem(url, product); //TBD
ImageField imagefield = entityItem.Fields[IProductConstants.ImageFieldName];
imagefield.Alt = image.Alt;
imagefield.MediaID = image.ID;
}
}

/// &lt;summary&gt;
/// Read product data from related item.
/// &lt;/summary&gt;
/// &lt;param name="entityItem"&gt;&lt;/param&gt;
/// &lt;param name="product"&gt;&lt;/param&gt;
protected override void PopulateEntity(Item entityItem, Product product)
{
base.PopulateEntity(entityItem, product);

ImageField imageField = entityItem.Fields[IProductConstants.ImageFieldName];
(product as CustomProduct).Image = imageField == null ? null : imageField.MediaItem;
}

#region Handle MediaItem
//…
#endregion
}
}
[/csharp]

.config
Angepasste Templates und Klassen in Sitecore.Commerce.Products.config registrieren:

[xml]
&lt;!– PRODUCT REPOSITORY –&gt;
&lt;productRepository type="Sitecore.Commerce.Data.Products.ProductRepository, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/productRepository&gt;
&lt;productRepository type="Comspace.Sitecore.CommerceConnect.Data.Products.CustomProductRepository, Comspace.Sitecore.CommerceConnect" singleInstance="true"&gt;
&lt;template&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/template&gt;
&lt;branch&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/branch&gt;
&lt;path ref="paths/products" /&gt;
&lt;Prefix&gt;Product_&lt;/Prefix&gt;
&lt;ProductsIndex&gt;commerce_products_master_index&lt;/ProductsIndex&gt;
&lt;ManufacturerRepository ref="productManufacturerRepository" /&gt;
&lt;DivisionRepository ref="productDivisionRepository" /&gt;
&lt;TypeRepository ref="productTypeRepository" /&gt;
&lt;ClassificationsRepository ref="productClassificationsFieldRepository" /&gt;
&lt;ResourcesRepository ref="productResourcesRepository" /&gt;
&lt;RelationsRepository ref="productRelationsRepository" /&gt;
&lt;GlobalSpecificationsRepository ref="productGlobalSpecificationsRepository" /&gt;
&lt;ClassificationsSpecificationsRepository ref="productClassificationsSpecificationsRepository" /&gt;
&lt;TypeSpecificationsRepository ref="productTypeSpecificationsRepository" /&gt;
&lt;/productRepository&gt;

&lt;includeTemplates&gt;
&lt;ProductTemplateId&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/ProductTemplateId&gt;
&lt;/includeTemplates&gt;

&lt;!– Commerce ENTITIES
Contains all the Commerce cart entities.
The configuration can be used to substitute the default entity implementation with extended one. –&gt;
&lt;commerce.Entities&gt;
&lt;Product type="Sitecore.Commerce.Entities.Products.Product, Sitecore.Commerce" &gt;
&lt;patch:delete /&gt;
&lt;/Product&gt;
&lt;Product type="Comspace.Sitecore.CommerceConnect.Entities.Products.CustomProduct, Comspace.Sitecore.CommerceConnect" /&gt;
&lt;/commerce.Entities&gt;
[/xml]

Stolpersteine

In der Klasse „Sitecore.Commerce.Templates“ existiert eine Konstante „ProductTemplateId“, deren Id fest verdrahtet ist und nicht auf die zuvor eingerichteten Konfigurationsdateien verweist. Die Konstante wird u.a. in der Klasse „ItemClassificationService“ verwendet, daher ist dieser auszutauschen:

[xml]
&lt;itemClassificationService type="Sitecore.Commerce.Products.ItemClassificationService, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/itemClassificationService&gt;
&lt;itemClassificationService type="Comspace.Sitecore.CommerceConnect.Entities.ItemClassificationService, Comspace.Sitecore.CommerceConnect" /&gt;
[/xml]

Resumé

Zuletzt möchte ich ein paar erste Eindrücke und Erkenntnisse zusammenfassen:

  • Der erste Einstieg durch Pipelines in Pipelines in Pipelines ist herausfordernd.
  • Es gibt umfangreiche Dokumentationen.
  • Einige hilfreiche Klassen aus Beispielen im Internet verweisen leider auf die CommerceServer API, z.B. ProductsSearchResult und CommerceConstants. Auch in dem Dynamics AX Demoshop von Sitecore (Commerce.Dynamics.Storefront) sind Verweise auf die CommerceServer API enthalten. Die Trennung bzw. Abstraktion ist m.E. noch nicht ganz sauber erfolgt.
  • Verwunderlich fand ich zunächst, dass die Synchronisation von Bildern nicht im Standard enthalten ist. Aber dem von Sitecore definierten Prinzip “kleinster gemeinsamer Nenner” folgend, ist es verständlich, denn Bild-Quelle und -Ziel sind doch sehr projektspezifisch.
  • Der “Merchandising Manager” ist NICHT Bestandteil des Commerce Connect, sondern des CommerceServer Connect.
  • In einem echten Projekt mit einem umfangreichen Datenmodell wird der größte Aufwand in das Verständnis und das Mapping der Datenmodelle gehen.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 1: Ein Überblick am Beispiel Hybris

Wie in meinem Vortrag zum Thema “Evaluierung einer Hybris-Anbindung an Sitecore” auf der letzten Sitecore Usergroup in Bielefeld versprochen, möchte ich mit dieser Beitragsserie unsere ersten Eindrücke von der Anbindung eines externen Shopsystems (in diesem Beispiel Hybris) an Sitecore mittels des Commerce Connect Moduls mit euch teilen.

In diesem Artikel geht es mir darum, euch einen ersten Überblick über das Marketing-Potenzial und Funktionsumfang des Moduls zu verschaffen. In den nächsten Beträgen wird es dann technischer mit konkreten Ausschnitten aus Konfigurationsdateien und Quellcode.

Warum sollten WebCMS und E-Commerce Platform verbunden werden?

Bevor wir an die technischen Details gehen, erlaube ich mir zunächst einige Gedanken aus Sicht von Produkt-Management, Marketing und Vertrieb auf Betreiberseite:

Webnutzer erwarten immer mehr eine einheitliche User-Experience, die sich über alle Kanäle und Plattformen eines Unternehmens erstrecken soll. Zudem  bringen auch Online-Shop Systeme kleinere CMS-Funktionalitäten mit und damit wird auch die Darstellung von Inhalten immer besser. Warum sollten dann CMS und Shop miteinander verbunden werden?

Sitecore bietet bereits in seiner eigenen Suite ein umfangreiches Bundle an Funktionen, um verschiedenste Touchpoints (Webseite, Newsletter, Social Media, Offline-Kanäle am POS usw.) zu einem Nutzererlebnis aus einem Guss zu verschmelzen.

Mit diesem Beispiel möchte ich Möglichkeiten aufzeigen, mit denen Besuchern auch auf Unternehmenswebseiten die Kernfunktionalitäten eines Online-Shops angeboten werden können (wie bspw. Produkte in den Warenkorb legen) und wie diese kombinierten Webseiten- und E-Commerce-Daten dann von der Unternehmenswebseite automatisch an das Shopsystem übergeben werden.

Ein Beispiel für eine Customer Journey mit Optimierungspotenzial sähe beispielsweise so aus:

Der Kunde schaut auf einer Webseite nach einem Produkt, muss sich das Produkt merken und im separaten Online-Shop danach suchen um zu bestellen. 

Das bedeutet:
Durch die zwei separaten Technologien Webseite und Shop entsteht für den Kunden Fehler- und Frustrations-Potenzial. Für Unternehmen und Shop-Anbieter kann das bedeuten: Erhöhte Abbruchgefahr und damit niedrigere Conversion-Rate.

Mit Lösungen wie dem Commerce Connect arbeitet die Technologie im Hintergrund für den Kunden, nimmt ihm Arbeitsschritte ab und erhöht damit die Wahrscheinlichkeit, dass es zu einem Verkauf kommt.

Der Endkunde bemerkt so den Technologiewechsel aus dem WebCMS in den Online-Shop überhaupt nicht und hat ein nahtloses Nutzererlebnis im Bestellprozess.

Kurzvorstellung Sitecore Commerce Connect

Sitecore Commerce Connect sieht sich als Framework und API zur nahtlosen Integration von 3rd-Party Commerce Systemen in Sitecore. Das Ziel ist die Überführung der Experience Marketing Features in die Commerce Welt:

  • Einheitliche Darstellung:  Produktkataloge, “normale” Inhalte und Bilder aus einem Guss
  • Bearbeitung von Produktkatalogen, Seiteninhalten und Bildern in einen System
  • Nutzung der Sitecore Facettensuche über ALLE Inhalte.
  • Personalisierung auch über das Kaufverhalten der Besucher
  • Tracking von Conversions und Values rund um den Einkauf
  • Identifizierung von und Einblick in die wertvollsten Kunden
  • Reaktion auf Änderungen im Geschäft, in Echtzeit

Konkreter Umfang des Commerce Connect Moduls

Das Sitecore Modul bietet die Möglichkeit Produktkataloge mit dem externen Commerce System zu synchronisieren, d.h. es stellt entsprechende Templates, Klassen, Repositories, Import-Pipelines etc. zur Verfügung:

Sitecore-Commerce-Connect-Modul

Erste Engagement-Pläne zu Warenkorb-Abbrüchen, neuen Aufträgen und Wiederverfügbarkeit von Produktbeständen werden mitgeliefert und genutzt:

Sitecore-Commerce-Connect-Engagement-Plan

Selbstverständlich kommen auch Erweiterungen für die Sitecore Rule Engine mit:

Sitecore-Commerce-Connect-Rule-Engine

Das Framework des Sitecore Commerce Connect 

Die Architektur wird von Sitecore wie folgt skizziert, wobei der Commerce Server Connector und der Dynamics AX Connector fertige Connectoren von Sitecore selbst sind und der Hybris Connector von uns im Rahmen der Evaluierung entwickelt wurde.

Service Layer:

Folgende Bereiche deckt das Modul ab:

  • Warenkorb
  • Preise
  • Kunden und Benutzer
  • Bestände
  • Produkte
  • Aufträge
  • Geschenkkarten
  • Wunschlisten
  • Bezahlung
  • Versand
  • Treueprogramme

Die einzelnen Layer sind optional und unabhängig voneinander. Ein Layer umfasst ein Datenmodell und Pipelines mit ggf. zugehörigen Service Providern. Die farblich markierten Service Layer wurden im Rahmen des PoC näher beleuchtet. Wenn ihr einen Eindruck haben möchtet was sich dahinter verbirgt, habt etwas Geduld – die zugehörigen Artikel folgen.

Prinzipien

Weil sie mir so gut gefallen haben, hier eine kurze Zusammenfassung und Übersetzung der Prinzipien welche dem Modul von Sitecore selbst auferlegt wurden:

  • Simplicity – kleinster gemeinsamer Nenner
  • Extensibility – Domain Model erweiterbar
  • Independently – keine Abhängigkeit zwischen Service Layern
  • Abstraction – keine Sitecore oder Shopsystem Abhängigkeit in Service Layern
  • Pipelines – jeder Service Layer benutzt Sitecore Pipelines
  • Fallback – fehlende Implementierungen im Shopsystem können überbrückt werden

Erstes Resumé

Mein Eindruck nach der Evaluierung des Commerce Connect anhand einer Hybris-Anbindung:

  • Der erste Einstieg ist herausfordernd, kurz stand ich davor wie “der Ochs vorm Berg”. Auf dem Partnertag in Frankfurt wurde von Sitecore eine Schulung zu dem Modul angekündigt. Die kann ich dann nur empfehlen.
  • Das bringt mich zu den zur Verfügung stehenden Materialien:
    • Es gibt umfangreiche Dokumentationen über das Modul im Developer Netzwerk.
    • Cookbooks gibt es leider keine, diese helfen sonst häufig die erste Hürde zu nehmen.
    • Wenn die Dokumentation nicht weiter hilft, gibt es im Internet aktuell leider noch wenig Material zum Commerce Connect.
    • Geholfen haben immer wieder die herunterladbaren Beispiel-Implementierungen für AX und CommerceServer und natürlich dotPeek.
  • Customizing gehört zum Grundkonzept. Durch eine gute Abstraktion ist fast alles flexibel anpassbar. Das gefällt, auch wenn es manchmal nicht einfach ist, im Pipeline-Dschungel den Überblick zu bewahren.
  • Für eine solide und benutzerfreundliche Integration ist eCommerce-KnowHow unumgänglich. Die dahinterliegenden Modelle und Prozesse müssen durchdrungen werden.
  • Die Komplexität einer Commerce Integration bringt auch höhere Konzeptions- und Abstimmungsaufwände mit sich als vielleicht sonst im CMS-Umfeld üblich.
  • Das Modul umfasst keine Layout-Komponenten und keine speziellen Applikationen.
  • Der “Merchandising Manager” ist  NICHT Bestandteil des Commerce Connect, sondern des CommerceServer Connect.
  • Eine gute Einsatzmöglichkeit für das Modul ist m.E. eine PIM-Integration.
  • Zu guter Letzt: Die Arbeit mit dem Modul hat mir Spaß gemacht!

Die Präsentation zum Vortrag:

In den kommenden Tagen werde ich diese Serie mit den folgenden Artikeln fortführen:

 

 

Sitecore Usergroup Deutschland: Ein Rückblick auf das Treffen in Bielefeld

Christopher Wojiciech Agentur netzkern

Vor kurzem waren wir Ausrichter des 3. Sitecore Usergroup (SUG) Deutschland Treffens in Bielefeld. Rund 30 Entwickler, Anwender und Projektmanager aus Sitecore-Agenturen und Unternehmen, die ihre Website auf Sitecore-Basis betreiben, konnten wir im Vortragssaal des Historischen Museums Bielefeld begrüßen. Die meisten waren “Wiederholungstäter” und schon bei einem der beiden ersten Treffen dabei gewesen, aber es gab auch einige neue Gesichter.

SUG Deutschland Treffen in Bielefeld

 

Nach einem ersten Kennenlernen und Small Talk bei Brötchen und Kaffee haben als offizielle Vertreter der Sitecore Usergroup Deutschland unser Account Manager Johannes Tappmeier und Christopher Wojciech von der Agentur netzkern das Treffen eröffnet. Danach starteten die Fachvorträge.

Evaluierung einer Anbindung von Hybris an Sitecore mittels Commerce Connect

So lautete das erste Thema, über das unsere Sitecore-Expertin Friederike Heinze referierte. In ihrer dreiwöchigen Evaluierungsphase hat sie sich intensiv damit beschäftigt, einen fiktiven Hybris-Shop mit Sitecore Commerce Connect an die Sitecore Experience Platform anzubinden. Dabei wurden folgende Anforderungen umgesetzt:

  • Produkte synchronisieren
  • Preise darstellen
  • Artikel in den Warenkorb legen.

 

Präsentation Friederike bei dem SUG Deutschland Treffen in Bielefeld

 

Friederikes Fazit:

Die Entwicklung und Arbeit mit dem Sitecore Commerce Connect macht Spaß. Customizing gehört zum Grundkonzept des Moduls, dadurch ist alles ist flexibel anpassbar, das bringt aber natürlich auch Komplexität mit sich. Durch die nahtlose Integration des Shopsystems in Sitecore werden die umfangereichen Personalisierungs-Features mit in die eCommerce Welt genommen. Für eine gute Anbindung eines Shopsystems ist E-Commerce Knowhow unbedingt empfehlenswert und man sollte einen hohen Konzeptions- und Abstimmungssaufwand einplanen.

Die gesamte Präsentation können Sie hier nachlesen.

The good, the bad and the ugly modul

Der nächste Vortrag kam von Sitecore-Entwickler Maksym Ponomarenko von der Agentur netzkern und beschäftigte sich mit dem Thema “The good, the bad and the ugly modul”.

Präsentation Maksym Ponomarenko Agentur netzkern

 

Dabei ging es um Design- und Architektur-Spezifika bei der Entwicklung von Sitecore Modulen. Neu entwickelte Module sollten über eine durchdacht gestaltete, flexible Architektur verfügen, die zukünftige Weiterentwicklungen unterstützt. Die Präsentation beinhaltete einige Architektur Tricks und Tipps, sowie Best Practices bei der Modul-Entwicklung.

Weitere Entwicklung der Sitecore Usergroup Deutschland

Nach einer Kaffeepause berichtete Chris von netzkern über die angedachte Weiterentwicklung der Sitecore Usergroup Deutschland und stellte die geplante Vereinsgründung vor. Über die grob skizzierte Satzung wurde lebhaft diskutiert. Anvisiert wird, den Verein im Rahmen des nächsten SUG Treffens zu gründen.

Christopher Wojiciech Agentur netzkern

 

Erfahrungen mit Sitecore 8 und Möglichkeiten zur CRM-Anbindung

Zum Abschluss gab es zwei Kurzvorträge mit anschließender Diskussions- und Fragerunde.

Chris von netzkern gab den Teilnehmern Praxis-Einblicke in die Experience Management Features von Sitecore 8 mit Schwerpunkt auf die xDB und die Entwicklung und den API-Zugriff auf die jeweiligen Features. Diese führte zu intensiven Diskussionen über die noch bestehenden Bugs von Sitecore 8. U.a. waren sich die meisten Teilnehmer einig, dass das Arbeiten im Experience Editor momentan sehr langsam und zeitaufwändig ist.

Unserer Sitecore-Expertin Friederike stellte das Sitecore Modul “Dynamics CRM Campaign Integration” vor. Sie berichtete über mögliche Integrationslevel und über ihre Erfahrungen aus einem PoC mit MS Dynamics 2015. Näheres dazu werden Sie demnächst in einem eigenen Beitrag hier im Blog lesen können.

Ausklang mit Führung im Museum Wäschefabrik

Nach dem offiziellen Programm, das von allen Teilnehmern als sehr informativ und bereichernd empfunden wurde, stand eine interessante Führung durch das Museum Wäschefabrik auf dem Programm, dem einzigen im Originalzustand verbliebenen Bielefelder Zeitzeugnis von ehemals über 250 Wäschefabriken der Stadt im 20. Jahrhundert. Wir konnten uns glücklich schätzen, vom Vorsitzenden des Fördervereins des Museums selbst durch die Räume der damaligen Fabrik und des Wohnhauses der Unternehmer geführt zu werden. Er brachte uns die damalige Zeit mit zahlreichen spannenden Anekdoten und Details nahe, so dass es keine der rund 90 Minuten langweilig wurde.

Museum Wäschefabrik Bielefeld

Bei gutem Essen und anregenden Gesprächen haben die noch verbleibenden Teilnehmer die Veranstaltung im numa Restaurant ausklingen lassen.

Fazit

Die engagierten Teilnehmer sowohl von Agentur- wie auch Kunden-Seite mit ihrem unterschiedlichen Sitecore-Hintergrund und die interessanten Vorträge zu aktuellen Themen und Weiterentwicklungen haben das Treffen zu einer erfolgreichen Veranstaltung gemacht, aus der sicherlich jeder Teilnehmer etwas für seine Arbeit mitnehmen konnte.

Sie haben ebenfalls Interesse an Sitecore-Themen?

Wir freuen uns auf die Weiterentwicklung der Sitecore Usergroup Deutschland und viele weitere regelmäßige Treffen mit konstruktivem Austausch. Wenn auch Sie mit Sitecore arbeiten und Interesse an den kommenden SUG-Veranstaltungen haben, melden Sie sich gerne unter sugde@comspace.de. Dann schreiben wir Sie an, wenn das nächste Treffen in Planung ist.

Hybris Partnertage 2014 – Recap

Hybris Partnertag Eindrücke
Hybris Partnertag Eindrücke

Mittwoch, 22.01.14

Keynotes von Carsten Thoma und Mark Jeffries auf dem Hybris Partnertag

Die Keynote von Mark Jeffries  zum Thema Präsentation ist unterhaltsam, praxistauglich und jeder Zuhörer bekommt humorvoll einen Spiegel über seine eigenen Präsentations(un)fähigkeit vorgehalten.

Die weiteren Vorträge laufen an diesem Tag alle im großen Auditorium. Wie von anderen Partnertagen gewohnt gibt´s einen Rückblick auf das zurückliegende Geschäftsjahr und sehr beeindruckende Wachstumszahlen. Mit dem Hightlight der Übernahme durch SAP schliesst Carsten Thoma (COO und Gründer) diesen Business Track. Der Sorge des “Auflösens” von Spirit, Innovationskraft und  Fokussierung tritt Carsten entgegen und stellt die Chancen der Zusammenarbeit mit SAP dar.

Was ändert sich durch die Übernahme von Hybris durch SAP?

hybris-Partnertage-E-Commerce-Approach
hybris-Partnertage-E-Commerce-Approach

Natürlich wird sich für alle Partner einiges verändern. SAP Partner sollen nun E-Business Projekte mit Web- und mobilem Fokus abwickeln und wir als relativ kleiner Partner sind nun automatisch SAP Partner. Für uns als comspace ist das cool, es ändert nichts an unserer Arbeitsweise. Für SAP Partner ist das schon herausfordernd, denn Web-Lösungen brauchen eine andere Denkweise.

Ein Track zur Internationalisierung von Hybris zeigt das globale Wachstum. Global Service wird vorgestellt und die Möglichkeiten der Zusammenarbeit mit uns als Partner dargestellt.

Im Track zum globalen Partnerprogramm wird schnell klar, dass SAP hier eine Differenzierung mit Hybris aufgesetzt hat. Erstmal die “Großen” (Deloitte, Accenture, …), dann kleinere Partner. comspace wird sich einen Platz in der Partnerlandschaft erarbeiten.

Ein Track zu Marketing und Strategie schließt den Arbeitstag ab. Omni Channel Commerce (verkaufen über alle Kanäle) und der Erhalt der Eigenmarke Hybris sind die Kernziele. Mit dem Omni Channel Ansatz attackiert Hybris auch bekannte POS Anbieter.

Gemeinsam sind wir vier noch Essen gegangen und haben Chancen und Auswirkungen des Gesehenen für comspace besprochen.

Donnerstag, 23.01.14

hybris Partnertage Manufacturing
hybris Partnertage Manufacturing

Die Masse wird geteilt, Business Leute (GF, Vertrieb, Business Development, Produktmanager) verbleiben in der Zeltstadt. Technisch Interessierte und Entwickler treffen sich in einem Hotel.

In den Business Tracks werden neue Geschäftsmodelle, branchenspezifische Lösungen für Banking und Telcos, Randthemen über E-Commerce hinaus und Hinweise zur Positionierung von Hybris im Vertriebsprozess gegeben.

Wir schauen in der Zeltstadt bei den Partnern vorbei, mit E-Spirit und Adobe haben zwei CMS Hersteller – neben Payment Partnern und Conversion Optimierern – einen Stand auf dem Partnertag und lassen uns die Manufacturing Lösungen und den B2B Accelerator ausgiebig demonstrieren.

Noch interessanter für comspace sind jedoch die Netzwerkgespräche, die uns innerhalb der Partnerlandschaft immer stärker einbinden. Donnerstag ist ein wertvoller Tag mit guten Kontakten.

Was bietet der Hybris Partner Summit auf der technisch Seite?

Im Technical Track gab es Einblicke in neue technische Errungenschaften der Plattform. Im Zuge der Integration mit SAP wird eine neue “Datendrehscheibe” mit weitreichenden Funktionen entwickelt. Dieser s.g. Data Hub wird es erlauben mit Hilfe von ImPex Daten aus Systemen Rollbackfähig zu Importieren. Diese Möglichkeit existiert bis dato nicht im Hybris und festigt damit die Position für hochwertige Integrationen in Firmennetze.

Der Anwendungszweck entstammt der Integration mit SAP und wird es erlauben auch zukünftige Drittsystem komfortabel an das System zu integrieren und langfristig konsistent zu halten.

hybris Partnertag Customer-Loyalty und Digital Consumer Trends
hybris Partnertag Customer-Loyalty und Digital Consumer Trends

Produktpräsentation, Suche und Marketing-Kampagnen in Hybris

Auch in Bereich der Produktpräsentation hat sich einiges getan. Die Enterprise Search Engine SolR wurde über eine neue Abstraktion (die Commerce Search) dazu gebracht treffende Produkte einfacher beeinflussbar zu machen. Durch ein Scoring verfahren, ist es nun möglich Produkte zu “Boosten”:

Anhand von Regelwerken die intuitiv zu schreiben sind, ist es möglich komplexe Marketingkampagnen zu erstellen. Dabei erhält jede Regel einen “Score” Wert, der bei einem Treffer das Produkt weiter oben platziert. Beispielsweise wäre eine sinnvolle Score Regel, dass Produkte mit Lagerbestand zu bevorzugen sind. Eine entsprechende Regel würde dann z.B. inStockFlag EQUAL_TO true  BOOST 500 aussehen. Damit würden automatisch Treffer eines Suchbegriffs nochmal weiter unterteilt werden. Die Treffer mit Bestand werden dann bevorzugt. Es können beliebig viele Regeln erstellt werden. Wenn wir z.B. eine bestimmte Marke zusätzlich noch bevorzugen möchte, würde einfach eine zweite Boost Regel erstellt werden. Die Bevorzugung einer Marke würde mit folgender Regel etabliert werden: brand EQUAL_TO ‘Coca Cola’ BOOST 50.

In unserem fiktivem Beispiel würden entsprechend Waren der Marke Coca Cola bevorzugt werden, wenn beide Bestand haben.  Im Zusammenspiel der Regeln ergibt sich die Sortierung welche „stärker“ von der ersten Regel (Boost 500) als von der zweiten Regel beeinflusst wird (Boost 50). Die Summe der Werte ist jeweils entscheidend für die finale Sortierung. Diese Scoring Werte werden dann über die gesamten Suchergebnisse genutzt und nicht nur für die “Sieger”. Sollten also zwei Produkte beide keinen Bestand haben (und damit die erste Regel nicht erfüllen), würde weiterhin die zweite Regel für die Sortierung maßgeblich sein. Die Felder sind dabei im SolR Index zu hinterlegen. Die SolR Felder sind frei zu kombinieren und entsprechend ergeben sich eine Vielzahl an Marketing-Möglichkeiten.

Time to Market und Erweiterungen

Im Bereich Time To Market hat Hybris viel an dem Grundkonzept erweitert. Der hierfür entscheidene Accelerator ist bereits seit mehreren Jahren Bestandteil des Hybris Konzepts und liefert Entwicklern einen strukturierten Basis-Aufbau für ihr nächstes eCommerce Projekt. Dieses Konzept wurde nun erweitert und soll die Wiederverwendbarkeit von Bestandteilen innerhalb eines Accelerator Projekts erhöhen. Das Konzept wird als AddOns bezeichnet und stellt eigene Extensions dar, die jeweils im Buildprozess in das Accelerator Projekt integriert werden. Für Hybris Kunden sowie Partner ist dieses besonders interessant, da nun zu der beschleunigten Entwicklung der Basisseite auch noch benötigte Komponenten wiederverwendet werden können.

Speziell für diese Wiederverwendung von Komponenten ist die Überlegung einen Marketplace zu eröffnen auf dem Kunden bereits vorgefertigte Teillösungen kaufen, welches einen Wandel im Ecosystem von Hybris einläuten könnte. Höhere Qualität in kürzeren Intervallen von Spezialisten für Teilsegmente, statt einer Versteifung auf einen Dienstleister für das Gesamtprojekt.

Hybris Neuerungen im Business Development

Im ersten Block des dreiteiligen Delivery Track haben uns die Hybris Berater und Produkt-Manager einen Einblick über die Neuerungen von hybris 5.1 ermöglicht. Hervorzuheben sind unter anderem folgende Neuerungen:

Das B2C Commerce Chinese Accelerator

Hybris zeigt damit die Umsatzkraft und die Wichtigkeit des asiatischen Marktes indem sie speziell für China ein eigenes accelerator bauen, welche an den Anforderungen des chinesichen Markt zugeschnitten sind:

  • Eigene chinesische Storefront Template, welche an das click und surf Verhalten des chinesischen Users angepasst ist.

  • Integration mit verschiedene chinesichen Lösungen; z.B chinesische sozial Netwerk, chinesische payment service Provider Alipay,  chinesische Store locator Baidu etc…

  • Das neue Commerce Search (wurde oben schon detailliert von Jan erlautert)

Der B2B Commerce Accelerator

hybris Business B2B Accelerator
hybris Business B2B Accelerator

Hybris bringt in der Version 5.1 mit dem „Multidimensional Produktmodel“ neue Features, die auf den Geschäftskundenbereich ausgerichtet sind und sich B2B Commerce Accelerator nennen:

  • Schnelle Implementierung von B2B Multichannel Commerce
  • Kanalübergreifende Informationsverbreitung in Echtzeit
  • Individuelle Kundenansprache
  • Integration des Vertriebsangebots
  • Optimierung der Auftragsbearbeitung
  • Budgetkontrolle, Account Management, Bestellmanagement

 

Hybris Professional Services

Im zweiten Block, der sich eigentlich um Qualität und Best Practices im Hybris Projekten drehen sollte, war mehr eine Info-Veranstaltung für die “Hybris Professional Services”, die Leistungen umfassen wie:

  • Software Workshops
  • Anforderungsworkshops und -Analysen
  • Lösungsarchitekturworkshops und -Analysen
  • Performance-Analysen
  • Bereitstellungsanalysen

Zum Abschluss des Tages stellten die Hybris Consultants bei Kunden gesammelte Erfahrungen (Best Practices und Herausforderungen) zu drei Themen vor:

  • Click & Collect
  • CMS Integration
  • ERP Integration : SAP und weiteren

hybris Partnertage Open Space Themen
Open Space Themen

Freitag, 24.01.14

Open Spaces beim Hybris Partnertag

Die Bilder der im großen Konferenzsaal sind beeindruckend und die Tragluftdächer für eine akkustische Entkopplung der Teilnehmer clever gemacht, die meisten Entscheider sind wieder weg.

Der harte Kern diskutiert technische Themen, aber auch Best Practices zur Projektabwicklung oder wie sich Partner weiterentwickeln können. Doch auch Zukunftsthemen wie Bezahlen mit Bitcoins sind ein Thema.

hybris-Partnertage-Open-Space-Responsive-Web.jpg
Responsive Web Open-Space

Am frühen Freitag Nachmittag endet die Veranstaltung nach drei Open Space Runden.

Rege Diskussion gab es um die mögliche Entwicklung von Hybris als teilweise Open Source Version, so wie dieses Konzept bei Mitbewerbern in Form von Community Editions bereits gelebt wird. Dabei gibt es Hybris intern mehrere Fürsprecher und ein klares Votum der primär aus dem Development-Sektor Anwesenden. Speziell der Einblick in die Hybris internen Abläufe war für diese eine gewünschte Arbeitserleichterung. Ein Konzept für eine quelloffene Basis-Plattform wäre hier ein möglicher Weg. Es wurde hier jedoch noch keine Entscheidung gefällt, aber dieses wird Firmenintern für weiteren Gesprächsstoff sorgen.

 Weiterführende Informationen

Unser Team
Unser Team