allBlogsList

How to Extend the Promotions View

Promotions: Customize Master view by using Entity Views and Action API

October 19th, 2021

This blog provides a step-by-step guide on how to customize a Master view for a promotion item programmatically.

Promotion defines a set of qualifying rules that must be met in order to apply benefits at the cart level or for specific sellable items within a cart. We can create a promotion after associating a promotion book with a catalog. The master view of a promotion item looks like the image below:

PB1

Let’s assume that we have to meet a specific set of requirements, shown below:

  • Extend the master view to show additional information of a promotion. The additional view should include the following fields:

            o   Disclaimer: Single Line Text

            o   Promotion Type: Drop Down

            o   Combinable: Checkbox

  • If a user chooses to click the tick button, then save information to the database
  • Display the information once saved

Expected Result:

PB2

PB3

PB4

Implementation Approach

1.      Add a new Blocks folder in your custom Plugin.

2.      Create a new view and add additional properties to the view inside an “EntityView” class defined under the Blocks folder.

3.      Define an action policy to populate the new view and its settings. 

4.      Execute “DoAction” on click of the tick button which performs a transaction and validates and stores the information in the database.

5.      Display the saved information by entity view class defined in step 1.

Code

Create a new view and add additional properties to the view inside an “EntityView” class defined under Blocks folder:

namespace Plugin.Demo.Promotion.Pipelines.Blocks.EntityViews
{
    [PipelineDisplayName(PromotionConstants.Pipelines.Blocks.GetPromotionAdditionalInformationViewBlock)]
    public class GetPromotionAdditionalInformationViewBlock : PipelineBlock
    {
        private readonly ViewCommander _viewCommander;

        public GetPromotionAdditionalInformationViewBlock(ViewCommander viewCommander)
        {
            this._viewCommander = viewCommander;
        }

        public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
        {
            Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");

            var request = this._viewCommander.CurrentEntityViewArgument(context.CommerceContext);

            KnownPromotionsViewsPolicy promotionsViewsPolicy = context.GetPolicy();

            
            // Only proceed if the current entity is a promotion item
            if (!(request.Entity is Sitecore.Commerce.Plugin.Promotions.Promotion))
            {
                return Task.FromResult(arg);
            }

            var promotionItem = (Sitecore.Commerce.Plugin.Promotions.Promotion)request.Entity;

            var component = promotionItem.GetComponent();
            var promotionAdditionalInformationViewsPolicy = context.GetPolicy();
            var targetView = arg;
            // Check if the add action was requested
            var isAddView = !string.IsNullOrEmpty(arg.Action) && arg.Action.Equals("AddAdditionalInformation", StringComparison.OrdinalIgnoreCase);

            // Create a new view and add it to the current entity view.
            if (!isAddView && arg?.ChildViews.Where(cv => cv.Name.Equals(additionalInformationViewsPolicy.Name)).Any() == false)
            {
                var view = new EntityView
                {
                    Name = additionalInformationViewsPolicy.Name,
                    DisplayName = additionalInformationViewsPolicy.DisplayName,
                    EntityId = arg.EntityId,
                    ItemId = arg.ItemId,
                    EntityVersion = arg.EntityVersion
                };
                if (component.Disclaimer == string.Empty)
                {
                    view.UiHint = "Table";                  
                }
                arg.ChildViews.Add(view);
                targetView = view;
                AddPropertiesToView(targetView, component, true);
            }
            if(isAddView && promotionItem != null)
            {
                AddPropertiesToView(targetView, component, true);
            }
            return Task.FromResult(arg);
        
            
        }

