November Happy Hour will be moved to Thursday December 5th.

Proper use of Promotions BETA

Vote:
 

I am trying to use some of the new BETA features and have some questions how to properly use it. I am trying new Promotions and created my own promotion implementation like in this article: http://www.david-tec.com/2015/07/creating-a-custom-promotion-with-the-new-episerver-commerce-9-promotion-engine-beta---part-1/

There is a small difference in IPromotionResult implementation's ApplyReward method I made - I am not modifying anything, but returning reward I want to be applied to whole order (in the sample I skipped discount calculation and use constant):

public IEnumerable ApplyReward()
{
	if (Status != FulfillmentStatus.Fulfilled || OrderGroup == null)
	{
		return new[] {NoReward()};
	}

	return new []
	{
		new PromotionInformation
		{
			Description = Description,
			IsActive = true,
			SavedAmount = 10
		}, 
	};
}

I assumed that consumer of this method will apply SavedAmount to OrderGroup (Cart), but I am stuck to achieve it.

In the controller I tried to call IPromotionEngine's method Run, I tried to call Cart validate workflow, tried to save Cart with IOrderRepository, tried to save Cart just with Cart.AcceptChanges(), but my promotion is not applied.

This works partially:

_promotionEngine.Run(cart);
_orderRepository.Save(cart);

It runs my ApplyReward method, but result is not used. I also see that after _promotionEngine.Run(cart) new promotion is added to ((IOrderGroup)cart).Promotions collection, but _orderRepository.Save(cart) seems not saving items in this collection. After loading cart again ((IOrderGroup)cart).Promotions collection is empty.

So the questions:

1. How to apply discount returned by ApplyReward or should ApplyReward update order?

2. How to save ((IOrderGroup)cart).Promotions that those doesn't disappear?

#143293
Jan 19, 2016 15:50
Vote:
 

We are currently reworking the processor so it will work as you expect when returning the saved amount the engine will modify the dicscount of the ILineItem for you.  This should be released in a couple weeks time.

1. In the version you are wroking with you will need to set the ILineItem.LineItemDiscountAmount or IlineItem.OrderLevelDiscountAmount manually.

2. You need to enable VNextWorkflows to save PromotionInformation right now.  http://world.episerver.com/documentation/Items/Developers-Guide/EPiServer-Commerce/9/Marketing/promotions-and-workflow-activities-beta/

#143308
Jan 20, 2016 1:17
Vote:
 

Mark, thanks for the answer, but I have more questions :)

1. Why order level discount should be applied to LineItem? And which line item should I use - first or it doesn't matter?

2. I have VNextWorkflows enabled (using code in initializable module). Should I call Cart Validate workflow or there are some new workflows to run?

#143312
Jan 20, 2016 6:44
Vote:
 

1.  Depends on the type of promotion.  Order level promotions are stored with the distributed amount per item in OrderLevelDiscountAmount in order to be able process returns.  For example $100 of your order would be would be $50 OrderLevelDiscount if you had two items that cost $100 each.  Again we are changing this in the interface for promotion processors so you wont need to know these details just the amount you want to save.  Also in Evaluate you should determine which lineitems matched in the promotion.  Apply reward is supposed to use the items that were matched in evaluate.  You also want to pass in the ContentLink to PromotionInformation so you know which sku was discounted for historical puprposes.

2.  In your initalization module have a [ModuleDependency(typeof(Mediachase.Commerce.Initialization.CommerceInitialization))]  Then just Addfeautre and EnableFeature no need to initalize.  I will update the docuementation.

#143316
Jan 20, 2016 10:07
Vote:
 

1. Thanks, this helps to understand the reason behind line item's OrderLevelDiscountAmount. But when I use IOrderFormCalculator's GetSubTotal() method, it calculates SubTotal taking OrderLevelDiscountAmount into account which is not right. SubTotal should be sum before applying OrderLevelDiscountAmount and Total should include OrderLevelDiscountAmount.

About ContentLink in the PromotionInformation - if I skip it I get Null reference exception when workflow runs, so I have to provide it always.

