internal static decimal GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(RetailDiscountLine discountLineDefinition, decimal price, out bool isFullyCovered)
            {
                isFullyCovered = true;
                decimal unitDiscountAmount = decimal.Zero;

                switch ((DiscountOfferMethod)discountLineDefinition.DiscountMethod)
                {
                case DiscountOfferMethod.DiscountAmount:
                    unitDiscountAmount = discountLineDefinition.DiscountAmount;
                    isFullyCovered     = unitDiscountAmount <= price;
                    break;

                case DiscountOfferMethod.DiscountPercent:
                    unitDiscountAmount = DiscountBase.GetDiscountAmountForPercentageOff(price, discountLineDefinition.DiscountPercent);
                    break;

                case DiscountOfferMethod.OfferPrice:
                    unitDiscountAmount = DiscountBase.GetDiscountAmountForDealUnitPrice(price, discountLineDefinition.OfferPrice);
                    isFullyCovered     = discountLineDefinition.OfferPrice <= price;
                    break;

                default:
                    break;
                }

                unitDiscountAmount = Math.Min(price, unitDiscountAmount);
                unitDiscountAmount = Math.Max(unitDiscountAmount, decimal.Zero);

                return(unitDiscountAmount);
            }
Exemple #2
0
            /// <summary>
            /// Prepares remaining quantities for thresholds for the first time.
            /// </summary>
            /// <param name="bestDiscountPath">Best discount path of application discount applications so far.</param>
            /// <param name="remainingQuantities">Remaining quantities for the next round.</param>
            /// <param name="remainingQuantitiesForCompound">Remaining quantities for compound for the next round.</param>
            /// <remarks>Threshold can be compounded on top on non-thresholds, so we have to reset remaining quantities before we evaluate threshold discounts.</remarks>
            internal void PrepareRemainingQuantitiesForThresholdsFirstTime(
                List <AppliedDiscountApplication> bestDiscountPath,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound)
            {
                // Step 1: fill in this.ItemGroupIndexToCompoundedOfferToQuantityLookup from the last round.
                //         reduce remaining quantitied for non-compounded discounts.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
                foreach (AppliedDiscountApplication appliedDiscountApplication in bestDiscountPath)
                {
                    DiscountBase discount = appliedDiscountApplication.DiscountApplication.Discount;
                    if (!discount.CanCompound)
                    {
                        ReduceQuantitiesFromNonCompounded(appliedDiscountApplication, remainingQuantities, remainingQuantitiesForCompound);
                    }
                }

                // Finish off items with threshold discounts.
                foreach (KeyValuePair <int, Dictionary <int, decimal> > pair in this.itemGroupIndexToPriorityToSettledCompoundedQuantityForThreshold)
                {
                    int itemGroupIndex = pair.Key;

                    // Compounded threshold always take the whole item.
                    remainingQuantities[itemGroupIndex]            = decimal.Zero;
                    remainingQuantitiesForCompound[itemGroupIndex] = decimal.Zero;
                }

                // Process non-threshold compounded quantities.
                this.ReduceQuantitiesFromCompoundedNonThresholdForThreshold(remainingQuantities);

                // Clear current lookup.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
            }
 private static void SetEffectiveDiscounDiscountPriorityFromPriceGroups(
     DiscountBase discount,
     PriceContext priceContext)
 {
     if (discount.PricingPriorityNumber <= 0)
     {
         foreach (long priceGroupRecordId in discount.PriceDiscountGroupIds)
         {
             string priceGroupId = null;
             if (priceContext.RecordIdsToPriceGroupIdsDictionary.TryGetValue(priceGroupRecordId, out priceGroupId))
             {
                 int priority = 0;
                 if (priceContext.PriceGroupIdToPriorityDictionary.TryGetValue(priceGroupId, out priority))
                 {
                     if (priority > discount.PricingPriorityNumber)
                     {
                         // We could have a new property on DiscountBase that indicates effective priority number.
                         // This is much simpler, for now, without complications.
                         discount.PricingPriorityNumber = priority;
                     }
                 }
             }
         }
     }
 }
        public override DiscountBase GetDiscountStrategy(Basket basket, DiscountBase parentDiscountStrategy = null)
        {
            DiscountBase bestStrategy = null;
            double       bestPrice    = 0;
            //Product Discount + MembershipDiscount
            DiscountBase productDiscount = new ProductDiscount(true);
            DiscountBase membershipAndProductDiscount = BasketDiscountOverMemberShipFactory.Instance.GetDiscountStrategy(basket, productDiscount);

            bestStrategy = membershipAndProductDiscount;
            bestPrice    = membershipAndProductDiscount.GetDiscountedTotal(basket);

            //Basket Discount + MembershipDiscount
            DiscountBase basketDiscount = BasketDiscountOverTotalFactory.Instance.GetDiscountStrategy(basket, null);
            DiscountBase memberShipAndBasketDiscount = BasketDiscountOverMemberShipFactory.Instance.GetDiscountStrategy(basket, basketDiscount);

            (bestPrice, bestStrategy) = GetBetterStrategy(bestPrice, bestStrategy, basket, memberShipAndBasketDiscount);
            //Product Discount + CategoryDiscount + MembershipDiscount
            DiscountBase categoryAndProductDiscount          = CategoryDiscountFactory.Instance.GetDiscountStrategy(basket, productDiscount);
            DiscountBase membershipCategoryAndProdutDiscount = BasketDiscountOverMemberShipFactory.Instance.GetDiscountStrategy(basket, categoryAndProductDiscount);

            (bestPrice, bestStrategy) = GetBetterStrategy(bestPrice, bestStrategy, basket, membershipCategoryAndProdutDiscount);
            //Special Offer Basket Discount (like BlackFriday)

            DiscountBase blackFridayDiscountStrategy = BlackFridayDiscountFactory.Instance.GetDiscountStrategy(basket, null);

            (bestPrice, bestStrategy) = GetBetterStrategy(bestPrice, bestStrategy, basket, blackFridayDiscountStrategy);
            return(bestStrategy);
        }