        private void AddPropertiesToView(EntityView entityView, PromotionAdditionalInformationComponent component, bool isReadOnly)
        {
            if (!entityView.ContainsProperty(nameof(PromotionAdditionalInformationComponent.Disclaimer))
                && !entityView.ContainsProperty(nameof(PromotionAdditionalInformationComponent.Combinable)))
            {
                entityView.Properties.Add(
                new ViewProperty
                {
                    Name = nameof(PromotionAdditionalInformationComponent.Disclaimer),
                    RawValue = component.Disclaimer,
                    IsReadOnly = false,
                    IsRequired = true,
                    OriginalType = "string"
                });

                ViewProperty promotionTypes =
                new ViewProperty
                {
                    Name = nameof(PromotionAdditionalInformationComponent.PromotionType),
                    RawValue = component.PromotionType.ToString(),
                    IsReadOnly = false,
                    IsRequired = true,
                    UiType = "Dropdown",
                    DisplayName = component.GetDisplayName(nameof(PromotionAdditionalInformationComponent.PromotionType))
                };

                var promotionTypeList = new List();
                promotionTypeList.Add(new Selection() { Name = "None", IsDefault = true, DisplayName = "None" });
                promotionTypeList.Add(new Selection() { Name = "Default", IsDefault = false, DisplayName = "Default" });
                promotionTypeList.Add(new Selection() { Name = "Shipping", IsDefault = false, DisplayName = "Shipping" });
                promotionTypeList.Add(new Selection() { Name = "Autoshipment", IsDefault = false, DisplayName = "Autoshipment" });

                promotionTypes.Policies = new List()
                {
                   new AvailableSelectionsPolicy()
                  {
                    List =  promotionTypeList
                  }
                };

                entityView.Properties.Add(promotionTypes);

                entityView.Properties.Add(
                new ViewProperty
                {
                    Name = nameof(PromotionAdditionalInformationComponent.Combinable),
                    RawValue = component.Combinable,
                    IsReadOnly = false,
                    IsRequired = false,
                });
            }
        }
    }
}

Define an action policy to populate the new view and its settings:

namespace Plugin.Demo.Promotion.Pipelines.Blocks.EntityViews
{
    [PipelineDisplayName(PromotionConstants.Pipelines.Blocks.PopulatePromotionAdditionalInformationActionsBlock)]
    public class PopulatePromotionAdditionalInformationActionsBlock: PipelineBlock
    {
        public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
            {   
            Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
            
            var viewsPolicy = context.GetPolicy();
            if (string.IsNullOrEmpty(arg?.Name) ||
               !arg.Name.Equals(viewsPolicy.Name, StringComparison.OrdinalIgnoreCase))
            {
                return Task.FromResult(arg);
            }
            var actionPolicy = arg.GetPolicy();

            actionPolicy.Actions.Add(
                new EntityActionView
                {
                Name = context.GetPolicy().Name,
                DisplayName = context.GetPolicy().DisplayName,
                Description = context.GetPolicy().Description,
                IsEnabled = true,
                EntityView = arg.Name,
                Icon = context.GetPolicy().Icon
            });
            return Task.FromResult(arg);
        }
    }
}

Execute “DoAction” on click of the tick button which performs a transaction and validates and stores the information in the database:

namespace Plugin.Demo.Promotion.Pipelines.Blocks.DoActions
{
    public class DoActionAddPromotionAdditionalInformationBlock :
    PipelineBlock
    {
        private readonly Commands.AddPromotionAdditionalInformationCommand _addPromotionAdditionalInformationCommand;

        public DoActionAddPromotionAdditionalInformationBlock(AddPromotionAdditionalInformationCommand addPromotionAdditionalInformationCommand)
          : base()
        {
            this._addPromotionAdditionalInformationCommand = addPromotionAdditionalInformationCommand;
        }
        public override async Task Run(EntityView arg, CommercePipelineExecutionContext context)
        { 
            var prmotionActionsPolicy = context.GetPolicy();
            // Only proceed if the right action was invoked
            if (string.IsNullOrEmpty(arg.Action) || !arg.Action.Equals("AddAdditionalInformation", StringComparison.OrdinalIgnoreCase))
            {
                return arg;
            }
            // Get the promotion item from the context
            Sitecore.Commerce.Plugin.Promotions.Promotion promotion1 = context.CommerceContext.GetObject(x => x.Id.Equals(arg.EntityId) && x.EntityVersion == arg.EntityVersion);
            if (promotion1 == null)
            {
                return arg;
            }
            component.Disclaimer =
                arg.Properties.FirstOrDefault(x =>
                    x.Name.Equals(nameof(PromotionAdditionalInformationComponent.Disclaimer), StringComparison.OrdinalIgnoreCase))?.Value;

            PromotionType tempPromotionType = PromotionType.None;
            viewProperty =
                arg.Properties.FirstOrDefault(x =>
                    x.Name.Equals(nameof(PromotionAdditionalInformationComponent.PromotionType), StringComparison.OrdinalIgnoreCase));
            if (!string.IsNullOrEmpty(viewProperty?.Value) && Enum.TryParse(viewProperty?.Value, out tempPromotionType))
                component.PromotionType = tempPromotionType;

            Sitecore.Commerce.Plugin.Promotions.Promotion promotion2 = await this._addPromotionAdditionalInformationCommand.Process(context.CommerceContext, promotion1,
                , component.Disclaimer, component.PromotionType, component.Combinable
                );

            return arg;
        }
    }
}

