Пример #1
0
            /// <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));
            }
Пример #4
0
            /// <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);
                }
            }
Пример #6
0
            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());
                }
            }
Пример #8
0
 internal static void PopulateProductIds(IPricingDataAccessor pricingDataManager, PriceContext priceContext, SalesTransaction transaction)
 {
     PopulateProductIds(pricingDataManager, priceContext, transaction.PriceCalculableSalesLines);
 }
Пример #9
0
            internal static Dictionary <long, List <DiscountBase> > GetProductOrVariantToDiscountMapLive(
                SalesTransaction transaction,
                PriceContext priceContext,
                IPricingDataAccessor pricingDataManager)
            {
                List <ItemUnit>           items = new List <ItemUnit>();
                Dictionary <string, long> itemIdInventDimIdToProductOrVariantIdMap = new Dictionary <string, long>(StringComparer.OrdinalIgnoreCase);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                    SetEffectiveDiscountPriorityFromPriceGroups(offerIdToDiscountMap, priceContext);

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

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

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

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

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

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

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

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

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

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

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

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

                return(productDiscountMap);
            }
Пример #10
0
            /// <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);
            }
Пример #11
0
            /// <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
                    }
                }
            }
Пример #12
0
            /// <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));
                }
            }
Пример #13
0
        public void Update(string realmId, SalesTransaction entity)
        {
            var dataService = GetDataService(realmId);

            dataService.Update(entity);
        }
Пример #14
0
 /// <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()
         });
     }
 }
Пример #15
0
 /// <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);
            }
Пример #17
0
            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));
            }
Пример #22
0
            /// <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));
            }