allBlogsList

Customize Sitecore Experience Profile Order Details View With Custom Data

There are instances when we use the properties bag in the Sitecore Commerce Connect entities (or Commerce Server Connect entities) to facilitate data exchange. One example of populating the properties with custom data is 'Unit Of Measure' in the sitecore commerce CartLine entity.

Whenever an order is submitted, the outcome is captured in xDB and we can navigate to the experience profile to see the order details and summary. When you click on the "Orders" sub tab, it gives a list of all orders and then you can click an order and view details. In this blog, I am demonstrating, how to add more columns to the 'Lines' section. This is how it looks OOTB.

My order entity has cart line properties of 'UnitOfMeasure', 'Pieces', etc. Whenever a weakly typed property is added to the order line, it does not make it to mongo document because the the outcome class "Sitecore.Commerce.OutcomeData.SubmittedOrderOutcomeData" removes all the properties. [It inherits from 'BaseOutcomeData' class]

 

So, to include the properties data in the reports, the first step is to include the properties dictionary from the 'Sitecore.Commerce.Entities.Orders.Order' object in mongo. We will need to override the base class behavior. Since this was exclusively required for Order submission, I decided to create a new outcome class to be used specifically in '<commerce.orders.submitvisitororder>' pipeline.

 
public class KPSubmittedOrderOutcomeData : SubmittedOrderOutcomeData
	{
		
		public override void Serialize(Dictionary<string,> values)
		{
			if (this.Order == null)
				return;
			values["ExternalId"] = this.ExternalId;
			values["ShopName"] = this.ShopName;
			values["Test"] = this.Order.ShopName;
			
			AddEntityToSimpleValues(this.Order, values, "Order");
		}

		private void AddEntityToSimpleValues(T connectEntity, Dictionary<string,> values, string baseTypeKey) where T : Entity
		{
			EntitiesIncludedInXdbTypes entitiesIncludedInXdb = ConnectSettings.EntitiesIncludedInXdb;
			if (entitiesIncludedInXdb == EntitiesIncludedInXdbTypes.Base || entitiesIncludedInXdb == EntitiesIncludedInXdbTypes.Both)
			{
				//T baseType = connectEntity.ToBaseType();
				//baseType.RemoveAllProperties();
				values[baseTypeKey] = connectEntity.ToJson();
			}
			if (entitiesIncludedInXdb != EntitiesIncludedInXdbTypes.Custom && entitiesIncludedInXdb != EntitiesIncludedInXdbTypes.Both)
				return;
			values["Custom"] = connectEntity.ToJson();
		}

		public Order ToBaseType(Entity entity)
		{
			return JsonConvert.DeserializeObject(JsonConvert.SerializeObject((object)entity));
		}

		public string ToJson(Entity entity)
		{
			return JsonConvert.SerializeObject((object)entity);
		}
		
	}

The next step is to replace the processor that triggers the outcome and replace the commerce outcome entity.

<commerce.orders.submitVisitorOrder> <processor type="KP.ECSPlusSitecore.Pipelines.KPTriggerOrderOutcome, KP.ECSPlusSitecore"   patch:instead="processor[@type='Sitecore.Commerce.Pipelines.Orders.TriggerOrderOutcome, Sitecore.Commerce']"> <OutcomeId>{9016E456-95CB-42E9-AD58-997D6D77AE83}</OutcomeId> </processor> </commerce.orders.submitVisitorOrder> <commerce.outcomeData> <KPSubmittedOrderOutcomeData type="KP.ECSPlusSitecore.Pipelines.KPSubmittedOrderOutcomeData, KP.ECSPlusSitecore"/> </commerce.outcomeData>
//This is how the class 'KPTriggerOrderOutcome' looks like
public class KPTriggerOrderOutcome : TriggerOutcome
	{
		protected override BaseOutcomeData GetOutcomeData()
		{
			return (BaseOutcomeData)OutcomeDataFactory.Create();
		}
	}

After making the above changes, I could see that mongo outcome collection table is storing the properties value.

The next step is to map the properties key's to a column in the CartLine datatable.

For this, I looked at the config and found out that the sections pipelines/group/groupName="ExperienceProfileContactViews"/pipelines/cart-detail-lines is responsible for populating the view for the report.

Specifically, I had to replace the following two processors 