namespace Plugin.Demo.Promotion.Commands
{
    public class AddPromotionAdditionalInformationCommand: PromotionsCommerceCommand
    {
        private readonly IAddPromotionAdditionalInformationPipeline _addPromotionAdditionalInformationPipeline;

        public AddPromotionAdditionalInformationCommand(
          IAddPromotionAdditionalInformationPipeline addPromotionAdditionalInformationPipeline,
          IFindEntityPipeline findEntityPipeline,
          IServiceProvider serviceProvider)
          : base(findEntityPipeline, serviceProvider)
        {
            this._addPromotionAdditionalInformationPipeline = addPromotionAdditionalInformationPipeline;
        }

        public virtual async Task Process(
        CommerceContext commerceContext,
        Sitecore.Commerce.Plugin.Promotions.Promotion promotion,        
        string disclaimer="",
        PromotionType promotionType = PromotionType.None,
        bool combinable = false)
        {
            AddPromotionAdditionalInformationCommand addPromotionAdditionalInformationCommand = this;
            Sitecore.Commerce.Plugin.Promotions.Promotion result = (Sitecore.Commerce.Plugin.Promotions.Promotion)null;
            Sitecore.Commerce.Plugin.Promotions.Promotion promotion1;
            using (CommandActivity.Start(commerceContext, (CommerceCommand)addPromotionAdditionalInformationCommand))
            {       
                CommercePipelineExecutionContextOptions contextOptions = commerceContext.GetPipelineContextOptions();
                using (CommandActivity.Start(commerceContext, (CommerceCommand)addPromotionAdditionalInformationCommand))
                {
                    await addPromotionAdditionalInformationCommand.PerformTransaction(commerceContext, (Func)(async () =>
                    {
                        CommercePipelineExecutionContextOptions pipelineContextOptions = commerceContext.GetPipelineContextOptions();
                        result = await this._addPromotionAdditionalInformationPipeline.Run(new PromotionAdditionalInformationArgument(promotion, disclaimer, promotionType, combinable), (IPipelineExecutionContextOptions)pipelineContextOptions);
                    }));
                    promotion1 = result;
                }
                return promotion1;
            }
        }
    }
}


namespace Plugin.Demo.Promotion.Pipelines.Blocks
{
    public class AddAdditionalPromotionInformationBlock: PipelineBlock
    {
        public override async Task Run(
        PromotionAdditionalInformationArgument arg,
        CommercePipelineExecutionContext context)
        {
            AddAdditionalPromotionInformationBlock additionalPromotionInformationBlock = this;
            // ISSUE: explicit non-virtual call
            Condition.Requires(arg).IsNotNull((additionalPromotionInformationBlock.Name) + ": The block argument cannot be null.");
            // ISSUE: explicit non-virtual call
            Condition.Requires(arg.Promotion).IsNotNull((additionalPromotionInformationBlock.Name) + ": The promotion cannot be null.");
            // ISSUE: explicit non-virtual call
            Condition.Requires(arg.Disclaimer).IsNotNullOrEmpty((additionalPromotionInformationBlock.Name) + ": The disclaimer cannot be null or empty.");
            Sitecore.Commerce.Plugin.Promotions.Promotion promotion = arg.Promotion;
            CommercePipelineExecutionContext executionContext;
            if (!promotion.IsDraft(context.CommerceContext))
            {
                executionContext = context;
                CommerceContext commerceContext = context.CommerceContext;
                string error = context.GetPolicy().Error;
                object[] args = new object[1]
                {
                    (object) promotion.FriendlyId
                };
                string defaultMessage = "'" + promotion.FriendlyId + "' cannot be edited or deleted because is approved.";
                executionContext.Abort(await commerceContext.AddMessage(error, "EntityIsApproved", args, defaultMessage), (object)context);
                executionContext = (CommercePipelineExecutionContext)null;
                return (Sitecore.Commerce.Plugin.Promotions.Promotion)null;
            }
            PromotionAdditionalInformationComponent promotionAdditionalInformationComponent = promotion.GetComponent();
            return promotion;
        }
    }
}