Customizing evaluation of Buy X Get Y discounts

If you want to modify the way Buy X Get Y discounts evaluate whether the content of the shopping cart is eligible for a Buy X Get Y discount, you need to override the GetMultiBuyDiscountsEvaluatorInternal method in theMultiBuyDiscountInfoProviderclass of the CMS.Ecommerce namespace.




/// <summary>
/// Returns an instance of the MultiBuyDiscountsEvaluator class that determines whether the shopping cart
/// is eligible for Buy X Get Y discounts on the site of the shopping cart.
/// </summary>
/// <param name="cart">The shopping cart which is to be evaluated.</param>
protected virtual MultiBuyDiscountsEvaluator GetMultiBuyDiscountsEvaluatorInternal(ShoppingCartInfo cart)
{
    return new MultiBuyDiscountsEvaluator(cart.CartProducts);
}


Since the GetMultiBuyDiscountsEvaluatorInternal method returns a MultiBuyDiscountsEvaluator object, you need to create your own class that inherits from the MultiBuyDiscountsEvaluator class.

How it works by default

When the system needs to evaluate whether the specific shopping cart contains products eligible for a Buy X Get Y discount, the system initializes the MultiBuyDiscountsEvaluator class and assigns a MultiBuyDiscountsApplicator object to the Applicator property.

  • The EvaluateDiscounts method loops through all applicable Buy X Get Y discounts on the current site (i.e., discounts that are enabled on the current site in the time of evaluation for the given user role and whether the coupon was entered) and tries to apply the discount on the content of the shopping cart. Through the loop, if the system finds out that a discount forbids lower priority discounts to be applied, the system stops the loop.
    • While the method is trying to apply the discount on shopping cart items, the PrepareItemsForDiscount sorts the SortedItems list (which is sorted according to the price of the items) to the PrioritizedItems list according to the priority set by the discount (the discounted items from the “get” conditions are first in the list). The prioritization works as set by the PrioritizeItems method which is located in the class of the Buy X Get Y discount, originally in the IMultiBuyDiscount interface.
    • The process of trying to apply the discount occurs in the TryApplyDiscount method. The method loops through all shopping cart items where it evaluates whether it is usable for the currently looped discount. The method also creates a list that records units of items that are used either for the “buy” conditions of the discount or the “get” conditions (either items required for the discount to be applied or items that are discounted).
      • The process of evaluating of the discount applicability occurs in the FindDiscountApplication method. It finds out whether the shopping cart contains any item that is in the “buy” or “get” conditions of the Buy X Get Y discount. It also checks whether a product should be autoadded to the shopping cart (the AcceptMissedDiscount method of the applicator).
        • The process of finding out whether the shopping cart contains any item in the “buy” conditions occurs in the FindItemsToBaseDiscountOn method. The method finds out the items with the lowest priority that fulfill the “buy” conditions and records how many units of the item was used for the discount.
          • The process of checking whether the item is required for the discount occurs in the CanBaseDiscountOn method. The method checks whether there is any item not used for the “buy” or “get” conditions. The method also checks whether the discount requires the item for being applied (the IsBasedOn method of the discount object).
          • The process of creating a record of how many units of the specific item was used occurs in the AddItemsCount method.
        • The process of finding out whether the shopping cart contains any item in the “get” conditions occurs in the FindItemsToBeDiscounted method. The method finds out the items with the highest priority that fulfill the “get” conditions and record how man units of the item was used for the discount.
          • The process of checking whether the item is to be discounted occurs in the CanBeDiscounted method. The method checks whether there is any item not used for the “buy” or “get” conditions. The method also checks whether the discount permits to apply the discount to the item (the IsApplicableOn method of the discount object).
          • The process of creating a record of how man units of the specific item was discounted occurs in the AddItemsCount method.
      • The process of applying the discount occurs in the ApplyDiscount method. The method loops through all discounted items and calls the applicator of the discount to apply the discount.
      • The process of creating a record of which shopping cart was used and how many units were used occurs in the RememberUsedItems method. The method loops through all the items used for the discount (both for the “buy” conditions and the “get” conditions) and records it.

Source code

