Ejemplo n.º 1
0
            /// <summary>
            /// Get single item non-overlapped discount result.
            /// </summary>
            /// <param name="itemGroupIndex">Item group index.</param>
            /// <param name="price">Item price.</param>
            /// <param name="quantity">Item quantity.</param>
            /// <returns>Single item non-overlapped discount result.</returns>
            protected internal override SingleItemNonOverlappedDiscountResult GetSingleItemNonOverlappedDiscountResult(
                int itemGroupIndex,
                decimal price,
                decimal quantity)
            {
                SingleItemNonOverlappedDiscountResult result = SingleItemNonOverlappedDiscountResult.NotApplicable;
                HashSet <decimal> discountLineNumberSet      = null;

                if (this.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(itemGroupIndex, out discountLineNumberSet))
                {
                    if (discountLineNumberSet.Count == 1)
                    {
                        RetailDiscountLine discountLineDefinition = this.DiscountLines[discountLineNumberSet.First()];

                        result = new SingleItemNonOverlappedDiscountResult(
                            (DiscountOfferMethod)discountLineDefinition.DiscountMethod,
                            discountLineDefinition.DiscountAmount,
                            discountLineDefinition.DiscountPercent,
                            discountLineDefinition.OfferPrice,
                            GetUnitDiscountAmount(discountLineDefinition, price),
                            this.ConcurrencyMode);
                    }
                }

                return(result);
            }
Ejemplo n.º 2
0
            private void ReduceWorseDiscountLines(
                DiscountableItemGroup[] discountableItemGroups)
            {
                // Tested in PricingTests.UnitTestOfferPreOptimization
                foreach (KeyValuePair <int, HashSet <decimal> > pair in this.ItemGroupIndexToDiscountLineNumberSetMap)
                {
                    int itemGroupIndex = pair.Key;
                    HashSet <decimal> discountLineNumberSet = pair.Value;

                    if (discountLineNumberSet.Count > 1)
                    {
                        decimal discountLineNumberToKeep = decimal.Zero;
                        decimal bestUnitDiscountAmount   = decimal.Zero;
                        bool    isFirst = true;

                        decimal price = discountableItemGroups[itemGroupIndex].Price;
                        foreach (decimal discountLineNumber in discountLineNumberSet)
                        {
                            RetailDiscountLine discountLine       = this.DiscountLines[discountLineNumber];
                            decimal            unitDiscountAmount = GetUnitDiscountAmount(discountLine, price);
                            if (isFirst)
                            {
                                discountLineNumberToKeep = discountLineNumber;
                                bestUnitDiscountAmount   = unitDiscountAmount;
                                isFirst = false;
                            }
                            else if (unitDiscountAmount > bestUnitDiscountAmount)
                            {
                                discountLineNumberToKeep = discountLineNumber;
                                bestUnitDiscountAmount   = unitDiscountAmount;
                            }
                        }

                        foreach (decimal discountLineNumber in discountLineNumberSet)
                        {
                            if (discountLineNumber != discountLineNumberToKeep)
                            {
                                HashSet <int> itemGroupIndexSetForDiscountLineNumber = null;
                                if (this.DiscountLineNumberToItemGroupIndexSetMap.TryGetValue(discountLineNumber, out itemGroupIndexSetForDiscountLineNumber))
                                {
                                    itemGroupIndexSetForDiscountLineNumber.Remove(itemGroupIndex);
                                }
                            }
                        }

                        discountLineNumberSet.Clear();
                        discountLineNumberSet.Add(discountLineNumberToKeep);
                    }
                }
            }
