How to Extend the Promotions View

  • Twitter
  • LinkedIn

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:

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:

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;
        }
    }
}

Related Blogs

Latest Blogs