See the whole source code of the class




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CMS.Ecommerce
{
    /// <summary>
    /// The class handling evaluating Buy X Get Y discounts on a set of shopping cart items.
    /// </summary>
    public class MultiBuyDiscountsEvaluator
    {
        #region "Variables"
        private List<ShoppingCartItemInfo> mSortedItems;
        private readonly List<ShoppingCartItemInfo> mOriginalItems;
        private readonly Dictionary<ShoppingCartItemInfo, int> mUsedDiscountItems = new Dictionary<ShoppingCartItemInfo, int>();
        #endregion

        #region "Properties"
        /// <summary>
        /// List of the shopping cart items sorted by priority given by the discount.
        /// The discounted items from the "get" conditions are at the beginning of the list.
        /// </summary>
        protected List<ShoppingCartItemInfo> PrioritizedItems
        {
            get;
            set;
        }

        /// <summary>
        /// List of the shopping cart items sorted by their price.
        /// </summary>
        protected IEnumerable<ShoppingCartItemInfo> SortedItems
        {
            get
            {
                // Check whether the sorted list already exists.
                if (mSortedItems == null)
                {
                    // If the list does not exist, create a new list and sort it from the cheapest to the most expensive.
                    mSortedItems = new List<ShoppingCartItemInfo>(mOriginalItems);
                    mSortedItems.Sort(CompareByUnitPriceWithOptions);
                }

                // Return the sorted list.
                return mSortedItems;
            }
        }

        /// <summary>
        /// Dictionary containing the number of units of shopping cart items used for the discount (for both "buy" and "get" conditions).
        /// </summary>
        private Dictionary<ShoppingCartItemInfo, int> UsedDiscountItems
        {
            get
            {
                return mUsedDiscountItems;
            }
        }

        /// <summary>
        /// Buy X Get Y discounts applicator which is used for the actual application of the discount.
        /// </summary>
        public IMultiBuyDiscountsApplicator Applicator
        {
            get;
            set;
        }
        #endregion

        #region "Constructor"
        /// <summary>
        /// Creates a new instance of the evaluator for the given shopping cart items.
        /// </summary>
        /// <param name="cartItems">Shopping cart items to be evaluated.</param>
        public MultiBuyDiscountsEvaluator(IEnumerable<ShoppingCartItemInfo> cartItems)
        {
            mOriginalItems = cartItems.ToList();
        }
        #endregion

        #region "Discount evaluation"
        /// <summary>
        /// Resets the evaluator to its initial state.
        /// </summary>
        protected virtual void Reset()
        {
            // Reset the applicator.
            if (Applicator != null)
            {
                Applicator.Reset();
            }

            // Resets the number of units of the items used for the discount.
            mOriginalItems.ForEach(item => UsedDiscountItems.Add(item, 0));
        }

        /// <summary>
        /// Evaluates the given Buy X Get Y discounts and applies the matching discounts to the corresponding shopping cart items.
        /// </summary>
        /// <param name="discounts">Buy X Get Y discounts to be evaluated.</param>
        public void EvaluateDiscounts(IEnumerable<IMultiBuyDiscount> discounts)
        {
            // Reset the evaluator to its initial state.
            Reset();

            // Loop through all Buy X Get Y discounts to check the eligibility.
            foreach (var discount in discounts)
            {
                // Initialize the discount for application.
                discount.Init();

                // Prepare the shopping cart item list for discount application.
                PrepareItemsForDiscount(discount);

                // Try to apply the discount on the shopping cart items.
                var applied = TryApplyDiscount(discount);

                // Stop processing when the discount already applied and does not allow processing further Buy X Get Y discounts.
                if (applied && !discount.ApplyFurtherDiscounts)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Ensures that the shopping cart items from the sorted list are prioritized according to the given Buy X Get Y discount.
        /// </summary>
        /// <param name="discount">Discount which prioritizes the sorted shopping cart items.</param>
        protected virtual void PrepareItemsForDiscount(IMultiBuyDiscount discount)
        {
            // Create a new list from the list sorted by the price.
            PrioritizedItems = new List<ShoppingCartItemInfo>(SortedItems);

            // Prioritize the items according to the given discount.
            discount.PrioritizeItems(PrioritizedItems);
        }

        /// <summary>
        /// Evaluates all shopping cart items whether they are eligible for the given Buy X Get Y discount
        /// and tries to apply the discount on the valid items.
        /// </summary>
        /// <param name="discount">Discount that evaluates the shopping cart items.</param>
        /// <returns>Returns true it the discount was applied at least once.</returns>
        private bool TryApplyDiscount(IMultiBuyDiscount discount)
        {
            var applied = false;

            // Create a new clear dictionary for items that are in the "get" conditions (discounted items).
            var itemsToBeDiscounted = new Dictionary<ShoppingCartItemInfo, int>();

            // Create a new clear dictionary for items that are in the "buy" conditions (items required by the discount). 
            var itemsToBaseDiscountOn = new Dictionary<ShoppingCartItemInfo, int>();

            // Loop while the discount is allowed to be applied more times.
            do
            {
                // Clear the dictionaries.
                itemsToBeDiscounted.Clear();
                itemsToBaseDiscountOn.Clear();

                // Find discount application possibility.
                var isApplicable = FindDiscountApplication(discount, itemsToBaseDiscountOn, itemsToBeDiscounted);

                // Stop the evaluation if the discount is not applicable.
                if (!isApplicable)
                {
                    break;
                }

                // Mark the discounted items.
                ApplyDiscount(discount, itemsToBeDiscounted);

                // Remember the used items.
                RememberUsedItems(itemsToBaseDiscountOn, itemsToBeDiscounted);

                // Set a flag indicating application of the given discount.
                applied = true;
            } while (discount.AllowsMoreUses());

            // Return true if the discount was applied at least once.
            return applied;
        }

        #region "Searching for the items needed for the discount application"
        /// <summary>
        /// Finds a possibility to apply the given discount by checking whether there are the required items in the shopping cart.
        /// </summary>
        /// <param name="discount">Discount which is tried to be applied.</param>
        /// <param name="itemsToBaseDiscountOn">Empty dictionary where the key is the shopping cart item and the value is
        /// the number of units calculated for the discount. Found required items for the given discount are added here.</param>
        /// <param name="itemsToBeDiscounted">Empty dictionary where the key is the shopping cart item and the value is
        /// the number of units to be discounted. Found items discounted by the given discount are added here.</param>
        /// <returns>Returns true if the given discount is applicable.</returns>
        private bool FindDiscountApplication(IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn, IDictionary<ShoppingCartItemInfo, int> itemsToBeDiscounted)
        {
            // Check whether the shopping cart contains items required by the given Buy X Get Y discount.
            if (!FindItemsToBaseDiscountOn(discount, itemsToBaseDiscountOn))
            {
                return false;
            }

            // Check whether the shopping cart contains at least one item that can be discounted.
            if (FindItemsToBeDiscounted(discount, itemsToBaseDiscountOn, itemsToBeDiscounted))
            {
                return true;
            }

            // Check by the applicator if a product should be autoadded.
            if (Applicator != null)
            {
                return Applicator.AcceptsMissedDiscount(discount);
            }

            return false;
        }

        /// <summary>
        /// Finds items required by "buy" conditions of the given Buy X Get Y discount.
        /// </summary>
        /// <param name="discount">Discount with the specified required items.</param>
        /// <param name="itemsToBaseDiscountOn">Empty dictionary. Found items are placed here with their counts.</param>
        /// <returns>Returns true if the found items fully satisfy discount requirement "buy" conditions.</returns>
        protected virtual bool FindItemsToBaseDiscountOn(IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn)
        {
            // Get the number of products needed to enable the given discount.
            var minDiscountingCount = discount.BasedOnUnitsCount;

            // Check whether enough items are found to apply the discount.
            for (var i = 0; i < minDiscountingCount; i++)
            {
                // Find the item with the lowest priority which satisfies the "buy" conditions.
                var discounting = PrioritizedItems.LastOrDefault(item => CanBaseDiscountOn(item, discount, itemsToBaseDiscountOn));

                // Check whether there is no more items left to fulfill the "buy" conditions.
                if (discounting == null)
                {
                    return false;
                }

                // Add 1 to the count of the specific discounting item.
                AddItemsCount(itemsToBaseDiscountOn, discounting, 1);
            }

            return true;
        }

        /// <summary>
        /// Finds items to be discounted with the given discount based on the given items.
        /// </summary>
        /// <param name="discount">Discount with the specific "get" conditions.</param>
        /// <param name="itemsToBaseDiscountOn">Shopping cart items used for requirement "buy" conditions.</param>
        /// <param name="itemsToBeDiscounted">Shopping cart items found to be discounted by this discount.</param>
        /// <returns>Returns true when at least one discounted item was found.</returns>
        protected virtual bool FindItemsToBeDiscounted(IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn, IDictionary<ShoppingCartItemInfo, int> itemsToBeDiscounted)
        {
            // Get the number of discounted units.
            var maxDiscountedCount = discount.ApplyOnUnitsCount;

            // Check which items are to be discounted.
            for (var i = 0; i < maxDiscountedCount; i++)
            {
                // Find the item with the highest priority which can be discounted.
                var discounted = PrioritizedItems.FirstOrDefault(item => CanBeDiscounted(item, discount, itemsToBaseDiscountOn, itemsToBeDiscounted));

                // Check whether no more items can be discounted.
                if (discounted == null)
                {
                    return i > 0;
                }

                // Add 1 to the count of the specific discounted item.
                AddItemsCount(itemsToBeDiscounted, discounted, 1);
            }

            return true;
        }

        /// <summary>
        /// Checks if the given item can be discounted using the given discount.
        /// </summary>
        /// <param name="item">Discounted item to be checked.</param>
        /// <param name="discount">Discount, which discounts the item, to be checked.</param>
        /// <param name="basedOnItems">Shopping cart items with the count of the items found as
        /// the satisfying items for the given discount.</param>
        /// <param name="alreadyDiscountedItems">Shopping cart items with the count of items already
        /// found as suitable for applying the discount for the given discount.</param>
        /// <returns>Returns true if the given discount is applicable on the given items.</returns>
        protected virtual bool CanBeDiscounted(ShoppingCartItemInfo item, IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> basedOnItems, IDictionary<ShoppingCartItemInfo, int> alreadyDiscountedItems)
        {
            // Get the count of the items used for "buy" conditions of the Buy X Get Y discount.
            var alreadyUsedUnits = GetItemsCount(basedOnItems, item);

            // Get the count of the already discounted items.
            var alreadyDiscountedUnits = GetItemsCount(alreadyDiscountedItems, item);

            // Check whether the shopping cart contains more unused and not free units of items then the units of items
            // used for "buy" conditions and the already discounted products. (Only paid items having some unit
            // not used for "buy" conditions can be used.)
            var availableUnits = GetUnusedNonFreeUnits(item) - item.CartItemAutoAddedUnits;
            if (!discount.AutoAddEnabled)
            {
                availableUnits -= item.CartItemAutoAddedUnits;
            }
            var hasEnoughUnits = (availableUnits - alreadyUsedUnits - alreadyDiscountedUnits > 0);

            // Return true if the shopping cart contains enough units and the discount is applicable, i.e.,
            // affects the price of the given shopping cart item.
            return hasEnoughUnits && discount.IsApplicableOn(item);
        }

        /// <summary>
        /// Checks if the given item can be used to fulfill discount "buy" conditions.
        /// </summary>
        /// <param name="item">Item to be checked.</param>
        /// <param name="discount">Discount to be checked.</param>
        /// <param name="alreadyBasedOnItems">Items with the count of the item units already found as fulfilling
        /// the requirements of the "buy" conditions of the given discount.</param>
        /// <returns>Returns true if the shopping cart contains enough units for the discount.</returns>
        protected virtual bool CanBaseDiscountOn(ShoppingCartItemInfo item, IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> alreadyBasedOnItems)
        {
            // Get the count of the items used for the "get" conditions of the discount.
            var alreadyUsedUnits = GetItemsCount(alreadyBasedOnItems, item);

            // Check whether the shopping cart contains more unused and not free units of items then
            // units of items used for "get" conditions. (Only paid items having some unit not used
            // for "get" conditions can be used.)
            var hasEnoughUnits = (GetUnusedNonFreeUnits(item) - alreadyUsedUnits > 0);

            // Return true if the shopping cart contains enough units and the discount.
            return hasEnoughUnits && discount.IsBasedOn(item);
        }
        #endregion
        #endregion

        #region "Discount application"
        /// <summary>
        /// Applies the Buy X Get Y discount on the given number of units of the given shopping cart item.
        /// </summary>
        /// <param name="discount">Discount which is applied.</param>
        /// <param name="itemsToBeDiscounted">Dictionary containing the items as the keys
        /// and the count of the discounted units as the values.</param>
        private void ApplyDiscount(IMultiBuyDiscount discount, IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> itemsToBeDiscounted)
        {
            // Loop through all discounted items.
            foreach (var itemCount in itemsToBeDiscounted)
            {
                var item = itemCount.Key;
                var count = itemCount.Value;

                // Apply the discount on the specific item.
                ApplyDiscount(discount, item, count);

                // Set that the discount was applied.
                discount.AcceptApplication(item, count);
            }
        }

        /// <summary>
        /// Applies the Buy X Get Y discount to the given number of units of the given shopping cart item.
        /// </summary>
        /// <param name="discount">Discount which is applied.</param>
        /// <param name="itemToBeDiscounted">Shopping cart item to apply the discount on.</param>
        /// <param name="units">Number of units to be discounted.</param>
        protected virtual void ApplyDiscount(IMultiBuyDiscount discount, ShoppingCartItemInfo itemToBeDiscounted, int units)
        {
            if (Applicator != null)
            {
                // Apply the discount on the specific item with the applicator.
                Applicator.ApplyDiscount(discount, itemToBeDiscounted, units);
            }
        }
        #endregion

        #region "Management of items already used for a discount."
        /// <summary>
        /// Makes a record that the given items in the given number of units were used for the given discount.
        /// </summary>
        /// <param name="discountingItemsCounts">Shopping cart items used for the requirement "buy" conditions.
        /// The value is the number of units used for discount.</param>
        /// <param name="discountedItemsCounts">Shopping cart items used for the "get" conditions (the discounted items).
        /// The value is the number of units used for discount.</param>
        protected virtual void RememberUsedItems(IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountingItemsCounts, IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountedItemsCounts)
        {
            // Loop through all the shopping cart items used for the requirement "buy" conditions.
            foreach (var item in discountingItemsCounts)
            {
                // Add the used units of the item to the count of all used item units.
                UsedDiscountItems[item.Key] += item.Value;
            }

            // Loop through all the shopping cart items used for the "get" conditions (the discounted items).
            foreach (var item in discountedItemsCounts)
            {
                // Add the used units of the item to the count of all used item units.
                UsedDiscountItems[item.Key] += item.Value;
            }
        }

        /// <summary>
        /// Returns the number of units not used for any discount (neither as the requirement "buy" conditions
        /// items nor as the "get" conditions items - the discounted items).
        /// </summary>
        /// <param name="item">Item to get number of units for.</param>
        /// <returns>Returns the number of units not used for any discount.</returns>
        protected virtual int GetUnusedNonFreeUnits(ShoppingCartItemInfo item)
        {
            return item.CartItemUnits - UsedDiscountItems[item];
        }
        #endregion

        #region "Helper methods"
        /// <summary>
        /// Gets the unit count of the given shopping cart item.
        /// </summary>
        /// <param name="items">Dictionary of shopping cart items. The key is the shopping cart item object,
        /// the value is number of units of the item.</param>
        /// <param name="item">Shopping cart item with the desired number of units.</param>
        /// <returns>The unit count of the given shopping cart item (if exists; otherwise, it returns 0).</returns>
        private int GetItemsCount(IDictionary<ShoppingCartItemInfo, int> items, ShoppingCartItemInfo item)
        {
            var units = 0;

            if (items.ContainsKey(item))
            {
                units = items[item];
            }

            return units;
        }

        /// <summary>
        /// Adds the number of units to the specified shopping cart item in the list of shopping cart items.
        /// </summary>
        /// <param name="items">Dictionary of shopping cart items. The key is the shopping cart item object,
        /// the value is number of units of the item.</param>
        /// <param name="item">Shopping cart item to which the units are to be added.</param>
        /// <param name="units">Number of units to be added to the specified shopping cart item.</param>
        private void AddItemsCount(IDictionary<ShoppingCartItemInfo, int> items, ShoppingCartItemInfo item, int units)
        {
            if (!items.ContainsKey(item))

            {
                items.Add(item, 0);
            }

            items[item] += units;
        }

        /// <summary>
        /// Computes the difference between two prices. By default, it uses the unit price.
        /// If the unit price is not available, the method uses the shopping cart item price.
        /// </summary>
        /// <param name="item1">Dictionary of shopping cart items. The key is the shopping cart item object,
        /// the value is number of units of the item.</param>
        /// <param name="item2">Shopping cart item with the desired number of units.</param>
        /// <returns>The unit count of the given shopping cart item (if exists; otherwise, it returns 0).</returns>
        private int CompareByUnitPriceWithOptions(ShoppingCartItemInfo item1, ShoppingCartItemInfo item2)
        {
            // For both prices, if the price is not unexpected value, use the unit price.
            // Otherwise, use shopping cart item price.
            var price1 = double.IsNaN(item1.CartItemPrice) ? item1.UnitPrice : item1.CartItemPrice;
            var price2 = double.IsNaN(item2.CartItemPrice) ? item2.UnitPrice : item2.CartItemPrice;

            // Return a number indicating the relative difference between the two prices.
            return price1.CompareTo(price2);
        }
        #endregion
    }
}


Creating a custom evaluating process of Buy X Get Y discounts

To customize the Buy X Get Y discount evaluation:

  1. Override theCMS.Ecommerce.MultiBuyDiscountInfoProvider.GetMultiBuyDiscountsEvaluatorInternalmethod.
  2. Implement a class which inherits from theMultiBuyDiscountsEvaluatorclass.

Customizing the MultiBuyDiscountInfoProvider class

Override the GetMultiBuyDiscountsEvaluatorInternal virtual method to be able to integrate your custom MultiBuyDiscountsEvaluator class.

  1. Create a new class file in your project in Visual Studio, for example CustomMultiBuyInfoProvider.cs that inherits from the MultiBuyDiscountInfoProvider class.

  2. Add the CMS and CMS.Ecommerce namespaces to the using block.

  3. Register the new InfoProvider class:

    
    
    
     [assembly: RegisterCustomProvider(typeof(CustomMultiBuyInfoProvider))]
    
    
     
  4. Override the GetMultiBuyDiscountsEvaluatorInternal method with your custom (not yet existing) MultiBuyDiscountsEvaluator class:

    
    
    
     public class CustomMultiBuyInfoProvider : MultiBuyDiscountInfoProvider
     {
         protected override MultiBuyDiscountsEvaluator GetMultiBuyDiscountsEvaluatorInternal(ShoppingCartInfo cart)
         {
             return new CustomMultiBuyEvaluator(cart.CartProducts);
         }
     }
    
    
     

    Creating the CustomMultiBuyInfoProvider class

The system now returns yet non-existing CustomMultiBuyEvaluator when evaluating Buy X Get Y discounts. Continue with the next steps to achieve the desired functionality.

Customizing the MultiBuyDiscountsEvaluator class

Implement a class inheriting from the MultiBuyDiscountsEvaluator class to provide your custom logic of evaluating Buy X Get Y discounts.

  1. Create a new class file in your project in Visual Studio, for example CustomMultiBuyEvaluator.cs that inherits from the MultiBuyDiscountsEvaluator class.

  2. Add the CMS and CMS.Ecommerce namespaces to the using block.

  3. Assign the inheritance from the MultiBuyDiscountsEvaluator class to the CustomMultiBuyEvaluator class:

    
    
    
     public class CustomMultiBuyEvaluator : MultiBuyDiscountsEvaluator
     {
     }
    
    
     
  4. Create a suitable constructor. The constructor of the MultiBuyDiscountsEvaluator class needs the IEnumerable<ShoppingCartItemInfo object. For example:

    
    
    
     public CustomMultiBuyEvaluator(IEnumerable<ShoppingCartItemInfo> cartItems)
         : base(cartItems)
     { }
    
    
     

  5. Override the methods you want to change. The MultiBuyDiscountsEvaluator class contains the following virtual methods:

    • protected virtual void Reset() – Resets the evaluator to its initial state.
    • protected virtual void PrepareItemsForDiscount(IMultiBuyDiscount discount) – Ensures that the shopping cart items from the SortedItems sorted list are prioritized according to the given Buy X Get Y discount.
      • IMultiBuyDiscount discount – Discount which prioritizes the sorted shopping cart items.
    • protected virtual bool FindItemsToBaseDiscountOn(IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn) – Finds items required by “buy” conditions of the given Buy X Get Y discount. Returns true if the found items fully satisfy discount requirement “buy” conditions.
      • IMultiBuyDiscount discount – Discount with the specified required items.
      • IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn – Empty dictionary. Found items are placed here with their counts.
    • protected virtual bool FindItemsToBeDiscounted(IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn, IDictionary<ShoppingCartItemInfo, int> itemsToBeDiscounted) – Finds items to be discounted with the given discount based on the given items. Returns true when at least one discounted item was found.
      • IMultiBuyDiscount discount – Discount with the specific “get” conditions.
      • IDictionary<ShoppingCartItemInfo, int> itemsToBaseDiscountOn – Shopping cart items used for requirement “buy” conditions.
      • IDictionary<ShoppingCartItemInfo, int> itemsToBeDiscounted – Shopping cart items found to be discounted by this discount.
    • protected virtual bool CanBeDiscounted(ShoppingCartItemInfo item, IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> basedOnItems, IDictionary<ShoppingCartItemInfo, int> alreadyDiscountedItems) – Checks if the given item can be discounted using the given discount. Returns true if the given discount is applicable on the given items.
      • ShoppingCartItemInfo item – Discounted item to be checked.
      • IMultiBuyDiscount discount – Discount, which discounts the item, to be checked.
      • IDictionary<ShoppingCartItemInfo, int> basedOnItems – Shopping cart items with the count of the items found as the satisfying items for the given discount.
      • IDictionary<ShoppingCartItemInfo, int> alreadyDiscountedItems – Shopping cart items with the count of items already found as suitable for applying the discount for the given discount.
    • protected virtual bool CanBaseDiscountOn(ShoppingCartItemInfo item, IMultiBuyDiscount discount, IDictionary<ShoppingCartItemInfo, int> alreadyBasedOnItems) – Checks if the given item can be used to fulfill discount “buy” conditions. Returns true if the shopping cart contains enough units for the discount.
      • ShoppingCartItemInfo item – Item to be checked.
      • IMultiBuyDiscount discount – Discount to be checked.
      • IDictionary<ShoppingCartItemInfo, int> alreadyBasedOnItems – Items with the count of the item units already found as fulfilling the requirements of the “buy” conditions of the given discount.
    • protected virtual void ApplyDiscount(IMultiBuyDiscount discount, ShoppingCartItemInfo itemToBeDiscounted, int units) – Applies the Buy X Get Y discount to the given number of units of the given shopping cart item.
      • IMultiBuyDiscount discount – Discount which is applied.
      • ShoppingCartItemInfo itemToBeDiscounted – Shopping cart item to apply the discount on.
      • int units – Number of units to be discounted.
    • protected virtual void RememberUsedItems(IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountingItemsCounts, IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountedItemsCounts) – Makes a record that the given items in the given number of units were used for the given discount.
      • IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountingItemsCounts – Shopping cart items used for the requirement “buy” conditions. The value is the number of units used for discount.
      • IEnumerable<KeyValuePair<ShoppingCartItemInfo, int>> discountedItemsCounts – Shopping cart items used for the “get” conditions (the discounted items). The value is the number of units used for discount.
    • protected virtual int GetUnusedNonFreeUnits(ShoppingCartItemInfo item) – Returns the number of units not used for any discount (neither as the requirement “buy” conditions items nor as the “get” conditions items – the discounted items).
      • ShoppingCartItemInfo item – Item to get number of units for.

See the complete default source code above.

The Buy X Get Y discounts are now evaluated according to your changes.