Ejemplo n.º 3
0
            internal void GetDiscountApplicationHonorQuantityForOneItem(
                List <DiscountApplication> discountApplications,
                int itemGroupIndex,
                HashSet <decimal> discountLineNumberSet,
                DiscountableItemGroup[] discountableItemGroups,
                decimal quantity,
                string discountCodeUsed)
            {
                if (quantity > decimal.Zero && discountLineNumberSet != null && discountLineNumberSet.Count > 0)
                {
                    decimal price = discountableItemGroups[itemGroupIndex].Price;

                    decimal discountLineNumberWithBestDeal = decimal.Zero;
                    decimal unitDiscountAmountBest         = decimal.Zero;

                    // Find the best deal from all discount lines.
                    foreach (decimal discountLineNumber in discountLineNumberSet)
                    {
                        RetailDiscountLine discountLineDefinition = this.DiscountLines[discountLineNumber];
                        decimal            unitDiscountAmount     = GetUnitDiscountAmount(discountLineDefinition, price);

                        if (unitDiscountAmount > unitDiscountAmountBest)
                        {
                            unitDiscountAmountBest         = unitDiscountAmount;
                            discountLineNumberWithBestDeal = discountLineNumber;
                        }
                    }

                    if (unitDiscountAmountBest > decimal.Zero)
                    {
                        RetailDiscountLine  discountLineDefinition = this.DiscountLines[discountLineNumberWithBestDeal];
                        DiscountApplication result = new DiscountApplication(this, applyStandalone: true)
                        {
                            // DISCOUNTPERF: replace list with array?
                            RetailDiscountLines = new List <RetailDiscountLineItem>(1)
                            {
                                new RetailDiscountLineItem(itemGroupIndex, discountLineDefinition)
                            },
                            DiscountCode = discountCodeUsed
                        };
                        result.ItemQuantities.Add(itemGroupIndex, quantity);
                        result.HonorQuantity = true;

                        discountApplications.Add(result);
                    }
                }
            }
Ejemplo n.º 4
0
            /// <summary>
            /// Gets the sort value to use for the specified discount line and item group.
            /// </summary>
            /// <param name="discountLine">The discount line used for this application.</param>
            /// <param name="discountableItemGroup">The item group used for this application.</param>
            /// <returns>The sort value.</returns>
            private static decimal GetSortValue(RetailDiscountLine discountLine, DiscountableItemGroup discountableItemGroup)
            {
                switch ((DiscountOfferMethod)discountLine.DiscountMethod)
                {
                case DiscountOfferMethod.DiscountAmount:
                    return(discountLine.DiscountAmount);

                case DiscountOfferMethod.DiscountPercent:
                    return(discountableItemGroup.Price * discountLine.DiscountPercent);

                case DiscountOfferMethod.OfferPrice:
                    return(discountableItemGroup.Price - discountLine.DiscountLinePercentOrValue - discountLine.OfferPrice);

                default:
                    return(0);
                }
            }
Ejemplo n.º 5
0
            private static RetailDiscountLine ConvertDiscountAndLineToDiscountLine(PeriodicDiscount discountAndLine, DiscountBase discount)
            {
                RetailDiscountLine discountLine = new RetailDiscountLine();

                discountLine.OfferId                             = discountAndLine.OfferId;
                discountLine.DiscountLineNumber                  = discountAndLine.DiscountLineNumber;
                discountLine.ProductId                           = discountAndLine.ProductId;
                discountLine.DistinctProductVariantId            = discountAndLine.DistinctProductVariantId;
                discountLine.DiscountAmount                      = discountAndLine.DiscountAmount;
                discountLine.DiscountLinePercentOrValue          = discountAndLine.DiscountLinePercentOrValue;
                discountLine.DiscountMethod                      = (int)GetLineDiscountOfferMethod(discount.PeriodicDiscountType, discount.DiscountType, discountAndLine.DiscountMethod, discountAndLine.MixAndMatchLineSpecificDiscountType);
                discountLine.DiscountPercent                     = discountAndLine.DiscountPercent;
                discountLine.MixAndMatchLineGroup                = discountAndLine.MixAndMatchLineGroup;
                discountLine.MixAndMatchLineNumberOfItemsNeeded  = discountAndLine.MixAndMatchLineNumberOfItemsNeeded;
                discountLine.MixAndMatchLineSpecificDiscountType = discountAndLine.MixAndMatchLineSpecificDiscountType;
                discountLine.OfferPrice                          = discountAndLine.OfferPrice;
                discountLine.UnitOfMeasureSymbol                 = discountAndLine.UnitOfMeasureSymbol;

                return(discountLine);
            }
