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); }
/// <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(); }
/// <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(); }
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); }
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); }
/// <summary> /// Converts retail discount data from database to discount object. /// </summary> /// <param name="retailDiscount">Retail discount data from database.</param> /// <returns>Discount object.</returns> /// <remarks>This is private. Exposed as internal for test.</remarks> internal static DiscountBase ConvertRetailDiscountToDiscountBase(RetailDiscount retailDiscount) { DiscountBase discount = null; OfferDiscount offer = null; MixAndMatchDiscount mixAndMatch = null; MultipleBuyDiscount multipleBuy = null; ThresholdDiscount threshold = null; switch (retailDiscount.PeriodicDiscountType) { case PeriodicDiscountOfferType.Offer: offer = new OfferDiscount(retailDiscount.ValidationPeriod); discount = offer; break; case PeriodicDiscountOfferType.MixAndMatch: mixAndMatch = new MixAndMatchDiscount(retailDiscount.ValidationPeriod); mixAndMatch.DealPriceValue = retailDiscount.MixAndMatchDealPrice; mixAndMatch.DiscountAmountValue = retailDiscount.MixAndMatchDiscountAmount; mixAndMatch.DiscountPercentValue = retailDiscount.MixAndMatchDiscountPercent; mixAndMatch.NumberOfLeastExpensiveLines = retailDiscount.MixAndMatchNumberOfLeastExpensiveLines; mixAndMatch.LeastExpensiveMode = retailDiscount.LeastExpensiveMode; mixAndMatch.NumberOfTimesApplicable = retailDiscount.MixAndMatchNumberOfTimeApplicable; foreach (RetailDiscountLine mixMatchLine in retailDiscount.DiscountLines) { if (!mixAndMatch.LineGroupToNumberOfItemsMap.ContainsKey(mixMatchLine.MixAndMatchLineGroup)) { mixAndMatch.LineGroupToNumberOfItemsMap.Add(mixMatchLine.MixAndMatchLineGroup, mixMatchLine.MixAndMatchLineNumberOfItemsNeeded); } } discount = mixAndMatch; break; case PeriodicDiscountOfferType.MultipleBuy: multipleBuy = new MultipleBuyDiscount(retailDiscount.ValidationPeriod); multipleBuy.QuantityDiscountLevels.AddRange(retailDiscount.MultibuyQuantityTiers); discount = multipleBuy; break; case PeriodicDiscountOfferType.Threshold: threshold = new ThresholdDiscount(retailDiscount.ValidationPeriod); threshold.ShouldCountNonDiscountItems = retailDiscount.ShouldCountNonDiscountItems != 0; threshold.ThresholdDiscountTiers.AddRange(retailDiscount.ThresholdDiscountTiers); discount = threshold; break; } if (discount != null) { discount.IsCategoryToProductOrVariantIdsMapSet = false; discount.OfferId = retailDiscount.OfferId; discount.OfferName = retailDiscount.Name; discount.PeriodicDiscountType = retailDiscount.PeriodicDiscountType; discount.IsDiscountCodeRequired = retailDiscount.IsDiscountCodeRequired; discount.ConcurrencyMode = retailDiscount.ConcurrencyMode; discount.PricingPriorityNumber = retailDiscount.PricingPriorityNumber; discount.CurrencyCode = retailDiscount.CurrencyCode; discount.DateValidationPeriodId = retailDiscount.ValidationPeriodId; discount.DateValidationType = (DateValidationType)retailDiscount.DateValidationType; discount.DiscountType = GetDiscountMethodType(discount.PeriodicDiscountType, retailDiscount.DiscountType); discount.ValidFrom = retailDiscount.ValidFromDate; discount.ValidTo = retailDiscount.ValidToDate; foreach (RetailDiscountLine discountLine in retailDiscount.DiscountLines) { discountLine.DiscountMethod = (int)GetLineDiscountOfferMethod(discount.PeriodicDiscountType, discount.DiscountType, discountLine.DiscountMethod, discountLine.MixAndMatchLineSpecificDiscountType); discount.DiscountLines.Add(discountLine.DiscountLineNumber, discountLine); } foreach (RetailDiscountPriceGroup priceGroup in retailDiscount.PriceGroups) { discount.PriceDiscountGroupIds.Add(priceGroup.PriceGroupId); } foreach (DiscountCode discountCode in retailDiscount.DiscountCodes) { discount.DiscountCodes.Add(discountCode.Code); discount.DiscountCodes.Add(discountCode.Barcode); } } return(discount); }