2. Now I use config file to enable new features and it works well.

Now I changed ApplyReward to this and it works:

public IEnumerable<PromotionInformation> ApplyReward()
{
	if (Status != FulfillmentStatus.Fulfilled 
		|| OrderGroup == null
		|| !HasLineItems())
	{
		return new[] {NoReward()};
	}

	var totalDiscount = CalculateDiscountAmount();

	var lineItems = OrderGroup.Forms.First().Shipments.First().LineItems;
	var lineItemDiscount = totalDiscount/lineItems.Count;

	lineItems.ToList().ForEach(x => x.OrderLevelDiscountAmount = lineItemDiscount);

	return lineItems.Select(x => new PromotionInformation
	{
		Description = Description,
		IsActive = true,
		SavedAmount = lineItemDiscount,
		ContentLink = _referenceConverter.GetContentLink(x.Code)
	});
}

In the CalculateDiscountAmount() method I have to manually calculate line item total before discounts:

private decimal CalculateDiscountAmount()
{
	var orderForm = OrderGroup.Forms.First();
	if (!orderForm.Shipments.Any())
	{
		return 0;
	}
	var itemTotal = orderForm.Shipments.First().LineItems.Sum(x => x.PlacedPrice*x.Quantity);
	var totalDiscount = itemTotal*(DiscountPercent/100M);
	return totalDiscount;
}

It would be nice if IOrderFormCalculator would contain method which would calculate line item sum before any discounts and also fix GetSubTotal() as mentioned in 1..

While everything seems to work, there is one issue I found. When there is only one line item and I am trying to remove it I get this exception:

The provided content link does not have a value.
Parameter name: contentLink

