
h3. Promotion Rules Engine
ECF includes a Promotion Rules Engine designed to provide a flexible and fast framework to create different types of promotions that will be displayed on the front-end.
The Promotion Engine has two distinct use cases:
* Design mode: To configure promotions
* Runtime mode: To display and apply promotions
h4. Design Mode
Promotions are created either using the graphical user interface in the administration console or by a developer. Most of the promotions can be created by using "Custom Promotion" type, which you can select when creating new promotion. In some cases when promotions are somewhat unique, developer will need to create custom expressions and user interfaces (please refer to the tutorial section for that).
There are 3 groups of promotions which determine in which scenario they are executed. Those are:
* CatalogEntry - executed against individual entry and can be applied to the items in the catalog and shown during search, catalog browsing
* Order - executed against the whole order and will only display on the shopping cart / order pages
* Shipping - executed against individual shipments and will be shown only on the order/checkout pages
h5. Expressions
Expressions is a core technology behind Marketing system. It allows for a very flexible and standards based way to extend different aspects of the system. Promotions, Customer Segments and Policies all rely on expressions.
When designing new Marketing we needed a way to provide a more flexible rules based extension mechanism. These rules had to be external and not built into the framework itself, since they will be changed often. It also had to have high performance and ability to process hundreds of rules for each customer accessing the site.
h5. Architecture
The Architecture of Expression Manager is similar to the Provider model used in .NET Framework. The engine relies on external class to process the expression which can be specified in the configuration file. That class simply needs to implement IExpressionValidator interface and the actual implementation is up to that class.
With ECF we provide one of the implementation that relies on Windows for Workflow Foundation Rules Engine, so if you pick that you can use our Expression Editor to create expressions. The Expression Editor is located in the following path of your solution:
BusinessLayer\MarketingSystem\ExpressionEditor
You can find more details on how rules engine works here [Introduction to the Windows Workflow Foundation Rules Engine|http://msdn2.microsoft.com/en-us/library/aa480193.aspx].
h3. Runtime Mode
h4. Execution Sequence
There are two distinct ways the promotions are applied to the product: during browsing or searching of catalog and when viewing shopping cart or checking out.
They both rely on "MarketingContext.EvaluatePromotions" method which cycles through each promotion prioritising and filtering them. The sequence for EvaluatePromotion goes like this:
# Checks global exclusivity (if global promotion has been applied, it stops executing)
# Checks target group
# Checks group exclusivity
# Checks limits (apply only 1 time)
# Checks dates and status
# Verifies coupon
# Performs basic catalog/node/entry filtering (not currently used in demo)
# Checks conditions (expressions)
# Checks policies
# Commits all new records (promotions added)
h5. Browsing Catalog
During catalog browsing you would typically use relatively simple promotions that just apply to the individual products.
h5. Execution Sequence
# StoreHelper.GetDiscountPrice(Entry entry, string catalogName)
# PromotionHelper.Eval(PromotionFilter filter)
# MarketingContext.Current.EvaluatePromotions(true, this.PromotionContext, filter)
h5. Step 1
During step 1 we get the sale price (which will be used to calculate the base price for which discounts if any will be added) using the following call:
{code:csharp}
decimal minQuantity = 1;
// get min quantity attribute
if (entry.ItemAttributes != null)
minQuantity = entry.ItemAttributes.MinQuantity;
// we can't pass qauntity of 0, so make it default to 1
if (minQuantity <= 0)
minQuantity = 1;
// Get sale price for the current user
Price price = StoreHelper.GetSalePrice(entry, minQuantity);
{code}
*Note:* we pass Minimum Quantity when browsing the item in the catalog, which will typically default to 1
Next we need to setup the PromotionContext, which will provide context for our rules to execute against. Since it is just one product in this case all we need to do is Create new PromotionEntry object (which represent one product for our marketing system) and populate it with all the attributes (meta fields from the catalog entry) that might be used during promotion evaluation. Some other key concept during this stage are:
* MarketingContext - this is where we add extra objects that can be used by the expression engine
* TargetGroup - we set it to the entry, since this is the only promotion group we should be executing when browsing catalogs
* IPromotionEntryPopulate - this is an interface that allows developer to override how the properties are copied from Entry object to the PromotionEntry object, this is configured in ecf.marketing.config file using PromotionEntryPopulateFunctionType config section (set to "Mediachase.Commerce.Marketing.Validators.PromotionEntryPopulate,Mediachase.Commerce.Marketing.Validators" by default)
{code:csharp}
// Create new promotion helper, which will initialize PromotionContext object for us and setup context dictionary
PromotionHelper helper = new PromotionHelper();
// Get current context
Dictionary<string, object> context = MarketingContext.Current.MarketingProfileContext;
// Create filter
PromotionFilter filter = new PromotionFilter();
filter.IgnoreConditions = false;
filter.IgnorePolicy = false;
filter.IgnoreSegments = false;
filter.IncludeCoupons = false;
// Create new entry
PromotionEntry promotEntry = new PromotionEntry(catalogName, String.Empty, entry.ID, price.Amount);
// Populate entry parameters
((IPromotionEntryPopulate)MarketingContext.Current.PromotionEntryPopulateFunctionClassInfo.CreateInstance()).Populate(ref promotEntry, entry);
PromotionEntriesSet sourceSet = new PromotionEntriesSet();
sourceSet.Entries.Add(promotEntry);
// Configure promotion context
helper.PromotionContext.SourceEntriesSet = sourceSet;
helper.PromotionContext.TargetEntriesSet = sourceSet;
// Only target entries
helper.PromotionContext.TargetGroup = PromotionGroup.GetPromotionGroup(PromotionGroup.PromotionGroupKey.Entry).Key;
{code}
And at last we call the eval method and return the discounted price back.
{code:csharp}
// Execute the promotions and filter out basic collection of promotions, we need to execute with cache disabled, so we get latest info from the database
helper.Eval(filter);
// Check the count, and get new price
if (helper.PromotionContext.PromotionResult.PromotionRecords.Count > 0)
return ObjectHelper.CreatePrice(price.Amount - GetDiscountPrice(helper.PromotionContext.PromotionResult), price.CurrencyCode);
else
return price;
{code}
h5. Step 2
During step 2 we simply execute the promotion engine with all the information collected during step 1:
{code:csharp}
MarketingContext.Current.EvaluatePromotions(true, this.PromotionContext, filter);
{code}
h5. Step 3
Goes through each promotion, checks the policies, customer segments, dates and validates the expressions.
When promotion is successfully applied, it is typically added to the PromotionResult object as a PromotionItemRecord object which contains information about the items affected and the discount applied.
h5. Cart/Checkout
During checkout more complex promotions can be used. In this case you are working in the context of the order system. You will no longer calculate discount directly, but through the use of workflow activities.
h6. Execution Sequence
# CartHelper.RunWorkflow("CartValidate")
# CalculateDiscountsActivity.CalculateDiscounts
h5. Step 1
This is a step where you initiate the workflow. The workflow (typically) will check if entries exist and available, remove all the existing discounts and then execute the CalculateDiscountsActivity. This will be done during each step in the checkout process or when accessing the shopping cart.
h5. Step 2
The logic of discounts activity is very similar to the one used during browsing with exception of two additional discount groups: Order and Shipment. The engine first calculates line item discounts, then order and then shipments.
The code below calculates discount for each individual order form:
{code:csharp}
#region Determine Order level discounts
foreach (OrderForm form in order.OrderForms)
{
// Now process global order discounts
// Now start processing it
// Create source from current form
sourceSet = CreateSetFromOrderForm(form);
promoContext.SourceEntriesSet = sourceSet;
promoContext.TargetEntriesSet = sourceSet;
promoContext.TargetGroup = PromotionGroup.GetPromotionGroup(PromotionGroup.PromotionGroupKey.Order).Key;
}
// Evaluate conditions
MarketingContext.Current.EvaluatePromotions(useCache, promoContext, filter);
#endregion
{code}
*Note:* Just like in previous step the CalculateDiscountsActivity relies on IPromotionEntryPopulate interface to populate properties for each item. This means that there is only one place for you to customize the logic related to populating custom properties and it can be completely replaced without any changes to the core logic.
{code:csharp}
((IPromotionEntryPopulate)MarketingContext.Current.PromotionEntryPopulateFunctionClassInfo.CreateInstance()).Populate(ref entry, lineItem);
{code}
h3. Coupon Codes (Reservations and Expiration)
Coming soon.