Ejemplo n.º 6
0
            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);
            }
Ejemplo n.º 7
0
            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);
            }
Ejemplo n.º 8
0
            /// <summary>
            /// Gets all of the possible applications of this discount to the specified transaction and line items.
            /// </summary>
            /// <param name="transaction">The transaction to consider for discounts.</param>
            /// <param name="discountableItemGroups">The valid sales line items on the transaction to consider.</param>
            /// <param name="remainingQuantities">The remaining quantities of each of the sales lines to consider.</param>
            /// <param name="priceContext">The pricing context to use.</param>
            /// <param name="appliedDiscounts">Applied discount application.</param>
            /// <param name="itemsWithOverlappingDiscounts">Items with overlapping discounts.</param>
            /// <param name="isInterrupted">A flag indicating whether it's interrupted for too many discount applications.</param>
            /// <returns>The possible permutations of line items that this discount can apply to, or an empty collection if this discount cannot apply.</returns>
            public override IEnumerable <DiscountApplication> GetDiscountApplications(
                SalesTransaction transaction,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities,
                PriceContext priceContext,
                IEnumerable <AppliedDiscountApplication> appliedDiscounts,
                HashSet <int> itemsWithOverlappingDiscounts,
                out bool isInterrupted)
            {
                if (discountableItemGroups == null)
                {
                    throw new ArgumentNullException("discountableItemGroups");
                }

                if (remainingQuantities == null)
                {
                    throw new ArgumentNullException("remainingQuantities");
                }

                if (priceContext == null)
                {
                    throw new ArgumentNullException("priceContext");
                }

                List <DiscountApplication> discountAppliations = new List <DiscountApplication>();

                isInterrupted = false;

                // Get the discount code to use for any discount lines, if one is required.
                string discountCodeUsed = this.GetDiscountCodeForDiscount(transaction);

                for (int x = 0; x < discountableItemGroups.Length; x++)
                {
                    DiscountableItemGroup discountableItemGroup = discountableItemGroups[x];
                    HashSet <decimal>     discountLineNumberSet;
                    if (remainingQuantities[x] != 0M && this.ItemGroupIndexToDiscountLineNumberSetMap.TryGetValue(x, out discountLineNumberSet))
                    {
                        bool hasOverlap = this.HasOverlap(x, itemsWithOverlappingDiscounts);

                        foreach (decimal discountLineNumber in discountLineNumberSet)
                        {
                            RetailDiscountLine  line   = this.DiscountLines[discountLineNumber];
                            DiscountApplication result = new DiscountApplication(this, applyStandalone: !hasOverlap)
                            {
                                RetailDiscountLines = new List <RetailDiscountLineItem>(1)
                                {
                                    new RetailDiscountLineItem(x, line)
                                },
                                SortIndex    = GetSortIndexForRetailDiscountLine(line),
                                SortValue    = GetSortValue(line, discountableItemGroup),
                                DiscountCode = discountCodeUsed
                            };
                            result.ItemQuantities.AddRange(GetItemQuantitiesForDiscountApplication(x, discountableItemGroup.Quantity));

                            discountAppliations.Add(result);
                        }
                    }
                }

                return(discountAppliations);
            }
Ejemplo n.º 9
0
            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);
            }
Ejemplo n.º 10
0
            internal static decimal GetUnitDiscountAmount(RetailDiscountLine discountLineDefinition, decimal price)
            {
                bool isFullyCovered = false;

                return(GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered));
            }
