Ejemplo n.º 1
0
            /// <summary>
            /// Creates a sales order given the cart and payment information.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override SaveCustomerOrderResponse Process(SaveCustomerOrderRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.CartId, "request.CartId");

                // Get the Sales Transaction
                SalesTransaction transaction = CustomerOrderWorkflowHelper.GetSalesTransaction(this.Context, request.CartId, request.ReceiptEmailAddress);

                // Update customer order fields before validation
                CustomerOrderWorkflowHelper.UpdateCustomerOrderFieldsOnCheckout(transaction);

                // Return validations
                switch (transaction.CustomerOrderMode)
                {
                case CustomerOrderMode.Return:
                    CustomerOrderWorkflowHelper.ValidateOrderForReturn(this.Context, transaction);
                    break;

                case CustomerOrderMode.CustomerOrderCreateOrEdit:
                case CustomerOrderMode.QuoteCreateOrEdit:
                    CustomerOrderWorkflowHelper.ValidateOrderAndQuoteCreationAndUpdate(this.Context, transaction);
                    break;

                default:
                    throw new NotSupportedException(
                              string.Format(CultureInfo.InvariantCulture, "Customer order mode {0} not supported.", transaction.CustomerOrderMode));
                }

                // Add customer order specific missing dependencies to the sales transaction
                CustomerOrderWorkflowHelper.FillMissingRequirementsForOrder(this.Context, transaction);

                // Resolve addresses
                ShippingHelper.ValidateAndResolveAddresses(this.Context, transaction);

                // Validate shipping addresses
                ShippingHelper.ValidateShippingInformation(this.Context, transaction);

                // Validate required reason code lines has been filled
                ReasonCodesWorkflowHelper.ValidateRequiredReasonCodeLinesFilled(this.Context, transaction);

                // Fill in receipt id. Receipt id will be needed if paying with credit memo.
                transaction = CustomerOrderWorkflowHelper.FillInReceiptId(this.Context, transaction, request.ReceiptNumberSequence);

                // Handle payments
                if (request.TokenizedPaymentCard != null)
                {
                    transaction = CustomerOrderWorkflowHelper.HandlePayments(this.Context, transaction, request.TokenizedPaymentCard);
                }
                else
                {
                    transaction = CustomerOrderWorkflowHelper.HandlePayments(this.Context, transaction);
                }

                // Create order through transaction service
                SalesOrder salesOrder = CustomerOrderWorkflowHelper.SaveCustomerOrder(this.Context, transaction);

                CartWorkflowHelper.TryDeleteCart(this.Context, transaction);

                return(new SaveCustomerOrderResponse(salesOrder));
            }
            /// <summary>
            /// Validates the required reason code lines filled.
            /// </summary>
            /// <param name="requestContext">The request context.</param>
            /// <param name="salesTransaction">The sales transaction.</param>
            /// <exception cref="ConfigurationException">Required Service missing.</exception>
            /// <exception cref="DataValidationException">One or more reason codes required for the transaction are missing.</exception>
            public static void ValidateRequiredReasonCodeLinesFilled(RequestContext requestContext, SalesTransaction salesTransaction)
            {
                ThrowIf.Null(requestContext, "requestContext");
                ThrowIf.Null(salesTransaction, "salesTransaction");

                var serviceRequest  = new CalculateRequiredReasonCodesServiceRequest(salesTransaction, ReasonCodeSourceType.None);
                var serviceResponse = requestContext.Execute <CalculateRequiredReasonCodesServiceResponse>(serviceRequest);

                ReasonCodesWorkflowHelper.ThrowIfRequiredReasonCodesMissing(serviceResponse);
            }
            /// <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);
            }
            /// <summary>
            /// Helper methods to calculates the required reason codes.
            /// </summary>
            /// <param name="requestContext">The request context.</param>
            /// <param name="serviceRequest">The service request.</param>
            /// <exception cref="ConfigurationException">Required Service missing: {0}.</exception>
            private static void CalculateRequiredReasonCodesHelper(RequestContext requestContext, CalculateRequiredReasonCodesServiceRequest serviceRequest)
            {
                ThrowIf.Null(serviceRequest.SalesTransaction, "serviceRequest.SalesTransaction");

                // Reason codes are only calculated for retail stores and carts that are not customer orders.
                if ((requestContext.GetChannelConfiguration().ChannelType == RetailChannelType.RetailStore) &&
                    (serviceRequest.SalesTransaction.CartType != CartType.CustomerOrder))
                {
                    var serviceResponse = requestContext.Execute <CalculateRequiredReasonCodesServiceResponse>(serviceRequest);
                    ReasonCodesWorkflowHelper.ThrowIfRequiredReasonCodesMissing(serviceResponse);
                }
            }