Exemple #5
0
            internal void FillLookup(
                DiscountBase discount,
                int itemGroupIndex,
                decimal quantity)
            {
                // Build the lookup from itemGroupId to OfferId to Quantity.
                Dictionary <string, decimal> offerIdToQuantityLookup = null;

                if (this.ItemGroupIndexToCompoundedOfferToQuantityLookup.TryGetValue(itemGroupIndex, out offerIdToQuantityLookup))
                {
                    decimal existingQuantity = decimal.Zero;
                    if (offerIdToQuantityLookup.TryGetValue(discount.OfferId, out existingQuantity))
                    {
                        offerIdToQuantityLookup[discount.OfferId] = quantity + existingQuantity;
                    }
                    else
                    {
                        offerIdToQuantityLookup.Add(discount.OfferId, quantity);
                    }
                }
                else
                {
                    offerIdToQuantityLookup = new Dictionary <string, decimal>();
                    offerIdToQuantityLookup.Add(discount.OfferId, quantity);
                    this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Add(itemGroupIndex, offerIdToQuantityLookup);
                }
            }
 /// <summary>
 /// Initializes a new instance of the <see cref="DiscountApplication" /> class.
 /// </summary>
 /// <param name="discount">The discount.</param>
 /// <param name="applyStandalone">Whether to apply it standalone.</param>
 /// <param name="removeItemsFromLookupsWhenApplied">Whether to remove items from lookups when applied.</param>
 public DiscountApplication(DiscountBase discount, bool applyStandalone, bool removeItemsFromLookupsWhenApplied)
 {
     this.Discount        = discount;
     this.ApplyStandalone = applyStandalone;
     this.RemoveItemsFromLookupsWhenApplied = removeItemsFromLookupsWhenApplied;
     this.NumberOfTimesApplicable           = discount != null ? discount.NumberOfTimesApplicable : 0;
     this.ItemQuantities = new Dictionary <int, decimal>();
 }
            /// <summary>
            /// Converts retail discount data from database to discount object.
            /// </summary>
            /// <param name="retailDiscount">Retail discount data from database.</param>
            /// <param name="priceContext">Price context.</param>
            /// <returns>Discount object.</returns>
            /// <remarks>This is private. Exposed as internal for test.</remarks>
            internal static DiscountBase ConvertRetailDiscountToDiscountBase(RetailDiscount retailDiscount, PriceContext priceContext)
            {
                DiscountBase discount = ConvertRetailDiscountToDiscountBase(retailDiscount);

                SetEffectiveDiscounDiscountPriorityFromPriceGroups(discount, priceContext);

                return(discount);
            }
            private static DiscountBase ConvertDiscountAndLineToDiscountBase(PeriodicDiscount discountAndLine)
            {
                DiscountBase        discount    = null;
                OfferDiscount       offer       = null;
                MixAndMatchDiscount mixAndMatch = null;
                MultipleBuyDiscount multipleBuy = null;
                ThresholdDiscount   threshold   = null;

                switch (discountAndLine.PeriodicDiscountType)
                {
                case PeriodicDiscountOfferType.Offer:
                    offer    = new OfferDiscount(discountAndLine.ValidationPeriod);
                    discount = offer;
                    break;

                case PeriodicDiscountOfferType.MixAndMatch:
                    mixAndMatch = new MixAndMatchDiscount(discountAndLine.ValidationPeriod);
                    mixAndMatch.DealPriceValue              = discountAndLine.MixAndMatchDealPrice;
                    mixAndMatch.DiscountAmountValue         = discountAndLine.MixAndMatchDiscountAmount;
                    mixAndMatch.DiscountPercentValue        = discountAndLine.MixAndMatchDiscountPercent;
                    mixAndMatch.NumberOfLeastExpensiveLines = discountAndLine.MixAndMatchNumberOfLeastExpensiveLines;
                    mixAndMatch.NumberOfTimesApplicable     = discountAndLine.MixAndMatchNumberOfTimeApplicable;
                    mixAndMatch.LeastExpensiveMode          = discountAndLine.LeastExpensiveMode;
                    discount = mixAndMatch;
                    break;

                case PeriodicDiscountOfferType.MultipleBuy:
                    multipleBuy = new MultipleBuyDiscount(discountAndLine.ValidationPeriod);
                    discount    = multipleBuy;
                    break;

                case PeriodicDiscountOfferType.Threshold:
                    threshold = new ThresholdDiscount(discountAndLine.ValidationPeriod);
                    threshold.ShouldCountNonDiscountItems = discountAndLine.ShouldCountNonDiscountItems != 0;
                    discount = threshold;
                    break;
                }

                if (discount != null)
                {
                    discount.IsCategoryToProductOrVariantIdsMapSet = true;

                    discount.OfferId                = discountAndLine.OfferId;
                    discount.OfferName              = discountAndLine.Name;
                    discount.PeriodicDiscountType   = discountAndLine.PeriodicDiscountType;
                    discount.IsDiscountCodeRequired = discountAndLine.IsDiscountCodeRequired;
                    discount.ConcurrencyMode        = discountAndLine.ConcurrencyMode;
                    discount.PricingPriorityNumber  = discountAndLine.PricingPriorityNumber;
                    discount.CurrencyCode           = discountAndLine.CurrencyCode;
                    discount.DateValidationPeriodId = discountAndLine.ValidationPeriodId;
                    discount.DateValidationType     = (DateValidationType)discountAndLine.DateValidationType;
                    discount.DiscountType           = GetDiscountMethodType(discount.PeriodicDiscountType, discountAndLine.DiscountType);
                    discount.ValidFrom              = discountAndLine.ValidFromDate;
                    discount.ValidTo                = discountAndLine.ValidToDate;
                }

                return(discount);
            }
 private static void SetEffectiveDiscountPriorityFromPriceGroups(
     Dictionary <string, DiscountBase> discountsLookup,
     PriceContext priceContext)
 {
     foreach (KeyValuePair <string, DiscountBase> pair in discountsLookup)
     {
         DiscountBase discount = pair.Value;
         SetEffectiveDiscounDiscountPriorityFromPriceGroups(discount, priceContext);
     }
 }
Exemple #10
0
        private static bool BirthdayDiscountPredicate(DiscountBase discountBase, Customer customer, Product product)
        {
            var birthdayDiscount = (BirthdayDiscount)discountBase;

            bool DateOfBirthIsDiscountRange()
            => customer.DateOfBirth >= DateTimeOffset.Now - TimeSpan.FromDays(birthdayDiscount.DateRangeInDays) &&
            customer.DateOfBirth <= DateTimeOffset.Now + TimeSpan.FromDays(birthdayDiscount.DateRangeInDays);

            return(birthdayDiscount.IsActive && DateOfBirthIsDiscountRange());
        }
 internal OverlapppedDiscounts(int itemGroupIndex, DiscountBase discount)
 {
     this.OverlapId = Guid.NewGuid();
     this.MixAndMatchAndQuantityDiscounts = new Dictionary <string, DiscountBase>(StringComparer.OrdinalIgnoreCase);
     this.MixAndMatchAndQuantityDiscounts.Add(discount.OfferId, discount);
     this.OfferDiscounts           = new Dictionary <string, DiscountBase>(StringComparer.OrdinalIgnoreCase);
     this.CoveredItemGroupIndexSet = new HashSet <int>()
     {
         itemGroupIndex
     };
 }
