/// <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); }
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); } } }
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); } } }
/// <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); } }
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); }
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 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); }
/// <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); }
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); }
internal static decimal GetUnitDiscountAmount(RetailDiscountLine discountLineDefinition, decimal price) { bool isFullyCovered = false; return(GetUnitDiscountAmountAndCheckWhetherItsFullyCovered(discountLineDefinition, price, out isFullyCovered)); }
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; }
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); }