Ejemplo n.º 5
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()
         });
     }
 }
            /// <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>
            /// 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>
            /// Creates a sales transaction given the cart.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override SubmitSalesTransactionResponse Process(SubmitSalesTransactionRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.CartId, "request.CartId");

                // Get the Sales Transaction
                SalesTransaction salesTransaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.CartId);

                if (salesTransaction == null)
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotFound, request.CartId);
                }

                if (salesTransaction.IsSuspended)
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotActive, request.CartId);
                }

                if (!(salesTransaction.TerminalId ?? string.Empty).Equals(this.Context.GetTerminal().TerminalId ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                {
                    // If the terminal id of the cart is not same as the context then it means that the cart is active on another terminal.
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_LoadingActiveCartFromAnotherTerminalNotAllowed, request.CartId);
                }

                salesTransaction.ReceiptEmail = request.ReceiptEmail;

                // Set the transaction type to represent Cash & Carry sales by default, if it's not an IncomeExpense nor AccountDeposit transaction.
                if (salesTransaction.TransactionType != SalesTransactionType.IncomeExpense && salesTransaction.TransactionType != SalesTransactionType.CustomerAccountDeposit)
                {
                    salesTransaction.TransactionType = SalesTransactionType.Sales;
                }

                // Fill in Store and Terminal information.
                OrderWorkflowHelper.FillTransactionWithContextData(this.Context, salesTransaction);

                // Calculate required reason code for end of transaction.
                ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnTransaction(this.Context, salesTransaction, ReasonCodeSourceType.EndOfTransaction);

                // Validate required reason code lines has been filled.
                ReasonCodesWorkflowHelper.ValidateRequiredReasonCodeLinesFilled(this.Context, salesTransaction);

                // Validate return permission.
                CartWorkflowHelper.ValidateReturnPermission(this.Context, salesTransaction, salesTransaction.CartType);

                // Fill in variant information.
                OrderWorkflowHelper.FillVariantInformation(this.Context, salesTransaction);

                // Fill in Receipt Id.
                OrderWorkflowHelper.FillInReceiptId(this.Context, salesTransaction, request.ReceiptNumberSequence);

                // Validate sales order for creation.
                OrderWorkflowHelper.ValidateContextForCreateOrder(this.Context, salesTransaction);

                // Validate payments.
                OrderWorkflowHelper.CalculateAndValidateAmountPaidForCheckout(this.Context, salesTransaction);

                salesTransaction = OrderWorkflowHelper.ProcessCheckoutPayments(this.Context, salesTransaction);

                // release/unlock gift cards on sales lines
                GiftCardWorkflowHelper.UnlockGiftCardsOnActiveSalesLines(this.Context, salesTransaction);

                // Pay Sales Invoices...
                OrderWorkflowHelper.SettleInvoiceSalesLines(this.Context, salesTransaction);

                // Create order
                var salesOrder = OrderWorkflowHelper.CreateSalesOrder(this.Context, salesTransaction);

                // We also need to delete the shopping cart.
                CartWorkflowHelper.TryDeleteCart(
                    this.Context,
                    new SalesTransaction
                {
                    Id         = salesTransaction.Id,
                    TerminalId = salesTransaction.TerminalId,
                    CustomerId = salesTransaction.CustomerId,
                });

                return(new SubmitSalesTransactionResponse(salesOrder));
            }
            /// <summary>
            /// Creates a sales transaction given the cart.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override SaveVoidTransactionResponse Process(SaveVoidTransactionRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.CartId, "request.CartId");

                // Get the Sales Transaction
                SalesTransaction salesTransaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.CartId, ignoreProductDiscontinuedNotification: true);

                if (salesTransaction == null)
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotFound, request.CartId);
                }

                if (salesTransaction.IsSuspended)
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotActive, request.CartId);
                }

                // If there are unvoided non-historical Tender lines throw exception that Transaction cannot be voided.
                if (salesTransaction.TenderLines != null && salesTransaction.TenderLines.Any(l => l.TransactionStatus != TransactionStatus.Voided && l.Status != TenderLineStatus.Historical))
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_VoidTransactionContainsTenderedLines, request.CartId);
                }

                // Add or update any incoming reason codes on the transaction.
                Cart cartToBeVoided = new Cart
                {
                    Id = request.CartId
                };

                if (request.ReasonCodeLines != null)
                {
                    cartToBeVoided.ReasonCodeLines = request.ReasonCodeLines.ToList();
                }

                ReasonCodesWorkflowHelper.AddOrUpdateReasonCodeLinesOnTransaction(salesTransaction, cartToBeVoided);

                // Calculate the required reason codes on the tender line for voiding transaction.
                ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnTransaction(this.Context, salesTransaction, ReasonCodeSourceType.VoidTransaction);

                GiftCardWorkflowHelper.VoidGiftCardSalesLines(this.Context, salesTransaction);

                // Set the Sales Transaction Status to Voided.
                salesTransaction.EntryStatus = TransactionStatus.Voided;

                // Set the transaction type to represent Cash & Carry sales.
                if (salesTransaction.TransactionType != SalesTransactionType.IncomeExpense)
                {
                    salesTransaction.TransactionType = SalesTransactionType.Sales;
                }

                // Fill in Store and Terminal information.
                OrderWorkflowHelper.FillTransactionWithContextData(this.Context, salesTransaction);

                // Create order
                var salesOrder = OrderWorkflowHelper.CreateSalesOrder(this.Context, salesTransaction);

                // We also need to delete the shopping cart.
                CartWorkflowHelper.TryDeleteCart(
                    this.Context,
                    new SalesTransaction
                {
                    Id         = request.CartId,
                    TerminalId = salesTransaction.TerminalId,
                    CustomerId = salesTransaction.CustomerId,
                });

                CartWorkflowHelper.LogAuditEntry(
                    this.Context,
                    "SaveVoidTransactionRequestHandler.Process",
                    string.Format("Transaction '{0}' voided.", request.CartId));

                return(new SaveVoidTransactionResponse(salesOrder));
            }
            /// <summary>
            /// Saves (updating if it exists and creating a new one if it does not) the shopping cart on the request.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns><see cref="SaveCartResponse"/> object containing the cart with updated item quantities.</returns>
            protected override SaveCartResponse Process(SaveCartRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.Cart, "request.Cart");

                var validateCustomerAccountRequest  = new GetValidatedCustomerAccountNumberServiceRequest(request.Cart.CustomerId, throwOnValidationFailure: false);
                var validateCustomerAccountResponse = this.Context.Execute <GetValidatedCustomerAccountNumberServiceResponse>(validateCustomerAccountRequest);

                if (validateCustomerAccountResponse.IsCustomerAccountNumberInContextDifferent)
                {
                    request.Cart.CustomerId = validateCustomerAccountResponse.ValidatedAccountNumber;
                }

                bool isItemSale = request.Cart.CartLines.Any(l => string.IsNullOrWhiteSpace(l.LineId) && !l.IsGiftCardLine && !l.IsVoided && l.Quantity >= 0m);

                if (string.IsNullOrWhiteSpace(request.Cart.Id))
                {
                    request.Cart.Id = CartWorkflowHelper.GenerateRandomTransactionId(this.Context);
                }

                // Copy the logic from CartService.CreateCart().
                foreach (CartLine line in request.Cart.CartLines)
                {
                    // Sets the IsReturn flag to true, when ReturnTransactionId is specified.
                    // The reason of doing so is that the IsReturn is not currently exposed on CartLine entity.
                    if (!string.IsNullOrEmpty(line.ReturnTransactionId))
                    {
                        line.LineData.IsReturnByReceipt = true;
                    }
                }

                // Get the Sales Transaction
                SalesTransaction salesTransaction = CartWorkflowHelper.LoadSalesTransaction(this.Context, request.Cart.Id);

                if (salesTransaction == null)
                {
                    // New transaction - set the default cart type to shopping if none
                    if (request.Cart.CartType == CartType.None)
                    {
                        request.Cart.CartType = CartType.Shopping;
                    }
                }

                CartWorkflowHelper.ValidateCartPermissions(salesTransaction, request.Cart, this.Context);

                if (salesTransaction == null)
                {
                    // New transaction - set the default cart type to shopping if none
                    if (request.Cart.CartType == CartType.None)
                    {
                        request.Cart.CartType = CartType.Shopping;
                    }

                    // Do not allow new transaction for blocked customer.
                    CartWorkflowHelper.ValidateCustomerAccount(this.Context, request.Cart, null);

                    // Set loyalty card from the customer number
                    CartWorkflowHelper.SetLoyaltyCardFromCustomer(this.Context, request.Cart);

                    // Set affiliations from the customer number
                    CartWorkflowHelper.AddOrUpdateAffiliationLinesFromCustomer(this.Context, null, request.Cart);

                    // If cannot find the transaction, create a new transaction.
                    salesTransaction = CartWorkflowHelper.CreateSalesTransaction(this.Context, request.Cart.Id, request.Cart.CustomerId);

                    // Set initial values on cart to be same as on transaction.
                    request.Cart.CopyPropertiesFrom(salesTransaction);

                    // Update transaction level reason code lines.
                    ReasonCodesWorkflowHelper.AddOrUpdateReasonCodeLinesOnTransaction(salesTransaction, request.Cart);

                    // Calculate required reason code lines for start of transaction.
                    ReasonCodesWorkflowHelper.CalculateRequiredReasonCodesOnTransaction(this.Context, salesTransaction, ReasonCodeSourceType.StartOfTransaction);
                }

                // If cart or the sales transaction is suspended then update is not permitted
                if (salesTransaction.IsSuspended)
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CartNotActive, request.Cart.Id);
                }

                // If the terminal id of the cart is not same as the context then it means that the cart is active on another terminal.
                GetCurrentTerminalIdDataRequest dataRequest = new GetCurrentTerminalIdDataRequest();

                if (!(salesTransaction.TerminalId ?? string.Empty).Equals(this.Context.Execute <SingleEntityDataServiceResponse <string> >(dataRequest).Entity ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                {
                    throw new CartValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_LoadingActiveCartFromAnotherTerminalNotAllowed, request.Cart.Id);
                }

                // At this point, the sales transaction is either newly created with no sales lines or just loaded from DB.
                // We are yet to add new sales lines or update existing sales lines.
                // Get the returned sales transaction if the cart contains a return line
                SalesTransaction returnTransaction = CartWorkflowHelper.LoadSalesTransactionForReturn(this.Context, request.Cart, salesTransaction, request.OperationType);

                // If customer account number is not specified on the request it should not be overriden.
                if (request.Cart.CustomerId == null)
                {
                    request.Cart.CustomerId = salesTransaction.CustomerId;
                }

                // Get the products in the cart lines
                IDictionary <long, SimpleProduct> productsByRecordId = CartWorkflowHelper.GetProductsInCartLines(this.Context, request.Cart.CartLines);

                // Validate update cart request
                CartWorkflowHelper.ValidateUpdateCartRequest(this.Context, salesTransaction, returnTransaction, request.Cart, request.IsGiftCardOperation, productsByRecordId);

                request.Cart.IsReturnByReceipt = returnTransaction != null;
                request.Cart.ReturnTransactionHasLoyaltyPayment = returnTransaction != null && returnTransaction.HasLoyaltyPayment;

                if (returnTransaction != null &&
                    !string.IsNullOrWhiteSpace(returnTransaction.LoyaltyCardId) &&
                    string.IsNullOrWhiteSpace(salesTransaction.LoyaltyCardId))
                {
                    // Set the loyalty card of the returned transaction to the current transaction
                    request.Cart.LoyaltyCardId = returnTransaction.LoyaltyCardId;
                }

                HashSet <string> newSalesLineIdSet = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

                // Perform update cart operations
                CartWorkflowHelper.PerformSaveCartOperations(this.Context, request, salesTransaction, returnTransaction, newSalesLineIdSet, productsByRecordId);

                // Sets the wharehouse id and invent location id for each line
                ItemAvailabilityHelper.SetSalesLineInventory(this.Context, salesTransaction);

                // Calculate totals and saves the sales transaction
                CartWorkflowHelper.Calculate(this.Context, salesTransaction, request.CalculationModes, isItemSale, newSalesLineIdSet);

                // Validate price on sales line after calculations
                CartWorkflowHelper.ValidateSalesLinePrice(this.Context, salesTransaction, productsByRecordId);

                // Validate the customer account deposit transaction.
                AccountDepositHelper.ValidateCustomerAccountDepositTransaction(this.Context, salesTransaction);

                // Validate return item and return transaction permissions
                CartWorkflowHelper.ValidateReturnPermission(this.Context, salesTransaction, request.Cart.CartType);

                // Calculate the required reason codes after the price calculation
                ReasonCodesWorkflowHelper.CalculateRequiredReasonCodes(this.Context, salesTransaction, ReasonCodeSourceType.None);

                CartWorkflowHelper.SaveSalesTransaction(this.Context, salesTransaction);

                Cart cart = CartWorkflowHelper.ConvertToCart(this.Context, salesTransaction);

                CartWorkflowHelper.RemoveHistoricalTenderLines(cart);

                return(new SaveCartResponse(cart));
            }
            protected override CreateOrderFromCartResponse Process(CreateOrderFromCartRequest request)
            {
                ThrowIf.Null(request, "request");
                ThrowIf.Null(request.CartId, "request.CartId");

                List <TenderLine> tenderLines = new List <TenderLine>();

                // Get the Sales Transaction.
                SalesTransaction salesTransaction = null;

                ExecutionHandler(
                    delegate
                {
                    PopulateSalesTransaction(this.Context, request.CartId, request.ReceiptEmailAddress, out salesTransaction);
                },
                    SubmitOrderOperationType.GetSalesTransaction.ToString());

                // Validate sales order for creation.
                ExecutionHandler(
                    delegate
                {
                    OrderWorkflowHelper.ValidateContextForCreateOrder(this.Context, salesTransaction);
                },
                    SubmitOrderOperationType.ValidateContextForCreateOrder.ToString());

                // Validate addresses.
                ExecutionHandler(
                    delegate
                {
                    ShippingHelper.ValidateAndResolveAddresses(this.Context, salesTransaction);
                },
                    SubmitOrderOperationType.ValidateAndResolveAddresses.ToString());

                // Add missing dependencies to the sales transaction.
                ExecutionHandler(
                    delegate
                {
                    OrderWorkflowHelper.FillMissingRequirementsForOrder(this.Context, salesTransaction);
                },
                    SubmitOrderOperationType.FillMissingRequirementsForOrder.ToString());

                // Validate addresses for shipping.
                ExecutionHandler(
                    delegate
                {
                    ShippingHelper.ValidateShippingInformation(this.Context, salesTransaction);
                },
                    SubmitOrderOperationType.ValidateShippingAddresses.ToString());

                // Validate required reason code lines has been filled.
                ExecutionHandler(
                    () => ReasonCodesWorkflowHelper.ValidateRequiredReasonCodeLinesFilled(this.Context, salesTransaction),
                    SubmitOrderOperationType.ValidateReasonCodes.ToString());

                // Authorize payments.
                ExecutionHandler(
                    delegate
                {
                    tenderLines = OrderWorkflowHelper.ProcessPendingOrderPayments(this.Context, salesTransaction, request.CartTenderLines);
                },
                    SubmitOrderOperationType.AuthorizePayments.ToString());

                SalesOrder salesOrder = null;

                try
                {
                    // Create order and add all authorization payment blobs.
                    ExecutionHandler(
                        delegate
                    {
                        salesOrder = OrderWorkflowHelper.CreateSalesOrder(this.Context, salesTransaction);
                    },
                        SubmitOrderOperationType.CreateSaleOrderInCrt.ToString());
                }
                catch (Exception ex)
                {
                    try
                    {
                        // Cancel the payment authorizations
                        OrderWorkflowHelper.CancelPayments(this.Context, salesTransaction, tenderLines, request.CartTenderLines);
                    }
                    catch (PaymentException cancelPaymentsEx)
                    {
                        // Inside of CancelPayments() we always wrap Exception as PaymentException.
                        RetailLogger.Log.CrtWorkflowCancelingPaymentFailure(ex, cancelPaymentsEx);
                    }

                    throw;
                }

                // We also need to delete the shopping cart.
                CartWorkflowHelper.TryDeleteCart(
                    this.Context,
                    new SalesTransaction
                {
                    Id         = request.CartId,
                    TerminalId = salesTransaction.TerminalId,
                    CustomerId = salesTransaction.CustomerId,
                });

                return(new CreateOrderFromCartResponse(salesOrder));
            }
            /// <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);
                }
            }