Exemple #12
0
        private IActionResult AddDiscountToProduct(Guid productId, DiscountBase discount)
        {
            var product = unitOfWork.ProductRepository.GetProductById(productId);

            if (product == null)
            {
                return(BadRequest());
            }

            product.Discounts.Add(discount);
            unitOfWork.ProductRepository.UpdateProduct(product);
            return(Ok());
        }
Exemple #13
0
            /// <summary>
            /// Refreshes item compounded quantity lookup after each round of calculating discounts
            ///     of exclusive or non-exclusive least-expensive-favor-retail discounts, of the same priority number.
            /// Prepares remaining quantities for the next round.
            /// </summary>
            /// <param name="bestDiscountPath">Best discount path of application discount applications so far.</param>
            /// <param name="lastPriorityNumber">Priority number of the last round.</param>
            /// <param name="remainingQuantities">Remaining quantities for the next round.</param>
            /// <param name="remainingQuantitiesForCompound">Remaining quantities for compound for the next round.</param>
            internal void RefreshLookupAndPrepareRemainingQuantitiesForLeastExpensiveFavoRetailer(
                List <AppliedDiscountApplication> bestDiscountPath,
                int lastPriorityNumber,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound)
            {
                // Step 1: fill in this.ItemGroupIndexToCompoundedOfferToQuantityLookup from the last round.
                //         reduce remaining quantitied for non-compounded discounts.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
                foreach (AppliedDiscountApplication appliedDiscountApplication in bestDiscountPath)
                {
                    DiscountBase discount = appliedDiscountApplication.DiscountApplication.Discount;

                    if (discount.CanCompound)
                    {
                        // We've already processed the lookups for the previous round. Here we only processed the most recent round.
                        // Only mix and match least expensive favoring retailer
                        MixAndMatchDiscount mixAndMatch = discount as MixAndMatchDiscount;
                        if (discount.PricingPriorityNumber == lastPriorityNumber &&
                            (mixAndMatch != null && mixAndMatch.DiscountType == DiscountMethodType.LeastExpensive && mixAndMatch.LeastExpensiveMode == LeastExpensiveMode.FavorRetailer))
                        {
                            this.FillLookup(appliedDiscountApplication);
                        }
                    }
                    else
                    {
                        ReduceQuantitiesFromNonCompounded(appliedDiscountApplication, remainingQuantities, remainingQuantitiesForCompound);
                    }
                }

                // Step 2: move the data to settled lookups.
                foreach (KeyValuePair <int, Dictionary <string, decimal> > pair in this.ItemGroupIndexToCompoundedOfferToQuantityLookup)
                {
                    int itemGroupIndex = pair.Key;
                    Dictionary <string, decimal> offerIdToQuantityLookup = pair.Value;
                    decimal compoundedQuantity = offerIdToQuantityLookup.Max(p => p.Value);
                    AddToOrUpdateItemGroupIndexToPriorityToSettledCompoundedQuantityLookup(
                        itemGroupIndex,
                        lastPriorityNumber,
                        compoundedQuantity,
                        this.itemGroupIndexToPriorityToSettledCompoundedQuantityForNonThreshold);
                }

                this.ReduceQuantitiesFromCompoundedForNonThresholds(
                    remainingQuantities,
                    remainingQuantitiesForCompound,
                    null);

                // Clear current lookup.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
            }
Exemple #14
0
            /// <summary>
            /// Refreshes item compounded quantity lookup after each round of calculating discounts
            ///     of exclusive or non-exclusive non-thresholds discounts, of the same priority number.
            /// Prepares remaining quantities for the next round.
            /// </summary>
            /// <param name="bestDiscountPath">Best discount path of application discount applications so far.</param>
            /// <param name="lastPriorityNumber">Priority number of the last round.</param>
            /// <param name="remainingQuantities">Remaining quantities for the next round.</param>
            /// <param name="remainingQuantitiesForCompound">Remaining quantities for compound for the next round.</param>
            /// <param name="reserveCompoundedQuantityForLeastExpensiveFavorRetailer">
            /// If it's followed by least expensive favoring retail of the same priority, then reserve the compounded quantity of the same priority.
            /// </param>
            internal void RefreshLookupAndPrepareRemainingQuantitiesForNonThresholds(
                List <AppliedDiscountApplication> bestDiscountPath,
                int lastPriorityNumber,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound,
                bool reserveCompoundedQuantityForLeastExpensiveFavorRetailer)
            {
                // Step 1: fill in this.ItemGroupIndexToCompoundedOfferToQuantityLookup from the last round.
                //         reduce remaining quantitied for non-compounded discounts.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
                foreach (AppliedDiscountApplication appliedDiscountApplication in bestDiscountPath)
                {
                    DiscountBase discount = appliedDiscountApplication.DiscountApplication.Discount;

                    if (discount.CanCompound)
                    {
                        // We've already processed the lookups for the previous round. Here we only processed the most recent round.
                        // Ignore mix and match least expensive favoring retailer
                        MixAndMatchDiscount mixAndMatch = discount as MixAndMatchDiscount;
                        if (discount.PricingPriorityNumber == lastPriorityNumber &&
                            discount.PeriodicDiscountType != PeriodicDiscountOfferType.Threshold &&
                            (mixAndMatch == null || mixAndMatch.DiscountType != DiscountMethodType.LeastExpensive || mixAndMatch.LeastExpensiveMode == LeastExpensiveMode.FavorCustomer))
                        {
                            this.FillLookup(appliedDiscountApplication);
                        }
                    }
                    else
                    {
                        ReduceQuantitiesFromNonCompounded(appliedDiscountApplication, remainingQuantities, remainingQuantitiesForCompound);
                    }
                }

                // Step 2: move the data to settled lookups.
                this.MoveToSettledCompoundedQuantityLookup(lastPriorityNumber, this.itemGroupIndexToPriorityToSettledCompoundedQuantityForNonThreshold);

                int?priorityNumberToSkipForCompounded = null;

                if (reserveCompoundedQuantityForLeastExpensiveFavorRetailer)
                {
                    priorityNumberToSkipForCompounded = lastPriorityNumber;
                }

                this.ReduceQuantitiesFromCompoundedForNonThresholds(
                    remainingQuantities,
                    remainingQuantitiesForCompound,
                    priorityNumberToSkipForCompounded);

                // Clear current lookup.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
            }