<processor type="Sitecore.Commerce.ExperienceProfile.Contact.Commerce.CartDetails.Processors.ConstructCartLinesTable, Sitecore.Commerce.ExperienceProfile" /> <processor type="Sitecore.Commerce.ExperienceProfile.Contact.Commerce.CartDetails.Processors.PopulateCartLinesWithXdbData, Sitecore.Commerce.ExperienceProfile" reusable="false">

The first one adds columns to the datatable and the second processor maps values to the datatable. So, I created a constant class for the columns that I need to add to the datatable:

public static readonly ViewField UoM = new ViewField("UoM");
public static readonly ViewField DisplayQty = new ViewField("DisplayQty");
public static readonly ViewField Pieces = new ViewField("Pieces");

I used a patch file to replace the two pipelines.This is how my 'ConstructCartLinesTable' processor looks like

public class ConstructCartLinesTable : ConnectClientReportProcessorBase
	{
		
		public override void Process(ReportProcessorArgs args)
		{
			args.ResultTableForView = new DataTable();
			args.ResultTableForView.Locale = CultureInfo.InvariantCulture;
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.LineNumber.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.ProductId.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.ProductName.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.Quantity.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.LineTotal.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.AllowanceSubtotal.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.ChargeSubtotal.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.StockStatus.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.ShippingDate.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.ShippingMethod.ToColumn());
			args.ResultTableForView.Columns.Add(ReportConstants.ClientReportSchemaColumns.PaymentMethod.ToColumn());
			args.ResultTableForView.Columns.Add(KPConstants.UoM.ToColumn());
			args.ResultTableForView.Columns.Add(KPConstants.DisplayQty.ToColumn());
			args.ResultTableForView.Columns.Add(KPConstants.Pieces.ToColumn());
		}
	}
 

This is how the 'PopulateCartLinesWithXdbData' processor override looks like

public class KPPopulateCartLinesWithXdbData : PopulateCartLinesFromConnect
	{
				
		protected override void PopulateRow(BaseOutcomeData outcomeData, Cart cart, CartLine lineItem, DataRow sourceRow, DataRow targetRow)
		{
			targetRow[ReportConstants.ClientReportSchemaColumns.LineNumber.Name] = (object)lineItem.LineNumber;
			targetRow[ReportConstants.ClientReportSchemaColumns.ProductId.Name] = (string)lineItem.Product.Properties["ERPNumber"];
			targetRow[ReportConstants.ClientReportSchemaColumns.ProductName.Name] = (object)(lineItem.Product.ProductName ?? this.GetProductName(outcomeData, lineItem.Product.ProductId));
			
			targetRow[ReportConstants.ClientReportSchemaColumns.LineTotal.Name] = (object)this.FormatTotalCurrencyColumn(lineItem.Total, cart.CurrencyCode);
			targetRow[ReportConstants.ClientReportSchemaColumns.AllowanceSubtotal.Name] = (object)this.GetCartLineAdjustments(false, cart, lineItem);
			targetRow[ReportConstants.ClientReportSchemaColumns.ChargeSubtotal.Name] = (object)this.GetCartLineAdjustments(true, cart, lineItem);
			targetRow[ReportConstants.ClientReportSchemaColumns.StockStatus.Name] = (object)this.GetColumnStringValue(this.GetCartLineStockStatus(lineItem));
			targetRow[ReportConstants.ClientReportSchemaColumns.ShippingDate.Name] = this.GetCartLineShippingDate(lineItem);
			targetRow[ReportConstants.ClientReportSchemaColumns.ShippingMethod.Name] = (object)this.GetColumnStringValue(string.Empty);
			targetRow[ReportConstants.ClientReportSchemaColumns.PaymentMethod.Name] = (object)this.GetColumnStringValue(string.Empty);
			targetRow[KPConstants.UoM.Name] = (string) lineItem.Properties["UnitOfMeasure"];
			if (lineItem.Properties.ContainsProperty(IntegrationConstants.Pieces))
			{
				targetRow[KPConstants.Pieces.Name] = (string)lineItem.Properties[IntegrationConstants.Pieces];
			}
			else
			{
				targetRow[KPConstants.Pieces.Name] = "N/A";
			}


			targetRow[ReportConstants.ClientReportSchemaColumns.Quantity.Name] = (object)lineItem.Quantity;
			targetRow[KPConstants.DisplayQty.Name] = (string)lineItem.Properties["QtyOrderedSC"];

		}
	}

The last step: Add the mapping in the core database for the view

All set, now you can view your order lines in experience profile with customized data.

Another One: [Notice how I am showing decimal quantities, using the properties bag]