Dynamics Retail Discount Extensibility – Multiple ISVs
Update - this blog post has been moved to Dynamics 365 Community.
The core of Dynamics retail discount extensibility is to allow new discount types, and it is natural for a customer to employ new discount types from multiple ISVs. The post is also a replacement of an early post of how to register a new discount type.
The issue with the original retail discount store approach is that someone has to integrate two discount types in the retail discount store. Now we would like to achieve ISV isolation, i.e. to be able to register multiple discount types from multiple ISVs independently.
The core ingredients for a new discount type are the new discount type itself, creation of the new discount type and loading discount specific data. Introduce the concept of the discount package.
public interface IDiscountPackage
{
PeriodicDiscountOfferType DiscountOfferType { get; }
DiscountBase CreateDiscount(PeriodicDiscount discountAndLine);
void LoadDiscountDetails(
Dictionary<string, DiscountBase> offerIdToDiscountMap,
IPricingDataAccessor pricingDataManager,
SalesTransaction transaction);
}
For ISV isolation, we can't have ISV data accessor piggyback on core IPricingDataAccessor. Now you may notice that it does not cover the new discount data accessor interface which would bridge channel and AX difference. The interface is still there, just not in IDiscountPackage directly, as we can see in the following IDiscountPackage implementation for discount offer with amount cap.
public class DiscountPackageAmountCap : IDiscountPackage
{
private IDataAccessorAmountCap amountCapDataAccessor;
public DiscountPackageAmountCap(IDataAccessorAmountCap amountCapDataAccessor)
{
this.amountCapDataAccessor = amountCapDataAccessor;
}
public PeriodicDiscountOfferType DiscountOfferType
{
get { return (PeriodicDiscountOfferType)AmountCapDiscount.PeriodicDiscountOfferTypeAmountCap; }
}
public DiscountBase CreateDiscount(PeriodicDiscount discountAndLine)
{
ThrowIf.Null(discountAndLine, "discountAndLine");
return new AmountCapDiscount(discountAndLine.ValidationPeriod);
}
public void LoadDiscountDetails(
Dictionary<string, DiscountBase> offerIdToDiscountMap,
IPricingDataAccessor pricingDataManager,
SalesTransaction transaction)
{
ThrowIf.Null(offerIdToDiscountMap, "offerIdToDiscountMap");
IEnumerable amountCapOfferIds = offerIdToDiscountMap.Where(p => (int)p.Value.PeriodicDiscountType == AmountCapDiscount.PeriodicDiscountOfferTypeAmountCap).Select(p => p.Key);
if (amountCapOfferIds.Any())
{
IEnumerable caps = this.amountCapDataAccessor.GetDiscountAmountCapsByOfferIds(amountCapOfferIds) as IEnumerable;
foreach (DiscountAmountCap cap in caps)
{
AmountCapDiscount discount = offerIdToDiscountMap[cap.OfferId] as AmountCapDiscount;
if (discount != null)
{
discount.DiscountAmountCap = cap.AmountCap;
discount.ApplyBaseReduction = cap.ApplyBaseReduction;
}
}
}
}
}
The next step is to register the discount package in the retail discount engine. At this moment, you can register it at the top of pricing services customization, before calling pricing engine. AX follows similar approach.
IDiscountPackage package = new DiscountPackageAmountCap(new ChannelDataAccessorAmountCap(requestContext));
PricingEngineExtensionRepository.RegisterDiscountPackage(package);
We are thinking of total dynamic invocation to avoid any code change at all, such as table-configuration driven.
Related: Dynamics Retail Discount Extensibility - Main picture
Related: Dynamics Retail Discount Extensibility - Sample Test