Exemple #15
0
        public override DiscountBase GetDiscountStrategy(Basket basket, DiscountBase parentDiscountStrategy)
        {
            DiscountBase bestStrategy = null;
            double       bestPrice    = 0;

            foreach (KeyValuePair <DiscountTypesEnum, Func <DiscountBase, DiscountBase> > discountStrategy in BasketDiscountOverMemberShipFactory.Creators)
            {
                DiscountBase currentStrategy = discountStrategy.Value(parentDiscountStrategy);
                if (currentStrategy.IsEnabled(basket) &&
                    (bestStrategy == null || bestStrategy.GetDiscountedTotal(basket) > currentStrategy.GetDiscountedTotal(basket)))
                {
                    (bestPrice, bestStrategy) = this.GetBetterStrategy(bestPrice, bestStrategy, basket, currentStrategy);
                }
            }
            return(bestStrategy);
        }
        public void BasketDiscount15PercentTest()
        {
            //Arrange
            User    u      = new User("user", MemberShipTypeEnum.STANDART);
            Basket  basket = new Basket(u, DateTime.Now);
            Product p1     = new Product("p1", 100, 90);
            Product p2     = new Product("p2", 200, 180);

            basket.AddOrderProduct(new BasketProduct(p1, 1));
            basket.AddOrderProduct(new BasketProduct(p2, 1));
            //Act
            DiscountBase discountStrategy = StandartDiscountFactory.Instance.GetDiscountStrategy(basket);
            double       discountedPrice  = basket.calcDiscountedTotal(discountStrategy);

            //Assert
            discountedPrice.Should().Be(255);
        }
        public void BlackFriday40PercentTest()
        {
            //Arrange
            User    u      = new User("user", MemberShipTypeEnum.STANDART);
            Basket  basket = new Basket(u, new DateTime(2019, 11, 29));
            Product p1     = new Product("p1", 700, 650);
            Product p2     = new Product("p2", 500, 400);

            basket.AddOrderProduct(new BasketProduct(p1, 1));
            basket.AddOrderProduct(new BasketProduct(p2, 1));
            //Act
            DiscountBase discountStrategy = StandartDiscountFactory.Instance.GetDiscountStrategy(basket);
            double       discountedPrice  = basket.calcDiscountedTotal(discountStrategy);

            //Assert
            discountedPrice.Should().Be(720);
        }
            private static DiscountDealEstimate BuildEstimates(
                Dictionary <string, DiscountDealEstimate> offerIdToEstimateNonCompoundedLookupHolder,
                List <DiscountBase> compoundedDiscountsHolder,
                DiscountDealEstimate existingCombinedEstimatesForCompounded,
                Dictionary <string, DiscountBase> discounts,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound,
                HashSet <int> itemsWithOverlappingDiscounts,
                HashSet <int> itemsWithOverlappingDiscountsCompoundedOnly)
            {
                using (SimpleProfiler profiler = new SimpleProfiler("OverlappedDiscounts.BuildEstimates", 2))
                {
                    DiscountDealEstimate combinedEstimateForCompounded = existingCombinedEstimatesForCompounded;
                    foreach (KeyValuePair <string, DiscountBase> pair in discounts)
                    {
                        DiscountBase         discount = pair.Value;
                        DiscountDealEstimate estimate = discount.GetDiscountDealEstimate(
                            discountableItemGroups,
                            discount.CanCompound ? remainingQuantitiesForCompound : remainingQuantities,
                            itemsWithOverlappingDiscounts,
                            itemsWithOverlappingDiscountsCompoundedOnly);

                        if (discount.CanCompound)
                        {
                            if (combinedEstimateForCompounded == null)
                            {
                                combinedEstimateForCompounded = estimate;
                            }
                            else
                            {
                                combinedEstimateForCompounded = DiscountDealEstimate.Combine(combinedEstimateForCompounded, estimate);
                            }

                            compoundedDiscountsHolder.Add(discount);
                        }
                        else
                        {
                            offerIdToEstimateNonCompoundedLookupHolder[discount.OfferId] = estimate;
                        }
                    }

                    // returns combined estimate for compounded
                    return(combinedEstimateForCompounded);
                }
            }
            internal static Dictionary <long, List <DiscountBase> > GetProductOrVarintToDiscountMapFromCache(
                IPricingDataAccessor pricingDataManager,
                PriceContext priceContext,
                SalesTransaction transaction)
            {
                ISet <long> productVariantMasterIdsInTransaction = GetProductVariantMasterIdsForTransaction(transaction);
                Dictionary <long, IList <RetailCategoryMember> > categorytoProductOrVariantIdsMap = GetCategoryToProductOrVariantIdsMapForTransaction(pricingDataManager, productVariantMasterIdsInTransaction);

                IEnumerable <RetailDiscount> allDiscounts = pricingDataManager.GetAllRetailDiscounts() as IEnumerable <RetailDiscount>;

                Dictionary <long, List <DiscountBase> > allApplicableDiscounts = new Dictionary <long, List <DiscountBase> >();

                foreach (RetailDiscount retailDiscount in allDiscounts)
                {
                    if (!PriceContextHelper.MatchCalculationMode(priceContext, retailDiscount.PeriodicDiscountType))
                    {
                        continue;
                    }

                    DiscountBase discount = ConvertRetailDiscountToDiscountBase(retailDiscount, priceContext);
                    discount.ProductOrVariantIdsInTransaction = productVariantMasterIdsInTransaction;

                    // Product or variant id to categories map is needed to filter which discount lines are applicable for the transaction. See DiscountBase class.
                    discount.CategoryToProductOrVariantIdsMap = categorytoProductOrVariantIdsMap;
                    IDictionary <long, IList <RetailDiscountLine> > itemDiscounts = discount.GetProductOrVariantIdToRetailDiscountLinesMap();
                    foreach (long productOrVariantId in itemDiscounts.Keys)
                    {
                        if (allApplicableDiscounts.ContainsKey(productOrVariantId))
                        {
                            allApplicableDiscounts[productOrVariantId].Add(discount);
                        }
                        else
                        {
                            allApplicableDiscounts.Add(productOrVariantId, new List <DiscountBase>()
                            {
                                discount
                            });
                        }
                    }
                }

                return(allApplicableDiscounts);
            }
            private void Aquire(
                Dictionary <int, OverlapppedDiscounts> itemGroupIndexToOverlapppedDiscountsLookup,
                OverlapppedDiscounts overlapppedDiscountsToAcquire)
            {
                using (SimpleProfiler profiler = new SimpleProfiler("OverlappedDiscounts.Aquire", 4))
                {
                    this.MixAndMatchAndQuantityDiscounts.AddRange(overlapppedDiscountsToAcquire.MixAndMatchAndQuantityDiscounts);
                    this.CoveredItemGroupIndexSet.AddRange(overlapppedDiscountsToAcquire.CoveredItemGroupIndexSet);

                    foreach (KeyValuePair <string, DiscountBase> pairOfferIdToDiscount in overlapppedDiscountsToAcquire.MixAndMatchAndQuantityDiscounts)
                    {
                        DiscountBase discount = pairOfferIdToDiscount.Value;
                        foreach (KeyValuePair <int, HashSet <decimal> > pair in discount.ItemGroupIndexToDiscountLineNumberSetMap)
                        {
                            int itemGroupIndex = pair.Key;
                            itemGroupIndexToOverlapppedDiscountsLookup[itemGroupIndex] = this;
                        }
                    }
                }
            }
