/// <summary> /// Update tender line amounts with cash back for company and channel currencies. /// </summary> /// <param name="tenderLine">The tender line to update.</param> /// <param name="context">The request context.</param> private static void CalculateAmountsWithCashBack(TenderLine tenderLine, RequestContext context) { tenderLine.Amount += tenderLine.CashBackAmount; // In case of cashback, tendered currency is always equal to the channel currency. tenderLine.AmountInTenderedCurrency += tenderLine.CashBackAmount; string companyCurrencyCode = context.GetChannelConfiguration().CompanyCurrency; if (!tenderLine.Currency.Equals(companyCurrencyCode, StringComparison.OrdinalIgnoreCase)) { // Convert cashback from tendered to company currency. GetCurrencyValueServiceResponse cashBackConversionInfo = ConvertCurrencyAmount( tenderLine.CashBackAmount, tenderLine.Currency, companyCurrencyCode, context); tenderLine.AmountInCompanyCurrency += cashBackConversionInfo.ConvertedAmount; } else { tenderLine.AmountInCompanyCurrency += tenderLine.CashBackAmount; } tenderLine.CashBackAmount = 0M; }
/// <summary> /// Get the gift card. /// </summary> /// <param name="request">The request.</param> /// <returns>A response containing the gift card information.</returns> private static GetGiftCardServiceResponse GetGiftCard(GetGiftCardServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (string.IsNullOrWhiteSpace(request.GiftCardId)) { throw new ArgumentException("request.Id is null or empty.", "request"); } var requestGetGiftCardBalance = new GetGiftCardRealtimeRequest(request.GiftCardId); GiftCard card = request.RequestContext.Execute <SingleEntityDataServiceResponse <GiftCard> >(requestGetGiftCardBalance).Entity; GetCurrencyValueServiceResponse convertBalanceToChannelCurrencyResponse = FromGiftCardCurrency(request.RequestContext, card.BalanceInCardCurrency, card.CardCurrencyCode); GiftCard giftCard = new GiftCard { Id = request.GiftCardId, BalanceCurrencyCode = convertBalanceToChannelCurrencyResponse.ToCurrencyCode, Balance = convertBalanceToChannelCurrencyResponse.RoundedConvertedAmount, CardCurrencyCode = card.CardCurrencyCode, BalanceInCardCurrency = card.BalanceInCardCurrency }; return(new GetGiftCardServiceResponse(giftCard)); }
/// <summary> /// Convert amount in one currency to another. /// </summary> /// <param name="fromCurrencyCode">From currency code.</param> /// <param name="toCurrencyCode">To currency code.</param> /// <param name="amountToConvert">Amount to convert.</param> /// <returns>Converted amount in new currency.</returns> public decimal ConvertCurrency(string fromCurrencyCode, string toCurrencyCode, decimal amountToConvert) { GetCurrencyValueServiceRequest currencyRequest = new GetCurrencyValueServiceRequest(fromCurrencyCode, toCurrencyCode, amountToConvert); GetCurrencyValueServiceResponse currencyResponse = this.context.Execute <GetCurrencyValueServiceResponse>(currencyRequest); return(currencyResponse.ConvertedAmount); }
/// <summary> /// Update tender line with amounts and exchange rates for company and channel currencies. /// </summary> /// <param name="tenderLine">Tender line to update.</param> /// <param name="context">Request context.</param> private static void CalculateTenderLineCurrencyAmounts(TenderLine tenderLine, RequestContext context) { string channelCurrencyCode = context.GetChannelConfiguration().Currency; if (!tenderLine.Currency.Equals(channelCurrencyCode, StringComparison.OrdinalIgnoreCase)) { if (tenderLine.AmountInTenderedCurrency == 0) { throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_InvalidRequest, "Currency on tender line is different from store currency but AmountInTenderedCurrency is not specified."); } IEnumerable <string> supportedCurrencies = GetCurrencyCodeSupportedByChannel(context); if (supportedCurrencies.FirstOrDefault(c => string.Equals(c, tenderLine.Currency, StringComparison.OrdinalIgnoreCase)) == null) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CurrencyConversionFailed, string.Format("Currency code '{0}' is not supported by current channel.", tenderLine.Currency)); } // Convert amount and get exchange rate from tendered currency to channel currency. GetCurrencyValueServiceResponse channelConversionInfo = ConvertCurrencyAmount( tenderLine.AmountInTenderedCurrency, tenderLine.Currency, channelCurrencyCode, context); tenderLine.ExchangeRate = channelConversionInfo.ExchangeRate; tenderLine.Amount = channelConversionInfo.RoundedConvertedAmount; // Round the amount in tendered currency. tenderLine.AmountInTenderedCurrency = RoundAmountByCurrency(context, tenderLine.AmountInTenderedCurrency, tenderLine.Currency); } else { tenderLine.ExchangeRate = 1m; tenderLine.AmountInTenderedCurrency = tenderLine.Amount; } string companyCurrencyCode = context.GetChannelConfiguration().CompanyCurrency; if (!tenderLine.Currency.Equals(companyCurrencyCode, StringComparison.OrdinalIgnoreCase)) { // Convert tendered amount to company amount, and get exchange rate from tendered to company currencies. GetCurrencyValueServiceResponse tenderConversionInfo = ConvertCurrencyAmount( tenderLine.AmountInTenderedCurrency, tenderLine.Currency, companyCurrencyCode, context); tenderLine.CompanyCurrencyExchangeRate = tenderConversionInfo.ExchangeRate; tenderLine.AmountInCompanyCurrency = tenderConversionInfo.RoundedConvertedAmount; } else { tenderLine.CompanyCurrencyExchangeRate = 1m; tenderLine.AmountInCompanyCurrency = PaymentManagerService.RoundAmountByCurrency(context, tenderLine.AmountInTenderedCurrency, tenderLine.Currency); } }
/// <summary> /// Convert amount from one currency to another. /// </summary> /// <param name="amountInCurrency">Amount to convert.</param> /// <param name="fromCurrencyCode">Currency to convert from.</param> /// <param name="toCurrencyCode">Currency to convert to.</param> /// <param name="context">Request context.</param> /// <returns>Response that contains converted amount along with exchange rate.</returns> private static GetCurrencyValueServiceResponse ConvertCurrencyAmount(decimal amountInCurrency, string fromCurrencyCode, string toCurrencyCode, RequestContext context) { var request = new GetCurrencyValueServiceRequest( fromCurrencyCode, toCurrencyCode, amountInCurrency); GetCurrencyValueServiceResponse response = context.Execute <GetCurrencyValueServiceResponse>(request); return(response); }
/// <summary> /// Converts a value from one currency to another. /// </summary> /// <param name="request">Currency request specifying retail runtime, source and destination currencies, and amount to convert.</param> /// <returns> /// The value as it is after conversion in the destination currency, rounded according to the destination currency's rounding setup. /// </returns> private static GetCurrencyValueServiceResponse CurrencyToCurrency(GetCurrencyValueServiceRequest request) { GetCurrencyValueServiceResponse response = GetCurrencyConversion(request.RequestContext, request); if (response.ExchangeRate <= 0) { throw new DataValidationException( DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_CurrencyConversionFailed, string.Format("Exchange rate from currency '{0}' to currency '{1}' is not supported or misconfigured. Calculated exchange rate: {2}", response.FromCurrencyCode, response.ToCurrencyCode, response.ExchangeRate)); } return(response); }
/// <summary> /// Calculate the total currency amount after converting to channel currency for the given list of currencies. /// </summary> /// <param name="request">The list of currencies that requires to be summed.</param> /// <returns>Returns the response that contains the sum of currencies amount.</returns> private static CalculateTotalAmountServiceResponse CalculateTotalCurrencyAmount(CalculateTotalAmountServiceRequest request) { var channelConfiguration = request.RequestContext.GetChannelConfiguration(); decimal totalAmount = 0m; foreach (var currency in request.CurrenciesToConvert) { var getCurrencyValueRequest = new GetCurrencyValueServiceRequest(currency.CurrencyCode, channelConfiguration.Currency, currency.AmountToConvert); GetCurrencyValueServiceResponse response = request.RequestContext.Execute <GetCurrencyValueServiceResponse>(getCurrencyValueRequest); totalAmount += response.RoundedConvertedAmount; } return(new CalculateTotalAmountServiceResponse(new CurrencyAmount { RoundedConvertedAmount = totalAmount, CurrencyCode = channelConfiguration.Currency })); }
/// <summary> /// Gets all supported channel currency value and exchange rate for the given amount. /// </summary> /// <param name="request">Request contains the amount to be converted.</param> /// <returns>The converted amount with exchange rates.</returns> private static GetChannelCurrencyServiceResponse GetSupportedChannelCurrencies(GetChannelCurrencyServiceRequest request) { string fromCurrencyCode = request.CurrencyCode; GetChannelCurrenciesDataRequest dataRequest = new GetChannelCurrenciesDataRequest(request.QueryResultSettings ?? QueryResultSettings.AllRecords); PagedResult <CurrencyAmount> pagedChannelCurrencies = request.RequestContext.Execute <EntityDataServiceResponse <CurrencyAmount> >(dataRequest).PagedEntityCollection; ReadOnlyCollection <CurrencyAmount> channelCurrencies = pagedChannelCurrencies.Results; if (channelCurrencies == null || !channelCurrencies.Any()) { NetTracer.Warning("Cannot find channel currencies"); return(new GetChannelCurrencyServiceResponse()); } var currencyList = channelCurrencies.ToList(); foreach (var toCurrency in currencyList) { var getCurrencyValueRequest = new GetCurrencyValueServiceRequest(fromCurrencyCode, toCurrency.CurrencyCode, request.Amount); GetCurrencyValueServiceResponse serviceResponse = GetCurrencyConversion(request.RequestContext, getCurrencyValueRequest); toCurrency.ExchangeRate = serviceResponse.ExchangeRate; toCurrency.ConvertedAmount = serviceResponse.ConvertedAmount; toCurrency.RoundedConvertedAmount = serviceResponse.RoundedConvertedAmount; } var storeCurrencyList = currencyList.Where(currency => currency.ExchangeRate > 0M).ToList(); // If the from currency does not exists add to the list. if (storeCurrencyList.All(currency => string.CompareOrdinal(currency.CurrencyCode, fromCurrencyCode) != 0)) { CurrencyAmount conversionResult = GetFromCurrencyAmount(request.RequestContext, request.Amount, fromCurrencyCode); storeCurrencyList.Add(conversionResult); } pagedChannelCurrencies.Results = storeCurrencyList.AsReadOnly(); return(new GetChannelCurrencyServiceResponse(pagedChannelCurrencies)); }
public static SalesOrder GetSalesOrderFromInfo(CustomerOrderInfo orderInfo, ChannelConfiguration channelConfiguration, RequestContext context) { // Stores the local copy. There is high probability of having the same shipping/delivery address on all the lines. Dictionary <long, Address> shippingAddressDictionary = new Dictionary <long, Address>(); decimal shippingChargeAmount; ColumnSet columnSet = new ColumnSet(); var salesOrder = new SalesOrder { SalesId = orderInfo.Id, TransactionType = SalesTransactionType.CustomerOrder, CustomerOrderMode = CustomerOrderMode.OrderRecalled, CartType = CartType.CustomerOrder, CustomerOrderType = orderInfo.OrderType, StoreId = orderInfo.StoreId, IsTaxIncludedInPrice = Convert.ToBoolean(orderInfo.IsTaxIncludedInPrice) }; switch (orderInfo.OrderType) { case CustomerOrderType.Quote: salesOrder.Status = Utilities.GetSalesStatus((SalesQuotationStatus)orderInfo.Status); break; case CustomerOrderType.SalesOrder: salesOrder.Status = Utilities.GetSalesStatus((SalesOrderStatus)orderInfo.Status, (DocumentStatus)orderInfo.DocumentStatus); break; default: salesOrder.Status = SalesStatus.Unknown; break; } DateTimeOffset currentChannelDate = context.GetNowInChannelTimeZone(); salesOrder.RequestedDeliveryDate = Utilities.ParseDateStringAsDateTimeOffset(orderInfo.RequestedDeliveryDateString, currentChannelDate.Date, currentChannelDate.Offset); salesOrder.QuotationExpiryDate = Utilities.ParseDateStringAsDateTimeOffset(orderInfo.ExpiryDateString, currentChannelDate.Date, currentChannelDate.Offset); // CreationDate is stored in UTC. It needs to be converted to local time zone where order is accessed. salesOrder.BeginDateTime = Utilities.ParseDateString(orderInfo.CreationDateString, currentChannelDate.ToUniversalTime().DateTime, DateTimeStyles.AssumeUniversal); salesOrder.Comment = orderInfo.Comment; // Header delivery salesOrder.InventoryLocationId = orderInfo.WarehouseId; salesOrder.DeliveryMode = orderInfo.DeliveryMode; foreach (var discountCode in orderInfo.DiscountCodes) { salesOrder.DiscountCodes.Add(discountCode); } // Customer info salesOrder.CustomerId = orderInfo.CustomerAccount; long addressRecordIdLong = 0; if (long.TryParse(orderInfo.AddressRecordId, out addressRecordIdLong)) { var dataServiceRequest = new GetAddressDataRequest(addressRecordIdLong, columnSet); SingleEntityDataServiceResponse <Address> dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); if (dataServiceResponse.Entity == null) { Utilities.DownloadCustomerData(context, salesOrder.CustomerId); dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); } if (dataServiceResponse.Entity != null) { salesOrder.ShippingAddress = dataServiceResponse.Entity; shippingAddressDictionary.Add(salesOrder.ShippingAddress.RecordId, salesOrder.ShippingAddress); } } if (!string.IsNullOrEmpty(orderInfo.SalespersonStaffId)) { // Sets the sales person id and name according to AX values // This is done because we do not know whether the sales person information is available on this store salesOrder.StaffId = orderInfo.SalespersonStaffId; } salesOrder.ChannelReferenceId = orderInfo.ChannelReferenceId; salesOrder.LoyaltyCardId = orderInfo.LoyaltyCardId; salesOrder.ReceiptEmail = orderInfo.Email; string shippingChargeCode = channelConfiguration.ShippingChargeCode; // Items int lineId = 0; foreach (ItemInfo item in orderInfo.Items) { lineId++; var lineItem = new SalesLine { LineId = lineId.ToString(CultureInfo.InvariantCulture), Found = true, RecordId = item.RecId, ItemId = item.ItemId, Comment = item.Comment, Quantity = item.Quantity, ReturnQuantity = item.Quantity, SalesOrderUnitOfMeasure = item.Unit, UnitOfMeasureSymbol = item.Unit, Price = item.Price, NetAmount = item.NetAmount, QuantityOrdered = item.Quantity, QuantityInvoiced = item.QuantityPicked, DeliveryMode = item.DeliveryMode, RequestedDeliveryDate = Utilities.ParseDateStringAsDateTimeOffset(item.RequestedDeliveryDateString, currentChannelDate.Date, currentChannelDate.Offset), FulfillmentStoreId = item.FulfillmentStoreId, InventoryLocationId = item.WarehouseId, SerialNumber = item.SerialId, BatchId = item.BatchId, Status = TransactionStatus.Normal, SalesStatus = Utilities.GetSalesStatus((SalesOrderStatus)item.Status) }; // Copy charges to line and calculates total shipping charge amount lineItem.ChargeLines.AddRange(SalesOrderHelper.CreateChargeLines(item.Charges, shippingChargeCode, salesOrder.BeginDateTime, out shippingChargeAmount)); lineItem.DeliveryModeChargeAmount = shippingChargeAmount; // Line level discount amounts lineItem.LineDiscount = item.LineDscAmount; lineItem.PeriodicDiscount = item.PeriodicDiscount; lineItem.PeriodicPercentageDiscount = item.PeriodicPercentageDiscount; lineItem.LineManualDiscountAmount = item.LineManualDiscountAmount; lineItem.LineManualDiscountPercentage = item.LineManualDiscountPercentage; lineItem.TotalDiscount = item.TotalDiscount; lineItem.TotalPercentageDiscount = item.TotalPctDiscount; // Copy discounts to line lineItem.DiscountLines.AddRange(SalesOrderHelper.CreateDiscountLines(item.Discounts)); long itemAddressRecordIdLong; if (long.TryParse(item.AddressRecordId, out itemAddressRecordIdLong)) { Address lineLevelshippingAddress = new Address(); if (!shippingAddressDictionary.TryGetValue(itemAddressRecordIdLong, out lineLevelshippingAddress)) { var dataServiceRequest = new GetAddressDataRequest(itemAddressRecordIdLong, columnSet); SingleEntityDataServiceResponse <Address> dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); // If address not found download and get. if (dataServiceResponse.Entity == null) { Utilities.DownloadCustomerData(context, salesOrder.CustomerId); dataServiceResponse = context.Execute <SingleEntityDataServiceResponse <Address> >(dataServiceRequest); } if (dataServiceResponse.Entity != null) { lineItem.ShippingAddress = dataServiceResponse.Entity; shippingAddressDictionary.Add(lineItem.ShippingAddress.RecordId, lineItem.ShippingAddress); } } else { lineItem.ShippingAddress = lineLevelshippingAddress; } } Utilities.SetUpVariantAndProduct(context, item.InventDimensionId, lineItem.ItemId, lineItem); lineItem.DiscountAmount = item.Discount; // Set tax info after defaults, as it may have been overridden. lineItem.SalesTaxGroupId = item.SalesTaxGroup ?? string.Empty; lineItem.ItemTaxGroupId = item.ItemTaxGroup ?? string.Empty; // Add it to the transaction salesOrder.SalesLines.Add(lineItem); } // Charges for the header salesOrder.ChargeLines.AddRange(SalesOrderHelper.CreateChargeLines(orderInfo.Charges, shippingChargeCode, salesOrder.BeginDateTime, out shippingChargeAmount)); salesOrder.DeliveryModeChargeAmount = shippingChargeAmount; // Payments // - total up amounts // - add history entries decimal nonPrepayments = decimal.Zero; decimal prepaymentAmountPaid = decimal.Zero; int tenderLineId = 0; foreach (PaymentInfo payment in orderInfo.Payments) { if (salesOrder.TenderLines == null) { salesOrder.TenderLines = new Collection <TenderLine>(); } decimal amount = 0M; if (string.IsNullOrWhiteSpace(payment.Currency) || payment.Currency.Equals(channelConfiguration.Currency, StringComparison.OrdinalIgnoreCase)) { amount = payment.Amount; } else { GetCurrencyValueServiceRequest currencyValueRequest = new GetCurrencyValueServiceRequest(payment.Currency, channelConfiguration.Currency, payment.Amount); GetCurrencyValueServiceResponse currencyValueResponse = context.Execute <GetCurrencyValueServiceResponse>(currencyValueRequest); amount = currencyValueResponse.RoundedConvertedAmount; } if (payment.Prepayment) { // Sum prepayments to track total deposits paid prepaymentAmountPaid += amount; } else { // Sum non-prepayments as base for calculating deposits applied to pickups nonPrepayments += amount; } tenderLineId++; var tenderLine = new TenderLine { TenderLineId = tenderLineId.ToString(CultureInfo.InvariantCulture), Amount = payment.Amount, Currency = payment.Currency, CardTypeId = string.Empty, Status = TenderLineStatus.Historical, IsVoidable = false, TenderDate = Utilities.ParseDateString( payment.DateString, currentChannelDate.Date, DateTimeStyles.None) // On channel timezone }; salesOrder.TenderLines.Add(tenderLine); } if (orderInfo.Affiliations != null && orderInfo.Affiliations.Any()) { salesOrder.AffiliationLoyaltyTierLines.Clear(); salesOrder.AffiliationLoyaltyTierLines.AddRange(orderInfo.Affiliations.Select(line => new SalesAffiliationLoyaltyTier { AffiliationId = line.AffiliationRecordId, LoyaltyTierId = line.LoyaltyTierRecordId, AffiliationType = line.AffiliationType })); } // Prepayment/Deposit override info if (orderInfo.PrepaymentAmountOverridden) { salesOrder.OverriddenDepositAmount = prepaymentAmountPaid; } salesOrder.PrepaymentAmountPaid = prepaymentAmountPaid; // Portion of the prepayment that has been applied to invoices // (total amount invoiced less payments, difference is the deposit applied) salesOrder.PrepaymentAmountInvoiced = orderInfo.PreviouslyInvoicedAmount - nonPrepayments; // if the prepayment invoiced is greater than the total paid as deposit, there is no credit left salesOrder.AvailableDepositAmount = Math.Max(decimal.Zero, salesOrder.PrepaymentAmountPaid - salesOrder.PrepaymentAmountInvoiced); salesOrder.HasLoyaltyPayment = orderInfo.HasLoyaltyPayment; salesOrder.CurrencyCode = orderInfo.CurrencyCode; return(salesOrder); }
/// <summary> /// Add balance to existing gift card. /// </summary> /// <param name="request">The request.</param> /// <returns>A response containing the gift card information.</returns> private static GetGiftCardServiceResponse AddToGiftCard(AddToGiftCardServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); } if (string.IsNullOrWhiteSpace(request.GiftCardId)) { throw new ArgumentException("request.Id is null or empty.", "request"); } if (string.IsNullOrWhiteSpace(request.CurrencyCode)) { throw new ArgumentException("request.Id is null or empty.", "request"); } if (!request.CurrencyCode.Equals(request.RequestContext.GetChannelConfiguration().Currency, StringComparison.OrdinalIgnoreCase)) { throw new PaymentException( PaymentErrors.Microsoft_Dynamics_Commerce_Runtime_GiftCardCurrencyMismatch, string.Format("Amount cannot be added to gift in currency different from channel currency. Currency requested: {0}", request.CurrencyCode)); } // Skip validation if we are reversing operation. if (request.Amount > 0) { ValidateNotAlreadyAdded(request.Transaction, request.GiftCardId); } string giftCardCurrencyCode; GiftCard currentGiftCard; if (request.IsReversal) { // In case of reversal (voiding gift card sales line) gift card we already know gift card currency // so call to GetGiftCardBalance() can be skipped. giftCardCurrencyCode = request.CardCurrencyCode; } else { var getGiftCardBalanceRequest = new GetGiftCardRealtimeRequest(request.GiftCardId); currentGiftCard = request.RequestContext.Execute <SingleEntityDataServiceResponse <GiftCard> >(getGiftCardBalanceRequest).Entity; giftCardCurrencyCode = currentGiftCard.CardCurrencyCode; } GetCurrencyValueServiceResponse convertAmountToGiftCardCurrencyResponse = ToGiftCardCurrency(request.RequestContext, request.Amount, giftCardCurrencyCode); var addToGiftCardRequest = new AddToGiftCardRealtimeRequest( request.GiftCardId, convertAmountToGiftCardCurrencyResponse.RoundedConvertedAmount, convertAmountToGiftCardCurrencyResponse.ToCurrencyCode, request.RequestContext.GetPrincipal().ChannelId, request.RequestContext.GetTerminal() == null ? string.Empty : request.RequestContext.GetTerminal().TerminalId, request.RequestContext.GetPrincipal().UserId, request.Transaction.Id, string.Empty); // ReceiptId is passed as an empty string in EPOS too at this point. GiftCard card = request.RequestContext.Execute <SingleEntityDataServiceResponse <GiftCard> >(addToGiftCardRequest).Entity; GetCurrencyValueServiceResponse convertBalanceToChannelCurrencyResponse = FromGiftCardCurrency(request.RequestContext, card.BalanceInCardCurrency, card.BalanceCurrencyCode); GiftCard giftCard = new GiftCard { Id = request.GiftCardId, BalanceCurrencyCode = convertBalanceToChannelCurrencyResponse.ToCurrencyCode, Balance = convertBalanceToChannelCurrencyResponse.RoundedConvertedAmount, CardCurrencyCode = giftCardCurrencyCode, BalanceInCardCurrency = card.BalanceInCardCurrency }; return(new GetGiftCardServiceResponse(giftCard)); }