/// <summary> /// Sets default Sales Tax Group (STG) for the cart based on a channel tax configuration for non-return transaction. /// </summary> /// <param name="context">The request context.</param> /// <param name="salesTransaction">Current transaction.</param> private static void SetSalesTaxGroupOnNonReturn(RequestContext context, SalesTransaction salesTransaction) { ThrowIf.Null(context, "context"); ThrowIf.Null(salesTransaction, "salesTransaction"); Channel channel; long currentChannelId = context.GetPrincipal().ChannelId; string channelTaxGroup; if (context.GetChannelConfiguration().ChannelType == RetailChannelType.RetailStore) { OrgUnit store = context.GetOrgUnit(); channel = store; channelTaxGroup = store.TaxGroup; } else { var getChannelByIdDataRequest = new GetChannelByIdDataRequest(currentChannelId); channel = context.Runtime.Execute <SingleEntityDataServiceResponse <Channel> >(getChannelByIdDataRequest, context).Entity; channelTaxGroup = string.Empty; } Address headerAddress = Address.IsNullOrEmpty(salesTransaction.ShippingAddress) ? null : salesTransaction.ShippingAddress; // Header charges follows header when taxed SalesTaxGroupPicker headerPicker = SalesTaxGroupPicker.Create( channel, context, headerAddress, salesTransaction.DeliveryMode, salesTransaction.StoreId ?? string.Empty, salesTransaction.InventoryLocationId, salesTransaction.CustomerId); FillChargeLinesSalesTaxGroup(context, salesTransaction.ChargeLines, headerPicker.SalesTaxGroup, channelTaxGroup); // items needed to retrieve ITG information from Product repo for each sales line IEnumerable <Item> items = GetItemsForSalesLines(context, salesTransaction.ActiveSalesLines); Dictionary <string, Item> itemsByItemId = items.ToDictionary(i => i.ItemId, i => i, StringComparer.OrdinalIgnoreCase); // Consider active lines for taxation purpose only. foreach (SalesLine salesLine in salesTransaction.ActiveSalesLines) { // On Return By Receipt carts, we don't change tax groups as they come populated from the headquarters // Tax groups have been set during sales transaction creation and could be from a different store than the one the return is being made. if (!salesLine.IsReturnByReceipt) { Address shippingAddress = Address.IsNullOrEmpty(salesLine.ShippingAddress) ? headerAddress : salesLine.ShippingAddress; SalesTaxGroupPicker linePicker = SalesTaxGroupPicker.Create( channel, context, shippingAddress, salesLine.DeliveryMode, salesLine.FulfillmentStoreId ?? string.Empty, salesTransaction.InventoryLocationId, salesTransaction.CustomerId); salesLine.SalesTaxGroupId = linePicker.SalesTaxGroup; salesLine.OriginalSalesTaxGroupId = salesLine.SalesTaxGroupId; Item cartItem; // case 1: item without item tax group (set to null), regular cash and carry or sales order // -> set ITG to Product ITG // case 2: customer order recall with tax exempt, sales line ITG // is set to "" (empty string) or carries any other value // -> keep ITG already set on sales line if (salesLine.ItemId != null && salesLine.ItemTaxGroupId == null && itemsByItemId.TryGetValue(salesLine.ItemId, out cartItem)) { salesLine.ItemTaxGroupId = cartItem.ItemTaxGroupId; salesLine.OriginalItemTaxGroupId = salesLine.ItemTaxGroupId; } FillChargeLinesSalesTaxGroup(context, salesLine.ChargeLines, linePicker.SalesTaxGroup, channelTaxGroup); } } }
/// <summary> /// Processes the void cart lines request. /// </summary> /// <param name="context">The context.</param> /// <param name="request">The request.</param> /// <param name="salesTransaction">Sales transaction.</param> private static void ProcessVoidCartLinesRequest(RequestContext context, SaveCartLinesRequest request, SalesTransaction salesTransaction) { Dictionary <string, SalesLine> salesLinesById = salesTransaction.SalesLines.ToDictionary(sl => sl.LineId, sl => sl); // Keeps track of the enabled (unvoided) sales lines. var enabledSalesLines = new List <SalesLine>(); foreach (CartLine cartLine in request.CartLines) { var salesLine = salesLinesById[cartLine.LineId]; if (salesTransaction.CartType == CartType.CustomerOrder && salesTransaction.CustomerOrderMode != CustomerOrderMode.CustomerOrderCreateOrEdit && salesTransaction.CustomerOrderMode != CustomerOrderMode.QuoteCreateOrEdit && cartLine.IsVoided) { string errorMessage = "Cart line can be voided only at the time of CustomerOrderCreateOrEdit or QuoteCreateOrEdit."; throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidCustomerOrderModeForVoidProducts, errorMessage); } if ((cartLine.IsCustomerAccountDeposit || salesTransaction.CartType == CartType.AccountDeposit) && cartLine.IsVoided) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CustomerAccountDepositCannotBeVoided, "Cart line cannot be voided for customer account deposit transaction."); } if (!cartLine.IsVoided && salesLine.IsVoided) { // Unvoid if (cartLine.IsGiftCardLine) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_GiftCardLineVoidReversalNotSupported, "Gift card line cannot be unvoided."); } // Unvoid the sales line. salesLine.IsVoided = false; // Unvoid the linked products' sales lines if any. if (salesLine.LineIdsLinkedProductMap.Any()) { foreach (string lineId in salesLine.LineIdsLinkedProductMap.Keys) { if (salesLinesById[lineId] != null) { salesLinesById[lineId].IsVoided = false; enabledSalesLines.Add(salesLinesById[lineId]); } else { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ObjectNotFound, string.Format("Sales line of the linked product with id : {0} was not found.", lineId)); } } } // Add the new line to the collection for attribute updates. enabledSalesLines.Add(salesLine); // Perform additional side-effect logic here (i.e. issue gift cart etc.) } else { // Process reason code lines on the cart line. ReasonCodesWorkflowHelper.AddOrUpdateReasonCodeLinesOnSalesLine(salesLine, cartLine, salesTransaction.Id); // Calculate the required reason codes for voiding sales lines. ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnSalesLine(context, salesTransaction, salesLine, ReasonCodeSourceType.VoidItem); // Void the sales line. salesLine.IsVoided = true; // Void the linked products' sales lines if any. if (salesLine.LineIdsLinkedProductMap.Any()) { foreach (string lineId in salesLine.LineIdsLinkedProductMap.Keys) { if (salesLinesById[lineId] != null) { salesLinesById[lineId].IsVoided = true; } else { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_ObjectNotFound, string.Format("Sales line of the linked product with id : {0} was not found.", lineId)); } } } // Void gift card lines. if (salesLine.IsGiftCardLine) { GiftCardWorkflowHelper.VoidGiftCardOperation(context, salesTransaction, salesLine.GiftCardId, salesLine.GiftCardCurrencyCode, salesLine.GiftCardOperation, salesLine.TotalAmount); } CartWorkflowHelper.LogAuditEntry( context, "SaveCartLinesRequestHandler.ProcessVoidCartLinesRequest", string.Format("Line item voided: {0}, #: {1}", salesLine.Description, salesLine.LineNumber)); } } // Set default attributes from order header if there are any enabled sales lines. if (enabledSalesLines.Any()) { CartWorkflowHelper.SetDefaultDataOnSalesLines(context, salesTransaction, enabledSalesLines); } }
/// <summary> /// Saves the cart lines based on the request operation type. /// </summary> /// <param name="request">The request.</param> /// <returns>The save cart line response.</returns> protected override SaveCartLinesResponse Process(SaveCartLinesRequest request) { ThrowIf.Null(request, "request"); // Load sales transaction. SalesTransaction transaction = CartWorkflowHelper.LoadSalesTransaction(request.RequestContext, request.Cart.Id); if (transaction == null) { throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotFound, request.Cart.Id); } // Load return sales transaction. // Get the returned sales transaction if the cart contains a return line. SalesTransaction returnTransaction = CartWorkflowHelper.LoadSalesTransactionForReturn(request.RequestContext, request.Cart, transaction, request.OperationType); transaction.IsReturnByReceipt = returnTransaction != null; // Get the products in the cart lines IDictionary <long, SimpleProduct> productsByRecordId = CartWorkflowHelper.GetProductsInCartLines(this.Context, request.Cart.CartLines); // Validate the save cart lines request against the sales transaction. CartWorkflowHelper.ValidateSaveCartLinesRequest(this.Context, request, transaction, returnTransaction, productsByRecordId); // Process the request. switch (request.OperationType) { case TransactionOperationType.Create: ProcessCreateCartLinesRequest(this.Context, request, transaction, returnTransaction, productsByRecordId); break; case TransactionOperationType.Update: ProcessUpdateCartLinesRequest(this.Context, request, transaction); break; case TransactionOperationType.Delete: ProcessDeleteCartLinesRequest(request, transaction); break; case TransactionOperationType.Void: ProcessVoidCartLinesRequest(this.Context, request, transaction); break; default: throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, string.Format("Operation {0} is not supported on cart lines.", request.OperationType)); } // Recalculates the sales transaction after processing the request. RecalculateSalesTransaction(this.Context, request, transaction); // Validate price on sales line after calculations CartWorkflowHelper.ValidateSalesLinePrice(this.Context, transaction, productsByRecordId); // Save the sales transaction. CartWorkflowHelper.SaveSalesTransaction(this.Context, transaction); Cart cart = CartWorkflowHelper.ConvertToCart(this.Context, transaction); CartWorkflowHelper.RemoveHistoricalTenderLines(cart); return(new SaveCartLinesResponse(cart)); }
/// <summary> /// Initializes a new instance of the <see cref="TaxCode"/> class. /// </summary> /// <param name="context">The request context.</param> /// <param name="lineItem">The taxable line item.</param> /// <param name="interval">The tax code interval.</param> /// <param name="taxContext">Tax context.</param> /// <param name="transaction">Current transaction.</param> public TaxCode(RequestContext context, TaxableItem lineItem, TaxCodeInterval interval, TaxContext taxContext, SalesTransaction transaction) { if (context == null) { throw new ArgumentNullException("context"); } if (interval == null) { throw new ArgumentNullException("interval"); } this.Code = interval.TaxCode; this.TaxableEntity = lineItem; this.TaxGroup = interval.TaxItemGroup; this.Currency = interval.TaxCurrencyCode; this.Exempt = interval.IsTaxExempt; this.TaxBase = (TaxBase)interval.TaxBase; this.TaxLimitBase = (TaxLimitBase)interval.TaxLimitBase; this.TaxCalculationMethod = (TaxCalculationMode)interval.TaxCalculationMethod; this.TaxOnTax = interval.TaxOnTax; this.Unit = interval.TaxUnit; this.RoundingOff = interval.TaxRoundOff; this.RoundingOffType = Rounding.ConvertRoundOffTypeToRoundingMethod(interval.TaxRoundOffType); this.CollectLimitMax = interval.TaxMaximum; this.CollectLimitMin = interval.TaxMinimum; this.TaxGroupRounding = interval.IsGroupRounding; this.IsTaxIncludedInTax = interval.IsTaxIncludedInTax; this.TaxIntervals = new Collection <TaxInterval>(new List <TaxInterval>(1)); // should this be removed in favor of intervals? this.Value = interval.TaxValue; this.TaxLimitMin = interval.TaxLimitMinimum; this.TaxLimitMax = interval.TaxLimitMaximum; this.RequestContext = context; this.Transaction = transaction; this.TaxIntervals.Add(new TaxInterval(interval.TaxLimitMinimum, interval.TaxLimitMaximum, interval.TaxValue)); this.TaxContext = taxContext; }
/// <summary> /// Processes the delete cart lines request. /// </summary> /// <param name="request">The request.</param> /// <param name="transaction">Current transaction.</param> private static void ProcessDeleteCartLinesRequest(SaveCartLinesRequest request, SalesTransaction transaction) { Dictionary <string, SalesLine> salesLinesById = transaction.SalesLines.ToDictionary(sl => sl.LineId, sl => sl); foreach (CartLine cartLine in request.CartLines) { var salesLine = salesLinesById[cartLine.LineId]; // Removing the linked products' sales lines if any. if (salesLine.LineIdsLinkedProductMap.Any()) { foreach (string lineId in salesLine.LineIdsLinkedProductMap.Keys) { transaction.SalesLines.Remove(salesLinesById[lineId]); } } // Removing the reference to the linked product from the parent product sales line if the linked product is removed from cart. if (!string.IsNullOrWhiteSpace(salesLine.LinkedParentLineId)) { var parentLine = transaction.SalesLines.Single(i => i.LineId.Equals(salesLinesById[salesLine.LinkedParentLineId].LineId)); parentLine.LineIdsLinkedProductMap.Remove(salesLine.LineId); } transaction.SalesLines.Remove(salesLine); } }
private static void SetSalesLineInventoryForShipping(RequestContext context, SalesTransaction transaction, IEnumerable <SalesLine> salesLines, Dictionary <string, decimal> salesLineInventoryQuantities) { ThrowIf.Null(context, "context"); ThrowIf.Null(salesLines, "salesLines"); NetTracer.Information("ItemAvailabilityHelper.SetSalesLineInventoryForShipping(): TransactionId = {0}, CustomerId = {1}", transaction.Id, transaction.CustomerId); // Calculate total converted quantities. Dictionary <ItemVariantInventoryDimension, ItemQuantity> itemQuantities = GetSalesLineItemQuantities(salesLines, salesLineInventoryQuantities); // Get item availablities by total item quantities. IEnumerable <ItemAvailability> itemAvailabilities = ChannelAvailabilityHelper.GetAllItemAvailablitiesByItemQuantities(context, transaction.CustomerId, itemQuantities.Values, GetMaxLinesPerItem(salesLines)); // Set inventory for sales lines. SetSalesLineInventoryForShipping(context, salesLines, itemAvailabilities, salesLineInventoryQuantities); }
/// <summary> /// Updates the sales transaction with the threshold promotion if applicable. /// </summary> /// <param name="existingTransaction">Existing transaction.</param> /// <param name="tempSalesTransaction">Copy of existing transaction.</param> /// <param name="context">The request context.</param> /// <param name="salesLineIndex">The sales line under consideration.</param> /// <param name="cartPromotionLines">The object with the cart promotion lines.</param> /// <param name="thresholdDiscount">The threshold discount line under consideration.</param> /// <param name="tiers">The tiers for the threshold discount.</param> private static void GetThresholdDiscounts( SalesTransaction existingTransaction, SalesTransaction tempSalesTransaction, RequestContext context, int salesLineIndex, Collection <string> cartPromotionLines, DiscountLine thresholdDiscount, IEnumerable <ThresholdDiscountTier> tiers) { // Find all the sales lines with the same offer. List <SalesLine> salesLinesWithOffer = tempSalesTransaction.SalesLines.Where(j => j.DiscountLines.Any(k => k.OfferId.Equals(thresholdDiscount.OfferId))).ToList(); decimal totalAmount = salesLinesWithOffer.Select(j => j.GrossAmount).Sum(); decimal currentQuantity = tempSalesTransaction.SalesLines[salesLineIndex].Quantity; // Find the minimum threshold amount required to hit a discount among all the tiers for this offer. IEnumerable <ThresholdDiscountTier> tiersForCurrentAmtOffer = tiers.Where(j => j.OfferId.Equals(thresholdDiscount.OfferId) && j.AmountThreshold > totalAmount).OrderBy(l => l.AmountThreshold); ThresholdDiscountTier tier = tiersForCurrentAmtOffer.Any() ? tiersForCurrentAmtOffer.First() : null; if (tier != null) { // Add that amount difference to the first item that has this offer in the cart by increasing its quantity and see if this discount applies after applying concurrency rules. existingTransaction.SalesLines[salesLineIndex].Quantity = Math.Ceiling(tempSalesTransaction.SalesLines[salesLineIndex].Quantity * (tier.AmountThreshold - totalAmount + tempSalesTransaction.SalesLines[salesLineIndex].GrossAmount) / (tempSalesTransaction.SalesLines[salesLineIndex].GrossAmount / tempSalesTransaction.SalesLines[salesLineIndex].Quantity)); CartWorkflowHelper.Calculate(context, existingTransaction, CalculationModes.All); DiscountLine isApplied = existingTransaction.SalesLines[salesLineIndex].DiscountLines.Where(j => j.OfferId.Equals(thresholdDiscount.OfferId)).SingleOrDefault(); if (isApplied != null) { var getItemsRequest = new GetItemsDataRequest(salesLinesWithOffer.Select(j => j.ItemId)) { QueryResultSettings = new QueryResultSettings(new ColumnSet("NAME"), PagingInfo.AllRecords) }; var getItemsResponse = context.Runtime.Execute <GetItemsDataResponse>(getItemsRequest, context); ReadOnlyCollection <Item> items = getItemsResponse.Items; StringBuilder buffer = new StringBuilder(); foreach (Item item in items.ToList()) { buffer.Append(item.Name).Append(", "); } buffer.Remove(buffer.Length - 2, 1); if (tier.DiscountMethod == ThresholdDiscountMethod.AmountOff) { thresholdDiscount.OfferName = string.Format(CultureInfo.CurrentUICulture, Resources.ThresholdDiscountPricePromotion, buffer, Math.Round(tier.AmountThreshold, 2), Math.Round(tier.DiscountValue, 2)); } else { thresholdDiscount.OfferName = string.Format(CultureInfo.CurrentUICulture, Resources.ThresholdDiscountPercentagePromotion, buffer, Math.Round(tier.AmountThreshold, 2), Math.Round(tier.DiscountValue, 2)); } cartPromotionLines.Add(thresholdDiscount.OfferName); } } existingTransaction.SalesLines[salesLineIndex].Quantity = currentQuantity; CartWorkflowHelper.Calculate(context, existingTransaction, CalculationModes.All); foreach (SalesLine salesLineWithOffer in salesLinesWithOffer) { salesLineWithOffer.DiscountLines.Remove(salesLineWithOffer.DiscountLines.Where(k => k.OfferId == thresholdDiscount.OfferId).SingleOrDefault()); } }
internal static void PopulateProductIds(IPricingDataAccessor pricingDataManager, PriceContext priceContext, SalesTransaction transaction) { PopulateProductIds(pricingDataManager, priceContext, transaction.PriceCalculableSalesLines); }
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> /// The calculation of the total customer discount. /// </summary> /// <param name="tradeAgreements">Trade agreement collection to calculate on. If null, uses the pricing data manager to find agreements.</param> /// <param name="retailTransaction">The retail transaction which needs total discounts.</param> /// <returns> /// The retail transaction. /// </returns> public SalesTransaction CalcTotalCustomerDiscount( List <TradeAgreement> tradeAgreements, SalesTransaction retailTransaction) { if (tradeAgreements != null && tradeAgreements.Any()) { decimal totalAmount = 0; // Find the total amount as a basis for the total discount // Consider calculable lines only. Ignore voided or return-by-receipt lines. var clonedTransaction = retailTransaction.Clone <SalesTransaction>(); foreach (var clonedSalesLine in clonedTransaction.PriceCalculableSalesLines) { if (this.IsTotalDiscountAllowed(clonedSalesLine.ItemId)) { SalesLineTotaller.CalculateLine(clonedTransaction, clonedSalesLine, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); totalAmount += clonedSalesLine.NetAmountWithAllInclusiveTax; } } decimal absTotalAmount = Math.Abs(totalAmount); // Find the total discounts. PriceDiscountType relation = PriceDiscountType.EndDiscountSales; // Total sales discount - 7 PriceDiscountItemCode itemCode = PriceDiscountItemCode.AllItems; // All items - 2 PriceDiscountAccountCode accountCode = 0; string itemRelation = string.Empty; decimal percent1 = 0m; decimal percent2 = 0m; decimal discountAmount = 0m; ProductVariant dimension = new ProductVariant(); int idx = 0; while (idx < /* Max(PriceDiscAccountCode) */ 3) { // Check discounts for Store Currency accountCode = (PriceDiscountAccountCode)idx; string accountRelation = string.Empty; if (accountCode == PriceDiscountAccountCode.Customer) { accountRelation = retailTransaction.CustomerId; } else if (accountCode == PriceDiscountAccountCode.CustomerGroup) { accountRelation = this.priceContext.CustomerTotalPriceGroup; } accountRelation = accountRelation ?? string.Empty; // Only get Active discount combinations if (this.discountParameters.Activation(relation, (PriceDiscountAccountCode)accountCode, (PriceDiscountItemCode)itemCode)) { var priceDiscTable = Discount.GetPriceDiscData(tradeAgreements, relation, itemRelation, accountRelation, itemCode, accountCode, absTotalAmount, this.priceContext, dimension, false); foreach (TradeAgreement row in priceDiscTable) { percent1 += row.PercentOne; percent2 += row.PercentTwo; discountAmount += row.Amount; if (!row.ShouldSearchAgain) { idx = 3; } } } idx++; } decimal totalPercentage = DiscountLine.GetCompoundedPercentage(percent1, percent2); if (discountAmount != decimal.Zero) { this.AddTotalDiscAmountLines(retailTransaction, DiscountLineType.CustomerDiscount, discountAmount); } if (totalPercentage != 0) { // Update the sale items. // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var saleItem in retailTransaction.PriceCalculableSalesLines) { if (this.IsTotalDiscountAllowed(saleItem.ItemId)) { DiscountLine discountItem = GetCustomerDiscountItem(saleItem, CustomerDiscountType.TotalDiscount, DiscountLineType.CustomerDiscount); discountItem.Percentage = totalPercentage; } } } } return(retailTransaction); }
/// <summary> /// Calculates all of the discount lines for the transactions. /// </summary> /// <param name="pricingDataManager">Provides data access to the calculation.</param> /// <param name="transaction">The sales transaction.</param> /// <param name="shouldTotalLines">True if discount lines should be totaled for each line. False if they should be left as raw discount lines.</param> /// <param name="priceContext">Price context.</param> /// <remarks>Each sales line will have a collection of DiscountLines and a net discount total in DiscountAmount property (if totaling is enabled).</remarks> public static void CalculateDiscountsForLines( IPricingDataAccessor pricingDataManager, SalesTransaction transaction, bool shouldTotalLines, PriceContext priceContext) { if (transaction == null) { throw new ArgumentNullException("transaction"); } List <SalesLine> existingSalesLines = new List <SalesLine>(); List <SalesLine> newSalesLines = new List <SalesLine>(); if (priceContext.CalculateForNewSalesLinesOnly) { foreach (SalesLine salesLine in transaction.SalesLines) { if (priceContext.NewSalesLineIdSet.Contains(salesLine.LineId)) { newSalesLines.Add(salesLine); } else { existingSalesLines.Add(salesLine); } } // Calculate for new sales lines only. transaction.SalesLines.Clear(); transaction.SalesLines.AddRange(newSalesLines); } Discount discountEngine = InitializeDiscountEngine(pricingDataManager); discountEngine.CalculateDiscount(pricingDataManager, transaction, priceContext); if (priceContext.CalculateForNewSalesLinesOnly) { // Add existing sales lines back after calculating for new sales lines only. List <SalesLine> newSalesLinesFromCalculation = new List <SalesLine>(); foreach (SalesLine salesLine in transaction.SalesLines) { if (!priceContext.NewSalesLineIdSet.Contains(salesLine.LineId)) { newSalesLinesFromCalculation.Add(salesLine); } } transaction.SalesLines.Clear(); transaction.SalesLines.AddRange(existingSalesLines); transaction.SalesLines.AddRange(newSalesLines); transaction.SalesLines.AddRange(newSalesLinesFromCalculation); } if (shouldTotalLines) { // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var salesLine in transaction.PriceCalculableSalesLines) { SalesLineTotaller.CalculateLine(transaction, salesLine, d => priceContext.CurrencyAndRoundingHelper.Round(d)); // technically rounding rule should be "sales rounding" rule } } }
/// <summary> /// This method will distribute the amountToDiscount across all the sale items in the transaction /// proportionally except for the line item with the largest amount. The remainder will be distributed /// to the line item with the largest amount to ensure the amount to discount is exactly applied. /// This method currently works for either the customer discount or when the total discount button is applied. /// </summary> /// <param name="transaction">The transaction receiving total discount lines.</param> /// <param name="discountType">Whether this discount is for a customer or for the total discount item.</param> /// <param name="amountToDiscount">The amount to discount the transaction.</param> private void AddTotalDiscAmountLines( SalesTransaction transaction, DiscountLineType discountType, decimal amountToDiscount) { decimal totalAmtAvailableForDiscount = decimal.Zero; // Build a list of the discountable items with the largest value item last. // Consider calculable lines only. Ignore voided or return-by-receipt lines. var discountableSaleItems = (from s in transaction.PriceCalculableSalesLines where s.IsEligibleForDiscount() && s.Quantity > 0 && PriceContextHelper.IsDiscountAllowed(this.priceContext, s.ItemId) orderby Math.Abs(s.NetAmount), s.LineId select s).ToList(); // Iterate through all non voided items whether we are going to discount or not so that they get added // back to the totals // Consider calculable lines only. Ignore voided or return-by-receipt lines. foreach (var saleItem in transaction.PriceCalculableSalesLines) { // We can clear the discount line for total discount because a total manual amount discount // will override a total manual percent discount, whereas customer discount can have both // amount and percentage applied simultaneously. if (discountType == DiscountLineType.ManualDiscount) { Discount.ClearManualDiscountLinesOfType(saleItem, ManualDiscountType.TotalDiscountAmount); Discount.ClearManualDiscountLinesOfType(saleItem, ManualDiscountType.TotalDiscountPercent); } SalesLineTotaller.CalculateLine(transaction, saleItem, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); if (saleItem.IsEligibleForDiscount() && saleItem.Quantity > 0) { // Calculate the total amount that is available for discount totalAmtAvailableForDiscount += Math.Abs(saleItem.NetAmountWithAllInclusiveTax); } } // Calculate the percentage (as a fraction) that we should attempt to discount each discountable item // to reach the total. decimal discountFactor = totalAmtAvailableForDiscount != decimal.Zero ? (amountToDiscount / totalAmtAvailableForDiscount) : decimal.Zero; decimal totalAmtDistributed = decimal.Zero; // Iterate through all discountable items. foreach (var saleItem in discountableSaleItems) { decimal amountToDiscountForThisItem = decimal.Zero; if (saleItem != discountableSaleItems.Last()) { // for every item except for the last in the list (which will have the largest value) // discount by the rounded amount that is closest to the percentage desired for the transaction decimal itemPrice = saleItem.NetAmount; amountToDiscountForThisItem = this.priceContext.CurrencyAndRoundingHelper.Round(discountFactor * Math.Abs(itemPrice)); totalAmtDistributed += amountToDiscountForThisItem; } else { // Discount the last item by the remainder to ensure that the exact desired discount is applied amountToDiscountForThisItem = amountToDiscount - totalAmtDistributed; } DiscountLine discountItem; if (amountToDiscountForThisItem != decimal.Zero) { if (discountType == DiscountLineType.ManualDiscount) { // Add a new total discount item discountItem = new DiscountLine(); discountItem.DiscountLineType = DiscountLineType.ManualDiscount; discountItem.ManualDiscountType = ManualDiscountType.TotalDiscountAmount; saleItem.DiscountLines.Add(discountItem); } else { // for customer discounts we need to either update the existing one, or add a new one. discountItem = GetCustomerDiscountItem(saleItem, CustomerDiscountType.TotalDiscount, DiscountLineType.CustomerDiscount); } discountItem.Amount = saleItem.Quantity != 0 ? amountToDiscountForThisItem / saleItem.Quantity : amountToDiscountForThisItem; } SalesLineTotaller.CalculateLine(transaction, saleItem, d => this.priceContext.CurrencyAndRoundingHelper.Round(d)); } }
public void Update(string realmId, SalesTransaction entity) { var dataService = GetDataService(realmId); dataService.Update(entity); }
/// <summary> /// Adds or updates the reason code lines on the sales transaction. /// </summary> /// <param name="request">The save tender line request.</param> /// <param name="salesTransaction">The sales transaction.</param> private void AddOrUpdateReasonCodeLinesOnTransaction(SaveTenderLineRequest request, SalesTransaction salesTransaction) { // Add or update any incoming reason codes on the transaction. if (request.ReasonCodeLines != null && request.ReasonCodeLines.Any()) { ReasonCodesWorkflowHelper.AddOrUpdateReasonCodeLinesOnTransaction( salesTransaction, new Cart { Id = request.CartId, ReasonCodeLines = request.ReasonCodeLines.ToList() }); } }
/// <summary> /// Needs the inventory checking. /// </summary> /// <param name="channelConfiguration">The channel configuration.</param> /// <param name="transaction">The transaction.</param> /// <returns><c>True</c> if the inventory checking is required, <c>false</c> otherwise.</returns> private static bool NeedInventoryChecking(ChannelConfiguration channelConfiguration, SalesTransaction transaction) { return((channelConfiguration.ChannelType != RetailChannelType.RetailStore) && (transaction.CartType != CartType.AccountDeposit)); }
/// <summary> /// Recalculates the sales transaction. /// </summary> /// <param name="context">The context.</param> /// <param name="request">The request.</param> /// <param name="transaction">Current transaction.</param> private static void RecalculateSalesTransaction(RequestContext context, SaveCartLinesRequest request, SalesTransaction transaction) { // Sets the wharehouse id and invent location id for each line ItemAvailabilityHelper.SetSalesLineInventory(context, transaction); // Calculate totals and saves the sales transaction CartWorkflowHelper.Calculate(context, transaction, request.CalculationModes); // Calculate the required reason codes after the price calculation ReasonCodesWorkflowHelper.CalculateRequiredReasonCodes(context, transaction, ReasonCodeSourceType.None); }
private static void SetSalesLineInventoryForPickup(RequestContext context, SalesTransaction transaction, IEnumerable <SalesLine> salesLines, Dictionary <string, decimal> salesLineInventoryQuantities) { ThrowIf.Null(context, "context"); ThrowIf.Null(salesLines, "salesLines"); NetTracer.Information("ItemAvailabilityHelper.SetSalesLineInventoryForPickup(): TransactionId = {0}, CustomerId = {1}", transaction.Id, transaction.CustomerId); // Get item availablities by item warehouses. IEnumerable <ItemAvailability> itemAvailabilities = ChannelAvailabilityHelper.GetItemAvailabilitiesByItemWarehouses(context, salesLines.Select(salesLine => salesLine.GetItemWarehouse())); // Set inventory for sales lines. SetSalesLineInventoryForPickup(context, salesLines, itemAvailabilities, salesLineInventoryQuantities); }
/// <summary> /// Processes the create cart lines request. /// </summary> /// <param name="context">The context.</param> /// <param name="request">The request.</param> /// <param name="transaction">Current transaction.</param> /// <param name="returnTransaction">Return transaction.</param> /// <param name="productByRecordId">The mapping of products by record identifier.</param> private static void ProcessCreateCartLinesRequest(RequestContext context, SaveCartLinesRequest request, SalesTransaction transaction, SalesTransaction returnTransaction, IDictionary <long, SimpleProduct> productByRecordId) { // Create the new cart lines. var salesLines = new List <SalesLine>(); foreach (CartLine cartLine in request.CartLines) { var salesLine = new SalesLine(); if (!cartLine.LineData.IsReturnByReceipt) { // Creates a sales line base on the cart line salesLine.CopyPropertiesFrom(cartLine.LineData); // Set ItemId and VariantInventDimId of the sales line, if the cart line is constructed from listing. if (cartLine.LineData.IsProductLine) { long id = cartLine.LineData.ProductId; SimpleProduct product = productByRecordId[id]; salesLine.ItemId = product.ItemId; salesLine.ProductId = id; salesLine.InventoryDimensionId = product.InventoryDimensionId; salesLine.Variant = ProductVariant.ConvertFrom(product); } } else { // Creates a sales line base on the retuned sales line var returnedSalesLine = CartWorkflowHelper.GetSalesLineByNumber(returnTransaction, cartLine.LineData.ReturnLineNumber); CartWorkflowHelper.SetSalesLineBasedOnReturnedSalesLine(salesLine, returnedSalesLine, returnTransaction, cartLine.LineData.Quantity); // Calculate required reason code lines for return transaction. ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnSalesLine(context, transaction, salesLine, ReasonCodeSourceType.ReturnItem); } // Assign sales line Id. Using format 'N' to remove dashes from the GUID. salesLine.LineId = Guid.NewGuid().ToString("N"); // Add sales lines to collection. salesLines.Add(salesLine); } // Set default attributes from order header. CartWorkflowHelper.SetDefaultDataOnSalesLines(context, transaction, salesLines); // Add sales lines to transation. transaction.SalesLines.AddRange(salesLines); }
/// <summary> /// Updates the sales transaction with the quantity promotion if applicable. /// </summary> /// <param name="existingTransaction">Existing transaction.</param> /// <param name="tempSalesTransaction">Copy of existing transaction.</param> /// <param name="context">The request context.</param> /// <param name="salesLineIndex">The sales line under consideration.</param> /// <param name="discountLine">The quantity discount under consideration.</param> /// <param name="multiBuyDiscountLines">The multi buy discount lines.</param> private static void GetQuantityPromotions(SalesTransaction existingTransaction, SalesTransaction tempSalesTransaction, RequestContext context, int salesLineIndex, DiscountLine discountLine, IEnumerable <QuantityDiscountLevel> multiBuyDiscountLines) { // Get the multi buy discount lines for the current multi buy discount. IEnumerable <QuantityDiscountLevel> multiBuyLinesForCurrentOffer = multiBuyDiscountLines.Where(j => j.OfferId.Equals(discountLine.OfferId)).OrderBy(l => l.MinimumQuantity); List <SalesLine> salesLinesWithSameProduct = tempSalesTransaction.SalesLines.Where(j => j.ItemId == tempSalesTransaction.SalesLines[salesLineIndex].ItemId && j.InventoryDimensionId == tempSalesTransaction.SalesLines[salesLineIndex].InventoryDimensionId).ToList(); decimal totalQuantity = salesLinesWithSameProduct.Select(j => j.Quantity).Sum(); decimal currentQuantity = tempSalesTransaction.SalesLines[salesLineIndex].Quantity; salesLinesWithSameProduct.Remove(tempSalesTransaction.SalesLines[salesLineIndex]); bool neverApplied = true; foreach (QuantityDiscountLevel multiBuyLine in multiBuyLinesForCurrentOffer) { // removing the quantity discounts that were not applied (because of concurrency rules). if (multiBuyLine.MinimumQuantity <= totalQuantity) { continue; } // Temporarily update the current transaction with the new quantity to see if the quantity discount will be applied. existingTransaction.SalesLines[salesLineIndex].Quantity = multiBuyLine.MinimumQuantity - totalQuantity + currentQuantity; CartWorkflowHelper.Calculate(context, existingTransaction, CalculationModes.All); DiscountLine isApplied = existingTransaction.SalesLines[salesLineIndex].DiscountLines.Where(j => j.OfferId == discountLine.OfferId).SingleOrDefault(); // If the quantity discount will be applied then remove the discount line from the lines with same product and get the min quantity to buy for discount. if (isApplied != null && (isApplied.Amount != 0 || isApplied.Percentage != 0)) { int toBuy = (int)(multiBuyLine.MinimumQuantity - totalQuantity); if (isApplied.Amount != 0) { discountLine.OfferName = string.Format(CultureInfo.CurrentUICulture, Resources.MultiBuyDiscountPricePromotion, toBuy, Math.Round(isApplied.Amount, 2)); } else { discountLine.OfferName = string.Format(CultureInfo.CurrentUICulture, Resources.MultiBuyDiscountPercentagePromotion, toBuy, Math.Round(isApplied.Percentage, 2)); } neverApplied = false; break; } } if (neverApplied) { tempSalesTransaction.SalesLines[salesLineIndex].DiscountLines.Remove(discountLine); } existingTransaction.SalesLines[salesLineIndex].Quantity = currentQuantity; CartWorkflowHelper.Calculate(context, existingTransaction, CalculationModes.All); foreach (SalesLine sameproductCartLine in salesLinesWithSameProduct) { sameproductCartLine.DiscountLines.Remove(sameproductCartLine.DiscountLines.Where(k => k.OfferId == discountLine.OfferId).SingleOrDefault()); } }
/// <summary> /// Processes the update cart lines request. /// </summary> /// <param name="context">The context.</param> /// <param name="request">The request.</param> /// <param name="transaction">Current transaction.</param> private static void ProcessUpdateCartLinesRequest(RequestContext context, SaveCartLinesRequest request, SalesTransaction transaction) { Dictionary <string, SalesLine> salesLinesById = transaction.SalesLines.ToDictionary(sl => sl.LineId, sl => sl); // Keep track of updated sales lines. var updatedSalesLines = new List <SalesLine>(); // Update sales lines. foreach (CartLine cartLine in request.CartLines) { var salesLine = salesLinesById[cartLine.LineId]; if (salesLine.Quantity != cartLine.Quantity) { // Validate permissions. context.Execute <NullResponse>(new CheckAccessServiceRequest(RetailOperation.SetQuantity)); } if (!salesLine.IsReturnByReceipt) { // Copy the properties from the cart line salesLine.CopyPropertiesFrom(cartLine.LineData); // we have to preserve the LineId, regardless what is set on line data salesLine.LineId = cartLine.LineId; } else { // For return // Keep the properties on the sales line and only copy the quantity from the cart line salesLine.Quantity = cartLine.LineData.Quantity; // Calculate required reason code lines for return item. ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnSalesLine(context, transaction, salesLine, ReasonCodeSourceType.ReturnItem); } updatedSalesLines.Add(salesLine); } // Set default attributes for the updated sales lines. if (updatedSalesLines.Any()) { CartWorkflowHelper.SetDefaultDataOnSalesLines(context, transaction, updatedSalesLines); } }
/// <summary> /// Executes the workflow to fetch the promotions. /// </summary> /// <param name="request">The request.</param> /// <returns>The response.</returns> protected override GetPromotionsResponse Process(GetPromotionsRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.CartId, "request.CartId"); // Get the current instance of the transaction from the database. SalesTransaction transaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.CartId); if (transaction == null) { return(new GetPromotionsResponse(null)); } ThrowIf.Null(transaction, "transaction"); // Calculate totals on the current instance of transaction. CartWorkflowHelper.Calculate(this.Context, transaction, CalculationModes.All); // The discount lines on this transaction are the discount lines that have been applied. SalesTransaction currentSalesTransaction = transaction.Clone <SalesTransaction>(); Cart cart = CartWorkflowHelper.ConvertToCart(this.Context, currentSalesTransaction); CartWorkflowHelper.RemoveHistoricalTenderLines(cart); // The discount lines on the transaction are all available discount lines for the items. CartWorkflowHelper.LoadAllPeriodicDiscounts(this.Context, currentSalesTransaction); SalesTransaction tempSalesTransaction = transaction.Clone <SalesTransaction>(); transaction = currentSalesTransaction.Clone <SalesTransaction>(); Collection <string> cartPromotionLines = new Collection <string>(); Collection <CartLinePromotion> cartLinePromotions = new Collection <CartLinePromotion>(); for (int i = 0; i < currentSalesTransaction.SalesLines.Count; i++) { // Removing the applied discount lines, except multiple buy because a different discount level of the already applied multi buy discount can be promoted. foreach (DiscountLine discountLine in currentSalesTransaction.SalesLines[i].DiscountLines) { tempSalesTransaction.SalesLines[i].DiscountLines.Remove(tempSalesTransaction.SalesLines[i].DiscountLines.Where(j => j.OfferId == discountLine.OfferId).SingleOrDefault()); } // Removing the discounts that require coupon code. // Removing the discount offers those were not applied (because of concurrency rules). // Removing mix and match discounts (mix and match discounts are not shown as promotions). List <DiscountLine> offerDiscountLines = tempSalesTransaction.SalesLines[i].DiscountLines.Where(j => (j.PeriodicDiscountType == PeriodicDiscountOfferType.Offer) || j.IsDiscountCodeRequired || (j.PeriodicDiscountType == PeriodicDiscountOfferType.MixAndMatch)).ToList(); foreach (DiscountLine discountLine in offerDiscountLines) { tempSalesTransaction.SalesLines[i].DiscountLines.Remove(discountLine); } PricingDataManager pricingDataManager = new PricingDataManager(this.Context); // Quantity discounts. // Finding all the quantity discounts that will be applied to the cart. List <DiscountLine> quantityDiscountLines = tempSalesTransaction.SalesLines[i].DiscountLines.Where(j => j.PeriodicDiscountType == PeriodicDiscountOfferType.MultipleBuy).ToList(); // Get the multibuy discount lines for this multi buy discounts. IEnumerable <QuantityDiscountLevel> multiBuyDiscountLines = pricingDataManager.GetMultipleBuyDiscountLinesByOfferIds(quantityDiscountLines.Select(j => j.OfferId)); foreach (DiscountLine discountLine in quantityDiscountLines) { GetQuantityPromotions(transaction, tempSalesTransaction, this.Context, i, discountLine, multiBuyDiscountLines); } // Threshhold Discounts. // Finding all the threshold discounts that will be applied to the cart. List <DiscountLine> thresholdDiscountLines = tempSalesTransaction.SalesLines[i].DiscountLines.Where(j => j.PeriodicDiscountType == PeriodicDiscountOfferType.Threshold).ToList(); // Get the tiers for this threshold discounts IEnumerable <ThresholdDiscountTier> tiers = pricingDataManager.GetThresholdTiersByOfferIds(thresholdDiscountLines.Select(j => j.OfferId)); foreach (DiscountLine thresholdDiscount in thresholdDiscountLines) { GetThresholdDiscounts(transaction, tempSalesTransaction, this.Context, i, cartPromotionLines, thresholdDiscount, tiers); } IEnumerable <string> promotionsForCurrentLine = tempSalesTransaction.SalesLines[i].DiscountLines.Select(j => j.OfferName); cartLinePromotions.Add(new CartLinePromotion(cart.CartLines[i].LineId, promotionsForCurrentLine)); } CartPromotions cartPromotions = new CartPromotions(cartPromotionLines, cartLinePromotions); return(new GetPromotionsResponse(cartPromotions)); }
/// <summary> /// Executes the workflow to add or delete discount codes in cart. /// </summary> /// <param name="request">The request.</param> /// <returns>The response.</returns> protected override SaveCartResponse Process(AddOrRemoveDiscountCodesRequest request) { ThrowIf.Null(request, "request"); ThrowIf.Null(request.CartId, "request.CartId"); ThrowIf.Null(request.DiscountCodes, "request.DiscountCodes"); // Load sales transaction. SalesTransaction transaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.CartId); if (transaction == null) { throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotFound, request.CartId); } IEnumerable <SalesTransaction> salesTransactions = new[] { transaction }; transaction = salesTransactions.SingleOrDefault(); if (transaction == null) { return(new SaveCartResponse(new Cart())); } bool update = false; switch (request.DiscountCodesOperation) { case DiscountCodesOperation.Add: foreach (string discountCode in request.DiscountCodes) { if (!transaction.DiscountCodes.Contains(discountCode)) { transaction.DiscountCodes.Add(discountCode); update = true; } } break; case DiscountCodesOperation.Remove: foreach (string discountCode in request.DiscountCodes) { transaction.DiscountCodes.Remove(discountCode); update = true; } break; default: throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, string.Format("Invalid discount code operation value: {0}", request.DiscountCodesOperation)); } if (update) { // Calculate totals CartWorkflowHelper.Calculate(this.Context, transaction, null); // Save the sales transaction CartWorkflowHelper.SaveSalesTransaction(this.Context, transaction); } Cart cart = CartWorkflowHelper.ConvertToCart(this.Context, transaction); CartWorkflowHelper.RemoveHistoricalTenderLines(cart); return(new SaveCartResponse(cart)); }