Exemple #21
0
            /// <summary>
            /// Refreshes item compounded quantity lookup after each round of calculating discounts
            ///     of exclusive or non-exclusive thresholds discounts, of the same priority number.
            /// Prepares remaining quantities for the next round.
            /// </summary>
            /// <param name="bestDiscountPath">Best discount path of application discount applications so far.</param>
            /// <param name="lastPriorityNumber">Priority number of the last round.</param>
            /// <param name="remainingQuantities">Remaining quantities for the next round.</param>
            /// <param name="remainingQuantitiesForCompound">Remaining quantities for compound for the next round.</param>
            internal void RefreshLookupAndPrepareRemainingQuantitiesForThresholds(
                List <AppliedDiscountApplication> bestDiscountPath,
                int lastPriorityNumber,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound)
            {
                // Step 1: fill in this.ItemGroupIndexToCompoundedOfferToQuantityLookup from the last round.
                //         reduce remaining quantitied for non-compounded discounts.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
                foreach (AppliedDiscountApplication appliedDiscountApplication in bestDiscountPath)
                {
                    DiscountBase discount = appliedDiscountApplication.DiscountApplication.Discount;

                    if (discount.CanCompound)
                    {
                        // We've already processed the lookups for the previous round. Here we only processed the most recent round.
                        if (discount.PricingPriorityNumber == lastPriorityNumber &&
                            discount.PeriodicDiscountType == PeriodicDiscountOfferType.Threshold)
                        {
                            this.FillLookup(appliedDiscountApplication);
                        }
                    }
                    else
                    {
                        ReduceQuantitiesFromNonCompounded(appliedDiscountApplication, remainingQuantities, remainingQuantitiesForCompound);
                    }
                }

                // Step 2: move the data to settled lookups.
                this.MoveToSettledCompoundedQuantityLookup(lastPriorityNumber, this.itemGroupIndexToPriorityToSettledCompoundedQuantityForThreshold);

                // Step 3: figure out remain quantities for threshold evaluation.
                this.ReduceQuantitiesFromCompoundedThresholdsForThresholds(remainingQuantities, remainingQuantitiesForCompound);
                this.ReduceQuantitiesFromCompoundedNonThresholdForThreshold(remainingQuantities);

                // Clear current lookup.
                this.ItemGroupIndexToCompoundedOfferToQuantityLookup.Clear();
            }
 private void AddDiscount(DiscountBase discount)
 {
     if (discount is AmountDiscount)
     {
         AmountDiscount a = discount as AmountDiscount;
         AmountDiscount aad = GetAmountDiscount(discount.DiscountType);
         aad.DiscountAmount += a.DiscountAmount;
     }
     if (discount is ProductAsDiscount)
     {
         ProductAsDiscount a = discount as ProductAsDiscount;
         ProductAsDiscount pad = GetProductAsDiscount(discount.DiscountType,a.ProductId);
         pad.Quantity += a.Quantity;
     }
 }
            internal bool IsOkayForMixAndMatchLeastExpensiveOneLineGroupPartialOptimizationAndFillupValueLookups(
                Dictionary <int, decimal> mixAndMatchRelativeValueLookup,
                List <int> itemGroupIndexListSortedByRelativePriceDescending,
                List <HashSet <int> > consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending,
                Dictionary <int, OfferDiscount> simpleDiscountOfferLookup,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities)
            {
                // Special optimization for this overlapped group when there is
                //   only one mix and match
                //   with only one line group
                //   with least expensive deal price and %-off
                bool isOkay = this.MixAndMatchAndQuantityDiscounts.Count == 1;

                consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Clear();

                MixAndMatchDiscount mixAndMatchDiscount = null;

                if (isOkay)
                {
                    mixAndMatchDiscount = this.MixAndMatchAndQuantityDiscounts.First().Value as MixAndMatchDiscount;

                    isOkay = mixAndMatchDiscount != null;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.Count == 1;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.DiscountType == DiscountMethodType.LeastExpensive &&
                             !mixAndMatchDiscount.IsLeastExpensiveAmountOff;
                }

                if (isOkay)
                {
                    var           lineGroupNumberOfItemsPair = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.First();
                    string        lineGroup      = lineGroupNumberOfItemsPair.Key;
                    decimal       quantityNeeded = lineGroupNumberOfItemsPair.Value;
                    HashSet <int> mixAndMatchItemIndexGroupSet = null;
                    if (mixAndMatchDiscount.LineGroupToItemGroupIndexSetLookup.TryGetValue(lineGroup, out mixAndMatchItemIndexGroupSet))
                    {
                        foreach (int itemGroupIndex in mixAndMatchItemIndexGroupSet)
                        {
                            if (remainingQuantities[itemGroupIndex] > decimal.Zero)
                            {
                                // Move it to mix and match
                                decimal mixAndMatchValue = decimal.Zero;

                                decimal price = discountableItemGroups[itemGroupIndex].Price;
                                decimal totalPriceForDiscount = price * mixAndMatchDiscount.NumberOfLeastExpensiveLines;

                                if (mixAndMatchDiscount.DiscountPercentValue > decimal.Zero)
                                {
                                    mixAndMatchValue = DiscountBase.GetDiscountAmountForPercentageOff(totalPriceForDiscount, mixAndMatchDiscount.DiscountPercentValue);
                                }
                                else if (mixAndMatchDiscount.DiscountAmountValue == decimal.Zero && mixAndMatchDiscount.DealPriceValue > decimal.Zero)
                                {
                                    mixAndMatchValue = totalPriceForDiscount - mixAndMatchDiscount.DealPriceValue;
                                    if (mixAndMatchValue < decimal.Zero)
                                    {
                                        isOkay = false;
                                    }
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                decimal simpleDiscountValue = decimal.Zero;
                                int     discountOfferCount  = 0;

                                // If an item has compounded discounts only, then we have already processed compounded discount offers, so we won't see them here.
                                //    And this won't affect algorithm.
                                // If we modify optimization before overlapped discounts, then this may not be true.
                                // See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                foreach (KeyValuePair <string, DiscountBase> pair in this.OfferDiscounts)
                                {
                                    OfferDiscount offer = pair.Value as OfferDiscount;

                                    if (offer != null)
                                    {
                                        HashSet <decimal> discountLineNumberSetForItem = null;
                                        if (offer.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(itemGroupIndex, out discountLineNumberSetForItem))
                                        {
                                            if (discountLineNumberSetForItem.Count >= 1)
                                            {
                                                decimal            discountLineNumber     = discountLineNumberSetForItem.First();
                                                RetailDiscountLine discountLineDefinition = offer.DiscountLines[discountLineNumber];

                                                // For now, it works for fully covered items.
                                                // Fully covered, as an example, buy 3 for $10, then if item price is $3, it won't get discount
                                                //     or buy 3 for $5 off, then if item price is $1, it won't get full discount.
                                                bool isFullyCovered = false;
                                                simpleDiscountValue = OfferDiscount.GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered) * quantityNeeded;
                                                if (!isFullyCovered)
                                                {
                                                    isOkay = false;
                                                }

                                                if (!isOkay)
                                                {
                                                    break;
                                                }

                                                simpleDiscountOfferLookup[itemGroupIndex] = offer;

                                                discountOfferCount++;
                                            }
                                        }
                                    }
                                }

                                // For now, we don't handle multiple discount offers.
                                // With optimization earlier, that's mostly the case. See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                if (discountOfferCount > 1)
                                {
                                    isOkay = false;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                mixAndMatchRelativeValueLookup.Add(itemGroupIndex, mixAndMatchValue - simpleDiscountValue);
                            }
                        }
                    }
                }

                if (isOkay)
                {
                    // Now group items by relative price and price, and order them by relative price, in consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.
                    // See tests in OverlappedDiscountsUnitTests.
                    itemGroupIndexListSortedByRelativePriceDescending.AddRange(mixAndMatchRelativeValueLookup.Keys);
                    ItemPriceComparer itemPriceComparer = new ItemPriceComparer(mixAndMatchRelativeValueLookup);
                    itemGroupIndexListSortedByRelativePriceDescending.Sort(itemPriceComparer.GetComparison());

                    decimal previousRelativePrice = decimal.Zero;
                    Dictionary <decimal, HashSet <int> > itemPriceToItemGroupIndexSetLookup = new Dictionary <decimal, HashSet <int> >();
                    for (int listIndex = 0; listIndex < itemGroupIndexListSortedByRelativePriceDescending.Count; listIndex++)
                    {
                        int     itemGroupIndex       = itemGroupIndexListSortedByRelativePriceDescending[listIndex];
                        decimal currentPrice         = discountableItemGroups[itemGroupIndex].Price;
                        decimal currentRelativePrice = mixAndMatchRelativeValueLookup[itemGroupIndex];
                        if (listIndex == 0)
                        {
                            previousRelativePrice = currentRelativePrice;
                            itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                            {
                                itemGroupIndex
                            });
                        }
                        else
                        {
                            if (currentRelativePrice == previousRelativePrice)
                            {
                                HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = null;
                                if (itemPriceToItemGroupIndexSetLookup.TryGetValue(currentPrice, out itemGroupIndexSetWithSameRelativePriceAndPrice))
                                {
                                    itemGroupIndexSetWithSameRelativePriceAndPrice.Add(itemGroupIndex);
                                }
                                else
                                {
                                    itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                                    {
                                        itemGroupIndex
                                    });
                                }
                            }

                            if (currentRelativePrice != previousRelativePrice)
                            {
                                foreach (KeyValuePair <decimal, HashSet <int> > pair in itemPriceToItemGroupIndexSetLookup)
                                {
                                    HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = pair.Value;

                                    consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Add(new HashSet <int>(itemGroupIndexSetWithSameRelativePriceAndPrice));
                                }

                                previousRelativePrice = currentRelativePrice;
                                itemPriceToItemGroupIndexSetLookup.Clear();
                                itemPriceToItemGroupIndexSetLookup.Add(currentPrice, new HashSet <int>()
                                {
                                    itemGroupIndex
                                });
                            }

                            if (listIndex == itemGroupIndexListSortedByRelativePriceDescending.Count - 1)
                            {
                                foreach (KeyValuePair <decimal, HashSet <int> > pair in itemPriceToItemGroupIndexSetLookup)
                                {
                                    HashSet <int> itemGroupIndexSetWithSameRelativePriceAndPrice = pair.Value;
                                    consolidatedListOfItemsWithSamePriceAndRelativeValuesDescending.Add(new HashSet <int>(itemGroupIndexSetWithSameRelativePriceAndPrice));
                                }

                                itemPriceToItemGroupIndexSetLookup.Clear();
                            }
                        }
                    }
                }

                return(isOkay);
            }
            internal DiscountBase[] GetSortedDiscountsToApplyInFastMode(
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities,
                decimal[] remainingQuantitiesForCompound,
                HashSet <int> itemsWithOverlappingDiscounts,
                HashSet <int> itemsWithOverlappingDiscountsCompoundedOnly)
            {
                using (SimpleProfiler profiler = new SimpleProfiler("OverlappedDiscounts.GetDiscountsToApplyInFastMode", 2))
                {
                    Dictionary <string, DiscountDealEstimate> offerIdToEstimateNonCompoundedLookup = new Dictionary <string, DiscountDealEstimate>(StringComparer.OrdinalIgnoreCase);

                    // Consolidate all compounded discounts into one estimate, to be sorted with the rest later.
                    List <DiscountBase>  compoundedDiscounts           = new List <DiscountBase>();
                    DiscountDealEstimate combinedEstimateForCompounded = null;

                    // Build estimates for offer discounts.
                    combinedEstimateForCompounded = OverlapppedDiscounts.BuildEstimates(
                        offerIdToEstimateNonCompoundedLookup,
                        compoundedDiscounts,
                        combinedEstimateForCompounded,
                        this.OfferDiscounts,
                        discountableItemGroups,
                        remainingQuantities,
                        remainingQuantitiesForCompound,
                        itemsWithOverlappingDiscounts,
                        itemsWithOverlappingDiscountsCompoundedOnly);

                    // Build estimates for mix and match and quantity discounts.
                    combinedEstimateForCompounded = OverlapppedDiscounts.BuildEstimates(
                        offerIdToEstimateNonCompoundedLookup,
                        compoundedDiscounts,
                        combinedEstimateForCompounded,
                        this.MixAndMatchAndQuantityDiscounts,
                        discountableItemGroups,
                        remainingQuantities,
                        remainingQuantitiesForCompound,
                        itemsWithOverlappingDiscounts,
                        itemsWithOverlappingDiscountsCompoundedOnly);

                    List <DiscountDealEstimate> estimatedSorted = new List <DiscountDealEstimate>(offerIdToEstimateNonCompoundedLookup.Values);
                    if (combinedEstimateForCompounded != null)
                    {
                        estimatedSorted.Add(combinedEstimateForCompounded);
                    }

                    estimatedSorted.Sort(DiscountDealEstimate.GetComparison());

    #if DEBUG
                    foreach (DiscountDealEstimate estimate in estimatedSorted)
                    {
                        estimate.DebugDisplay();
                    }
    #endif

                    DiscountBase[] discountsSorted = new DiscountBase[this.MixAndMatchAndQuantityDiscounts.Count + this.OfferDiscounts.Count];
                    int            discountIndex   = 0;
                    for (int i = estimatedSorted.Count - 1; i >= 0; i--)
                    {
                        DiscountDealEstimate estimate = estimatedSorted[i];

                        if (estimate.CanCompound)
                        {
                            for (int compoundedIndex = 0; compoundedIndex < compoundedDiscounts.Count; compoundedIndex++)
                            {
                                discountsSorted[discountIndex] = compoundedDiscounts[compoundedIndex];
                                discountIndex++;
                            }
                        }
                        else
                        {
                            DiscountBase discount = null;

                            if (this.MixAndMatchAndQuantityDiscounts.TryGetValue(estimate.OfferId, out discount))
                            {
                                discountsSorted[discountIndex] = discount;
                                discountIndex++;
                            }
                            else if (this.OfferDiscounts.TryGetValue(estimate.OfferId, out discount))
                            {
                                discountsSorted[discountIndex] = discount;
                                discountIndex++;
                            }
                        }
                    }

                    return(discountsSorted);
                }
            }
            internal bool IsOkayForMixAndMatchOneLineGroupOptimizationAndFillupValueLookups(
                Dictionary <int, decimal> mixAndMatchRelativeValueLookup,
                Dictionary <int, OfferDiscount> simpleDiscountOfferLookup,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities)
            {
                // Special optimization for this overlapped group when there is
                //   only one mix and match
                //   with only one line group
                //   with any of deal price, $ off, % off, or least expensive $-off.
                bool isOkay = this.MixAndMatchAndQuantityDiscounts.Count == 1;

                MixAndMatchDiscount mixAndMatchDiscount = null;

                if (isOkay)
                {
                    mixAndMatchDiscount = this.MixAndMatchAndQuantityDiscounts.First().Value as MixAndMatchDiscount;

                    isOkay = mixAndMatchDiscount != null;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.DiscountType == DiscountMethodType.DealPrice ||
                             mixAndMatchDiscount.DiscountType == DiscountMethodType.DiscountPercent ||
                             mixAndMatchDiscount.DiscountType == DiscountMethodType.DiscountAmount ||
                             mixAndMatchDiscount.IsLeastExpensiveAmountOff;
                }

                if (isOkay)
                {
                    isOkay = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.Count == 1;
                }

                if (isOkay)
                {
                    // Mix and match example: buy 3 for $10.
                    // For each item, calculate relative value of mix and match against discount offer with quantity 3 as a group.
                    //     relative value of item A = M(AAA) - 3S(A)
                    // See DiscountCalculator.TryOptimizeForOneMixAndMatchWithOneLineGroup
                    var           lineGroupNumberOfItemsPair = mixAndMatchDiscount.LineGroupToNumberOfItemsMap.First();
                    string        lineGroup      = lineGroupNumberOfItemsPair.Key;
                    decimal       quantityNeeded = lineGroupNumberOfItemsPair.Value;
                    HashSet <int> mixAndMatchItemIndexGroupSet = null;
                    if (mixAndMatchDiscount.LineGroupToItemGroupIndexSetLookup.TryGetValue(lineGroup, out mixAndMatchItemIndexGroupSet))
                    {
                        foreach (int itemGroupIndex in mixAndMatchItemIndexGroupSet)
                        {
                            if (remainingQuantities[itemGroupIndex] > decimal.Zero)
                            {
                                // Move it to mix and match
                                decimal mixAndMatchValue = decimal.Zero;

                                decimal price      = discountableItemGroups[itemGroupIndex].Price;
                                decimal totalPrice = price * quantityNeeded;
                                switch (mixAndMatchDiscount.DiscountType)
                                {
                                case DiscountMethodType.DealPrice:
                                    mixAndMatchValue = totalPrice - mixAndMatchDiscount.DealPriceValue;
                                    isOkay           = mixAndMatchValue >= decimal.Zero;
                                    break;

                                case DiscountMethodType.DiscountAmount:
                                    mixAndMatchValue = mixAndMatchDiscount.DiscountAmountValue;
                                    isOkay           = totalPrice >= mixAndMatchDiscount.DiscountAmountValue;
                                    break;

                                case DiscountMethodType.DiscountPercent:
                                    mixAndMatchValue = DiscountBase.GetDiscountAmountForPercentageOff(totalPrice, mixAndMatchDiscount.DiscountPercentValue);
                                    break;

                                default:
                                    isOkay = false;
                                    if (mixAndMatchDiscount.IsLeastExpensiveAmountOff && price >= mixAndMatchDiscount.DiscountAmountValue)
                                    {
                                        isOkay           = true;
                                        mixAndMatchValue = mixAndMatchDiscount.DiscountAmountValue;
                                    }

                                    break;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                int     discountOfferCount  = 0;
                                decimal simpleDiscountValue = decimal.Zero;
                                foreach (KeyValuePair <string, DiscountBase> pair in this.OfferDiscounts)
                                {
                                    OfferDiscount offer = pair.Value as OfferDiscount;

                                    if (offer != null)
                                    {
                                        HashSet <decimal> discountLineNumberSetForItem = null;
                                        if (offer.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(itemGroupIndex, out discountLineNumberSetForItem))
                                        {
                                            if (discountLineNumberSetForItem.Count >= 1)
                                            {
                                                decimal            discountLineNumber     = discountLineNumberSetForItem.First();
                                                RetailDiscountLine discountLineDefinition = offer.DiscountLines[discountLineNumber];

                                                // For now, it works for fully covered items.
                                                // Fully covered, as an example, buy 3 for $10, then if item price is $3, it won't get discount
                                                //     or buy 3 for $5 off, then if item price is $1, it won't get full discount.
                                                bool isFullyCovered = false;
                                                simpleDiscountValue = OfferDiscount.GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered) * quantityNeeded;
                                                if (!isFullyCovered)
                                                {
                                                    isOkay = false;
                                                }

                                                if (!isOkay)
                                                {
                                                    break;
                                                }

                                                simpleDiscountOfferLookup[itemGroupIndex] = offer;
                                                discountOfferCount++;
                                            }
                                        }
                                    }
                                }

                                // For now, we don't handle multiple discount offers.
                                // With optimization earlier, that's mostly the case. See DiscountCalcuator.ReduceOverlappedOfferAndQuantityDiscountsPerItem.
                                if (discountOfferCount > 1)
                                {
                                    isOkay = false;
                                }

                                if (!isOkay)
                                {
                                    break;
                                }

                                mixAndMatchRelativeValueLookup.Add(itemGroupIndex, mixAndMatchValue - simpleDiscountValue);
                            }
                        }
                    }
                }

                return(isOkay);
            }