Ejemplo n.º 11
0
            public override IEnumerable <DiscountApplication> GetDiscountApplicationsNonOverlappedWithBestDeal(
                SalesTransaction transaction,
                DiscountableItemGroup[] discountableItemGroups,
                decimal[] remainingQuantities,
                PriceContext priceContext,
                IEnumerable <AppliedDiscountApplication> appliedDiscounts,
                HashSet <int> itemsWithOverlappingDiscounts,
                HashSet <int> itemsWithOverlappingDiscountsCompoundedOnly)
            {
                if (priceContext == null)
                {
                    throw new ArgumentNullException("priceContext");
                }

                if (discountableItemGroups == null)
                {
                    throw new ArgumentNullException("discountableItemGroups");
                }

                if (remainingQuantities == null)
                {
                    throw new ArgumentNullException("remainingQuantities");
                }

                if (itemsWithOverlappingDiscounts == null)
                {
                    throw new ArgumentNullException("itemsWithOverlappingDiscounts");
                }

                if (itemsWithOverlappingDiscountsCompoundedOnly == null)
                {
                    throw new ArgumentNullException("itemsWithOverlappingDiscountsCompoundedOnly");
                }

                List <DiscountApplication> discountApplications = new List <DiscountApplication>();
                string discountCodeUsed = this.GetDiscountCodeForDiscount(transaction);

                foreach (KeyValuePair <int, HashSet <decimal> > pair in this.ItemGroupIndexToDiscountLineNumberSetMap)
                {
                    int  itemGroupIndex = pair.Key;
                    bool isOverlappedWithNonCompoundedDiscounts = this.IsItemIndexGroupOverlappedWithNonCompoundedDiscounts(
                        itemGroupIndex,
                        itemsWithOverlappingDiscounts,
                        itemsWithOverlappingDiscountsCompoundedOnly);

                    if (!isOverlappedWithNonCompoundedDiscounts)
                    {
                        HashSet <decimal> discountLineNumberSet = pair.Value;
                        decimal           quantity = remainingQuantities[itemGroupIndex];

                        if (quantity > decimal.Zero && discountLineNumberSet.Count == 1)
                        {
                            RetailDiscountLine  discountLineDefinition = this.DiscountLines[discountLineNumberSet.First()];
                            DiscountApplication result = new DiscountApplication(this, applyStandalone: true, removeItemsFromLookupsWhenApplied: true)
                            {
                                RetailDiscountLines = new List <RetailDiscountLineItem>(1)
                                {
                                    new RetailDiscountLineItem(itemGroupIndex, discountLineDefinition)
                                },
                                DiscountCode = discountCodeUsed
                            };
                            result.ItemQuantities.Add(itemGroupIndex, quantity);

                            discountApplications.Add(result);
                        }
                    }
                }

                return(discountApplications);
            }
 /// <summary>
 /// Initializes a new instance of the RetailDiscountLineItem class, with the values set to the specified parameters.
 /// </summary>
 /// <param name="itemIndex">The ItemIndex value.</param>
 /// <param name="discountLine">The RetailDiscountLine value.</param>
 public RetailDiscountLineItem(int itemIndex, RetailDiscountLine discountLine)
 {
     this.ItemIndex          = itemIndex;
     this.RetailDiscountLine = discountLine;
 }