[ArgumentNullException: The provided content link does not have a value.
Parameter name: contentLink]
   EPiServer.Core.DefaultContentLoader.Get(ContentReference contentLink, LoaderOptions loaderOptions) +502
   Mediachase.Commerce.Workflow.Activities.CalculateDiscountsVNextActivity.Execute(ActivityExecutionContext executionContext) +314
   Mediachase.Commerce.WorkflowCompatibility.Activity.Execute() +40
   Mediachase.Commerce.Engine.<>c__DisplayClass1`1.<Do>b__0() +314
   Mediachase.Commerce.Engine.ActivityFlowRunner.Execute() +124
   Mediachase.Commerce.Engine.ExecutionManager.ExecuteActivityFlow(String name, ActivityFlowContext context) +275

It seems that one Promotion in the cart has empty content link now. I am removing line item from cart like this:

var lineItem = cart.GetLineItem(code);
PurchaseOrderManager.RemoveLineItemFromOrder(cart, lineItem.LineItemId);

Is it the right way to remove line item so that promotions will work?

#143322
Edited, Jan 20, 2016 11:13
Vote:
 

We will look into IOrderFormCalculator.GetSubTotal it should be total before discounts, tax, handling etc.

Yes that is the correct way to remove the lineItem.  It may be you need to save the cart for now to get past the error.  We will look into this as well.

Thanks for the feedback 

#143324
Jan 20, 2016 11:41
Vote:
 

Just tried to save cart before running workflow and it didn't help. Still same error. Tried to save Cart with OrderRepository and also with cart.AcceptChanges() and no difference.

#143325
Jan 20, 2016 11:49
Vote:
 

Can you check the PromotionInfromation table I think there might be some orphaned records in there that has no contentLink

#143326
Jan 20, 2016 11:53
Vote:
 

I deleted the cart in Commerce Manager and started again. PromotionInformation table was empty. Added new item to the cart and applied discount. One record appeared in PromotionInformation table and it has valid ContentReference. Now I am trying to remove item and exception still appears. PromotionInformation table still have same record with valid ContentReference.

#143327
Jan 20, 2016 11:59
Vote:
 

Okay, I will file a bug.  PurchaseOrderManager.RemoveLineItemFromOrder should remove the promotionInformation for the remove lineitem.  To work around I suggest you remove the PromotionInformation manually for removed LineItem

#143328
Jan 20, 2016 12:03
Vote:
 

I am trying to remove PromotionInformation, but it seems to not work at least as I am doing it.

So I tried to remove it like this:

PurchaseOrderManager.RemoveLineItemFromOrder(cart, lineItem.LineItemId);
var lineItemLink = _referenceConverter.GetContentLink(code);
var orderGroup = (IOrderGroup) cart;
var promotion = orderGroup.Promotions.FirstOrDefault(x => x.ContentLink.Equals(lineItemLink, true));
if (promotion != null)
{
	orderGroup.Promotions.Remove(promotion);
	cart.PromotionInformationRepository.Service.Save(orderGroup.Promotions, cart.OrderGroupId);
}

It finds promotion and removes from Promotions collection, but after save it doesn't remove promotions from database. I also tried to save Cart itself, but it didn't help.

#143329
Edited, Jan 20, 2016 12:23
Vote:
 

Hi,

Did you enable the VNextFeature?

/Q

#143364
Jan 21, 2016 14:04
Vote:
 

Yes, I did. And checked it using IFeatureSwitch.

#143365
Jan 21, 2016 14:07
Vote:
 

OK, I looked into PromotionsInformationSave SP and see the problem there. It does not support deleting removed promotion information. So your solution is now call 

cart.PromotionInformationRepository.Service.Delete(cart.OrderGroupId);

before saving.

I'll file a bug for this.

Thanks,

/Q

#143366
Jan 21, 2016 14:12
Vote:
 

So it will delete all promotions and will add back those which should be in Promotions collection?

#143367
Jan 21, 2016 14:15
Vote:
 

Yes it'll work that way. The "current" collection in OrderGroup.Promotions should win and be saved. 

/Q

#143368
Jan 21, 2016 14:19
Vote:
 

Now I do it like this:

var lineItem = cart.GetLineItem(code);
PurchaseOrderManager.RemoveLineItemFromOrder(cart, lineItem.LineItemId);
var lineItemLink = _referenceConverter.GetContentLink(code);
var orderGroup = (IOrderGroup) cart;
var promotion = orderGroup.Promotions.FirstOrDefault(x => x.ContentLink.Equals(lineItemLink, true));
if (promotion != null)
{
	orderGroup.Promotions.Remove(promotion);
	cart.PromotionInformationRepository.Service.Delete(cart.OrderGroupId);
	cart.PromotionInformationRepository.Service.Save(orderGroup.Promotions, cart.OrderGroupId);
}
cart.RunWorkflow(OrderGroupWorkflowManager.CartValidateWorkflowName);
cart.AcceptChanges()

But I still get same error running workflow when deleting line item first time. When checking DB, PromotionInformation is deleted. Line item still in cart.

Then I click delete in UI second time and line item gets deleted.

Seems that workflow still uses cached promotions from somewhere.

#143369
Jan 21, 2016 14:38
Vote:
 

Thanks Quan and Mark for help!

Finally I get also removal of last item to work. The issue was in my promoton result class on ApplyReward. When there is no promotion I returned single promotion with IsActive = false, but for it to work properly I have to return empty sequence. So the code for ApplyReward now is:

public IEnumerable<PromotionInformation> ApplyReward()
{
	if (Status != FulfillmentStatus.Fulfilled 
		|| OrderGroup == null
		|| !HasLineItems())
	{
		return Enumerable.Empty<PromotionInformation>();
	}

	var lineItems = OrderGroup.Forms.First().Shipments.First().LineItems;

	lineItems.ToList().ForEach(x => x.OrderLevelDiscountAmount = CalculateOrderLevelDiscount(x));

	return lineItems
		.Where(x => x.OrderLevelDiscountAmount > 0)
		.Select(x => new PromotionInformation
		{
			Description = Description,
			IsActive = true,
			SavedAmount = x.OrderLevelDiscountAmount,
			ContentLink = _referenceConverter.GetContentLink(x.Code)
		});
}
#143665
Jan 28, 2016 15:38
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.