Exemple #26
0
 protected MovieLicenseBase(string movie, DateTime purchaseTime, DiscountBase discount)
 {
     Movie        = movie;
     PurchaseTime = purchaseTime;
     _discount    = discount;
 }
 public double calcDiscountedTotal(DiscountBase discountStrategy)
 {
     DiscountedTotal = discountStrategy.GetDiscountedTotal(this);
     return(DiscountedTotal);
 }
Exemple #28
0
 public abstract DiscountBase GetDiscountStrategy(Basket basket, DiscountBase parentDiscountStrategy);
 public TwoDaysMovieLicense(string movie, DateTime purchaseTime, DiscountBase discount) : base(movie, purchaseTime, discount)
 {
 }
Exemple #30
0
 protected virtual (double price, DiscountBase) GetBetterStrategy(double discountedPrice, DiscountBase currentStragey, Basket basket, DiscountBase nextDiscountStrategy)
 {
     if (nextDiscountStrategy != null)
     {
         double nextPrice = nextDiscountStrategy.GetDiscountedTotal(basket);
         if (currentStragey == null || nextPrice < discountedPrice)
         {
             return(nextPrice, nextDiscountStrategy);
         }
     }
     return(discountedPrice, currentStragey);
 }
            public override AppliedDiscountApplication GetAppliedDiscountApplication(
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities,
                IEnumerable <AppliedDiscountApplication> appliedDiscounts,
                DiscountApplication discountApplication,
                PriceContext priceContext)
            {
                if (discountApplication == null || !discountApplication.RetailDiscountLines.Any() || discountableItemGroups == null || remainingQuantities == null)
                {
                    return(null);
                }

                decimal[] prices = new decimal[discountableItemGroups.Length];
                Dictionary <int, IList <DiscountLineQuantity> > discountDictionary = this.GetExistingDiscountDictionaryAndDiscountedPrices(
                    discountableItemGroups,
                    remainingQuantities,
                    appliedDiscounts,
                    discountApplication,
                    true,
                    true,
                    prices);

                RetailDiscountLineItem retailDiscountLineItem = discountApplication.RetailDiscountLines.ElementAt(0);

                DiscountOfferMethod discountMethod = (DiscountOfferMethod)retailDiscountLineItem.RetailDiscountLine.DiscountMethod;
                decimal             dealPrice      = decimal.Zero;

                decimal discountValue = decimal.Zero;
                decimal discountAmountForDiscountLine = decimal.Zero;

                switch (discountMethod)
                {
                case DiscountOfferMethod.DiscountAmount:
                    discountValue = retailDiscountLineItem.RetailDiscountLine.DiscountAmount;
                    discountAmountForDiscountLine = discountValue;
                    break;

                case DiscountOfferMethod.DiscountPercent:
                    discountValue = prices[retailDiscountLineItem.ItemIndex] * (retailDiscountLineItem.RetailDiscountLine.DiscountPercent / 100M);
                    break;

                case DiscountOfferMethod.OfferPrice:
                    dealPrice = retailDiscountLineItem.RetailDiscountLine.OfferPrice;
                    decimal bestExistingDealPrice = 0m;
                    bool    hasExistingDealPrice  = DiscountBase.TryGetBestExistingDealPrice(discountDictionary, retailDiscountLineItem.ItemIndex, out bestExistingDealPrice);

                    // We don't use discounted price here.
                    discountValue = DiscountBase.GetDiscountAmountFromDealPrice(discountableItemGroups[retailDiscountLineItem.ItemIndex].Price, hasExistingDealPrice, bestExistingDealPrice, dealPrice);
                    discountAmountForDiscountLine = discountValue;
                    break;

                default:
                    break;
                }

                // When has no competing discounts or compounded, apply all remaining quantity.
                bool    applyAllAvailableQuantity = (discountApplication.ApplyStandalone || this.CanCompound) && !discountApplication.HonorQuantity;
                decimal quantityToApply           = applyAllAvailableQuantity ?
                                                    remainingQuantities[retailDiscountLineItem.ItemIndex] : discountApplication.ItemQuantities[retailDiscountLineItem.ItemIndex];

                decimal result = discountValue * quantityToApply;

                AppliedDiscountApplication newAppliedDiscountApplication = null;

                if (result > decimal.Zero)
                {
                    Dictionary <int, decimal> itemQuantities;

                    if (applyAllAvailableQuantity)
                    {
                        itemQuantities = new Dictionary <int, decimal>();
                        itemQuantities[retailDiscountLineItem.ItemIndex] = quantityToApply;
                    }
                    else
                    {
                        itemQuantities = discountApplication.ItemQuantities;
                    }

                    newAppliedDiscountApplication = new AppliedDiscountApplication(discountApplication, result, itemQuantities, isDiscountLineGenerated: true);

                    DiscountLine discountLine = this.NewDiscountLine(discountApplication.DiscountCode, discountableItemGroups[retailDiscountLineItem.ItemIndex].ItemId);

                    discountLine.PeriodicDiscountType = PeriodicDiscountOfferType.Offer;
                    discountLine.DealPrice            = dealPrice;
                    discountLine.Amount     = discountAmountForDiscountLine;
                    discountLine.Percentage = retailDiscountLineItem.RetailDiscountLine.DiscountPercent;

                    newAppliedDiscountApplication.AddDiscountLine(retailDiscountLineItem.ItemIndex, new DiscountLineQuantity(discountLine, itemQuantities[retailDiscountLineItem.ItemIndex]));

                    if (discountApplication.RemoveItemsFromLookupsWhenApplied)
                    {
                        this.RemoveItemIndexGroupFromLookups(retailDiscountLineItem.ItemIndex);
                    }
                }

                return(newAppliedDiscountApplication);
            }