Ejemplo n.º 13
0
            internal static Dictionary <long, List <DiscountBase> > GetProductOrVariantToDiscountMapLive(
                SalesTransaction transaction,
                PriceContext priceContext,
                IPricingDataAccessor pricingDataManager)
            {
                List <ItemUnit>           items = new List <ItemUnit>();
                Dictionary <string, long> itemIdInventDimIdToProductOrVariantIdMap = new Dictionary <string, long>(StringComparer.OrdinalIgnoreCase);

                foreach (SalesLine salesLine in transaction.PriceCalculableSalesLines)
                {
                    // The map is to look up product or variant id, but not master id if variant id is present.
                    itemIdInventDimIdToProductOrVariantIdMap[GetItemIdInventDimIdKey(salesLine.ItemId, salesLine.InventoryDimensionId)] = salesLine.ProductId;

                    items.Add(new ItemUnit()
                    {
                        ItemId = salesLine.ItemId, VariantInventoryDimensionId = salesLine.InventoryDimensionId, Product = salesLine.MasterProductId == 0 ? salesLine.ProductId : salesLine.MasterProductId, DistinctProductVariant = salesLine.Variant != null ? salesLine.Variant.DistinctProductVariantId : 0, UnitOfMeasure = Discount.GetUnitOfMeasure(salesLine)
                    });
                }

                ReadOnlyCollection <PeriodicDiscount> discountAndLines = GetRetailDiscountsAndLines(items, priceContext, pricingDataManager, QueryResultSettings.AllRecords);
                ISet <long> productVariantMasterIdsInTransaction       = GetProductVariantMasterIdsForTransaction(transaction);

                Dictionary <long, List <DiscountBase> > productDiscountMap   = new Dictionary <long, List <DiscountBase> >();
                Dictionary <string, DiscountBase>       offerIdToDiscountMap = new Dictionary <string, DiscountBase>(StringComparer.OrdinalIgnoreCase);

                foreach (PeriodicDiscount discountAndLine in discountAndLines)
                {
                    if (!PriceContextHelper.MatchCalculationMode(priceContext, discountAndLine.PeriodicDiscountType))
                    {
                        continue;
                    }

                    string key = GetItemIdInventDimIdKey(discountAndLine.ItemId, discountAndLine.InventoryDimensionId);
                    long   productOrVariantId = 0;
                    if (itemIdInventDimIdToProductOrVariantIdMap.TryGetValue(key, out productOrVariantId))
                    {
                        DiscountBase discount = null;

                        if (offerIdToDiscountMap.TryGetValue(discountAndLine.OfferId, out discount))
                        {
                            RetailDiscountLine discountLine = null;
                            if (!discount.DiscountLines.TryGetValue(discountAndLine.DiscountLineNumber, out discountLine))
                            {
                                discountLine = ConvertDiscountAndLineToDiscountLine(discountAndLine, discount);
                                discount.DiscountLines.Add(discountLine.DiscountLineNumber, discountLine);
                            }

                            IList <RetailDiscountLine> discountLines = null;
                            if (discount.ProductOfVariantToDiscountLinesMap.TryGetValue(productOrVariantId, out discountLines))
                            {
                                discountLines.Add(discountLine);
                            }
                            else
                            {
                                discount.ProductOfVariantToDiscountLinesMap[productOrVariantId] = new List <RetailDiscountLine> {
                                    discountLine
                                };
                            }
                        }
                        else
                        {
                            discount = ConvertDiscountAndLineToDiscountBase(discountAndLine);
                            discount.ProductOrVariantIdsInTransaction = productVariantMasterIdsInTransaction;
                            RetailDiscountLine discountLine = ConvertDiscountAndLineToDiscountLine(discountAndLine, discount);
                            discount.DiscountLines.Add(discountLine.DiscountLineNumber, discountLine);
                            offerIdToDiscountMap.Add(discount.OfferId, discount);
                            discount.ProductOfVariantToDiscountLinesMap[productOrVariantId] = new List <RetailDiscountLine> {
                                discountLine
                            };
                        }

                        List <DiscountBase> discounts;
                        if (productDiscountMap.TryGetValue(productOrVariantId, out discounts))
                        {
                            if (!discounts.Where(p => p.OfferId == discount.OfferId).Any())
                            {
                                discounts.Add(discount);
                            }
                        }
                        else
                        {
                            productDiscountMap[productOrVariantId] = new List <DiscountBase>()
                            {
                                discount
                            };
                        }
                    }
                }

                IEnumerable <string> offerIds = offerIdToDiscountMap.Select(p => p.Key);

                if (offerIds.Any())
                {
                    IEnumerable <DiscountCode> discountCodes = pricingDataManager.GetDiscountCodesByOfferIds(offerIds) as IEnumerable <DiscountCode>;

                    foreach (DiscountCode discountCode in discountCodes)
                    {
                        DiscountBase discountBase;
                        if (offerIdToDiscountMap.TryGetValue(discountCode.OfferId, out discountBase))
                        {
                            // Accept both discount code and barcode in retail channel.
                            discountBase.DiscountCodes.Add(discountCode.Code);
                            discountBase.DiscountCodes.Add(discountCode.Barcode);
                        }
                    }

                    IEnumerable <RetailDiscountPriceGroup> discountPriceGroups = pricingDataManager.GetRetailDiscountPriceGroups(new HashSet <string>(offerIds)) as IEnumerable <RetailDiscountPriceGroup>;

                    foreach (RetailDiscountPriceGroup discountPriceGroup in discountPriceGroups)
                    {
                        offerIdToDiscountMap[discountPriceGroup.OfferId].PriceDiscountGroupIds.Add(discountPriceGroup.PriceGroupId);
                    }

                    SetEffectiveDiscountPriorityFromPriceGroups(offerIdToDiscountMap, priceContext);

                    IEnumerable <string> quantityOfferIds = offerIdToDiscountMap.Where(p => p.Value.PeriodicDiscountType == PeriodicDiscountOfferType.MultipleBuy).Select(p => p.Key);

                    if (quantityOfferIds.Any())
                    {
                        IEnumerable <QuantityDiscountLevel> quantityLevels = pricingDataManager.GetMultipleBuyDiscountLinesByOfferIds(quantityOfferIds) as IEnumerable <QuantityDiscountLevel>;

                        foreach (QuantityDiscountLevel quantityLevel in quantityLevels)
                        {
                            DiscountBase discountBase;
                            if (offerIdToDiscountMap.TryGetValue(quantityLevel.OfferId, out discountBase))
                            {
                                MultipleBuyDiscount multipleBuy = discountBase as MultipleBuyDiscount;

                                if (multipleBuy != null)
                                {
                                    multipleBuy.QuantityDiscountLevels.Add(quantityLevel);
                                }
                            }
                        }
                    }

                    IEnumerable <string> mixMatchOfferIds = offerIdToDiscountMap.Where(p => p.Value.PeriodicDiscountType == PeriodicDiscountOfferType.MixAndMatch).Select(p => p.Key);

                    if (mixMatchOfferIds.Any())
                    {
                        IEnumerable <MixAndMatchLineGroup> mixMatchLineGroups = pricingDataManager.GetMixAndMatchLineGroupsByOfferIds(mixMatchOfferIds) as IEnumerable <MixAndMatchLineGroup>;

                        foreach (MixAndMatchLineGroup lineGroup in mixMatchLineGroups)
                        {
                            DiscountBase discountBase;
                            if (offerIdToDiscountMap.TryGetValue(lineGroup.OfferId, out discountBase))
                            {
                                MixAndMatchDiscount mixMatch = discountBase as MixAndMatchDiscount;

                                if (mixMatch != null)
                                {
                                    mixMatch.LineGroupToNumberOfItemsMap.Add(lineGroup.LineGroup, lineGroup.NumberOfItemsNeeded);
                                }
                            }
                        }
                    }

                    IEnumerable <string> thresholdOfferIds = offerIdToDiscountMap.Where(p => p.Value.PeriodicDiscountType == PeriodicDiscountOfferType.Threshold).Select(p => p.Key);

                    if (thresholdOfferIds.Any())
                    {
                        IEnumerable <ThresholdDiscountTier> thresholdTiers = pricingDataManager.GetThresholdTiersByOfferIds(thresholdOfferIds) as IEnumerable <ThresholdDiscountTier>;

                        foreach (ThresholdDiscountTier tier in thresholdTiers)
                        {
                            DiscountBase discountBase;
                            if (offerIdToDiscountMap.TryGetValue(tier.OfferId, out discountBase))
                            {
                                ThresholdDiscount threshold = discountBase as ThresholdDiscount;

                                if (threshold != null)
                                {
                                    threshold.ThresholdDiscountTiers.Add(tier);
                                }
                            }
                        }
                    }
                }

                return(productDiscountMap);
            }