From 3306ce6e484d4a08935bf7f6b4994ebc6ebedabf Mon Sep 17 00:00:00 2001 From: RomanovM Date: Mon, 18 Jul 2016 08:45:30 +0300 Subject: [PATCH] Issue 8. Automatically update order totals when editing an order --- .../Nop.Core/Domain/Orders/OrderSettings.cs | 7 +- .../Catalog/IPriceCalculationService.cs | 12 +- .../Catalog/PriceCalculationService.cs | 41 +- .../CodeFirstInstallationService.cs | 1 + .../Nop.Services/Nop.Services.csproj | 1 + .../Nop.Services/Orders/GiftCardService.cs | 5 +- .../Nop.Services/Orders/IGiftCardService.cs | 3 +- .../Orders/IOrderProcessingService.cs | 6 + .../Orders/IOrderTotalCalculationService.cs | 9 +- .../Orders/IRewardPointService.cs | 6 + .../Orders/OrderProcessingService.cs | 112 ++++ .../Orders/OrderTotalCalculationService.cs | 481 ++++++++++++++++-- .../Nop.Services/Orders/RewardPointService.cs | 15 + .../Orders/UpdateOrderParameters.cs | 94 ++++ .../Controllers/OrderController.cs | 72 ++- .../Controllers/SettingController.cs | 2 + .../Mapper/AutoMapperConfiguration.cs | 1 + .../Models/Orders/OrderModel.cs | 5 + .../Models/Settings/OrderSettingsModel.cs | 4 + .../Order/AddProductToOrderDetails.cshtml | 15 +- .../Administration/Views/Order/Edit.cshtml | 11 + .../Administration/Views/Setting/Order.cshtml | 10 + .../Localization/defaultResources.nopres.xml | 6 + .../3.70-the next version/upgrade.sql | 14 + 24 files changed, 879 insertions(+), 54 deletions(-) create mode 100644 src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs index 965c8edbf85..e0cd11e1ea3 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs @@ -24,7 +24,12 @@ public class OrderSettings : ISettings /// Gets or sets a minimum order total amount /// public decimal MinOrderTotalAmount { get; set; } - + + /// + /// Gets or sets a value indicating whether automatically update order totals on editing an order in admin area + /// + public bool AutoUpdateOrderTotalsOnEditingOrder { get; set; } + /// /// Gets or sets a value indicating whether anonymous checkout allowed /// diff --git a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs index 796fb4d754e..6f7d4cd2221 100644 --- a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs @@ -135,8 +135,16 @@ public partial interface IPriceCalculationService out decimal discountAmount, out List appliedDiscounts); - - + /// + /// Gets the shopping cart item sub total with overriden price + /// + /// The updated shopping cart item + /// Overriden price + /// Applied discount amount + /// Applied discounts + /// + decimal GetSubTotalWithOverridenPrice(ShoppingCartItem updatedShoppingCartItem, decimal price, + out decimal discountAmount, out List appliedDiscounts); /// /// Gets the product cost (one item) diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 5878bd35a2d..83f8970b50f 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -739,9 +739,48 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer } return subTotal; } - + /// + /// Gets the shopping cart item sub total with overriden price + /// + /// The updated shopping cart item + /// Overriden price + /// Applied discount amount + /// Applied discounts + /// + public virtual decimal GetSubTotalWithOverridenPrice(ShoppingCartItem updatedShoppingCartItem, decimal price, + out decimal discountAmount, out List appliedDiscounts) + { + discountAmount = GetDiscountAmount(updatedShoppingCartItem.Product, updatedShoppingCartItem.Customer, price, out appliedDiscounts); + var priceWithDiscount = price - discountAmount; + + if (priceWithDiscount < decimal.Zero) + priceWithDiscount = decimal.Zero; + + //rounding + if (_shoppingCartSettings.RoundPricesDuringCalculation) + priceWithDiscount = RoundingHelper.RoundPrice(priceWithDiscount); + + //discount + if (appliedDiscounts == null) + appliedDiscounts = new List(); + if (appliedDiscounts.Count == 1) + { + //we can properly use "MaximumDiscountedQuantity" property only for one discount (not cumulative ones) + var oneAndOnlyDiscount = appliedDiscounts.First(); + if (oneAndOnlyDiscount.MaximumDiscountedQuantity.HasValue && + updatedShoppingCartItem.Quantity > oneAndOnlyDiscount.MaximumDiscountedQuantity.Value) + { + //we cannot apply discount for all shopping cart items + return priceWithDiscount * oneAndOnlyDiscount.MaximumDiscountedQuantity.Value + + price * (updatedShoppingCartItem.Quantity - oneAndOnlyDiscount.MaximumDiscountedQuantity.Value); + } + } + + return priceWithDiscount * updatedShoppingCartItem.Quantity; + } + /// /// Gets the product cost (one item) /// diff --git a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs index 48b57b05707..e8cbd54313c 100644 --- a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs +++ b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs @@ -6013,6 +6013,7 @@ protected virtual void InstallSettings() MinOrderSubtotalAmount = 0, MinOrderSubtotalAmountIncludingTax = false, MinOrderTotalAmount = 0, + AutoUpdateOrderTotalsOnEditingOrder = true, AnonymousCheckoutAllowed = true, TermsOfServiceOnShoppingCartPage = true, TermsOfServiceOnOrderConfirmPage = false, diff --git a/src/Libraries/Nop.Services/Nop.Services.csproj b/src/Libraries/Nop.Services/Nop.Services.csproj index 56662de6c5c..f4e082e4785 100644 --- a/src/Libraries/Nop.Services/Nop.Services.csproj +++ b/src/Libraries/Nop.Services/Nop.Services.csproj @@ -250,6 +250,7 @@ + diff --git a/src/Libraries/Nop.Services/Orders/GiftCardService.cs b/src/Libraries/Nop.Services/Orders/GiftCardService.cs index e9a010d9650..d8d2e1f3f8b 100644 --- a/src/Libraries/Nop.Services/Orders/GiftCardService.cs +++ b/src/Libraries/Nop.Services/Orders/GiftCardService.cs @@ -71,6 +71,7 @@ public virtual GiftCard GetGiftCardById(int giftCardId) /// Gets all gift cards /// /// Associated order ID; null to load all records + /// The order ID in which the gift card was used; null to load all records /// Created date from (UTC); null to load all records /// Created date to (UTC); null to load all records /// Value indicating whether gift card is activated; null to load all records @@ -79,7 +80,7 @@ public virtual GiftCard GetGiftCardById(int giftCardId) /// Page index /// Page size /// Gift cards - public virtual IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, + public virtual IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, int? usedWithOrderId = null, DateTime? createdFromUtc = null, DateTime? createdToUtc = null, bool? isGiftCardActivated = null, string giftCardCouponCode = null, string recipientName = null, @@ -88,6 +89,8 @@ public virtual GiftCard GetGiftCardById(int giftCardId) var query = _giftCardRepository.Table; if (purchasedWithOrderId.HasValue) query = query.Where(gc => gc.PurchasedWithOrderItem != null && gc.PurchasedWithOrderItem.OrderId == purchasedWithOrderId.Value); + if (usedWithOrderId.HasValue) + query = query.Where(gc => gc.GiftCardUsageHistory.Any(history => history.UsedWithOrderId == usedWithOrderId)); if (createdFromUtc.HasValue) query = query.Where(gc => createdFromUtc.Value <= gc.CreatedOnUtc); if (createdToUtc.HasValue) diff --git a/src/Libraries/Nop.Services/Orders/IGiftCardService.cs b/src/Libraries/Nop.Services/Orders/IGiftCardService.cs index 2acec2f630a..14e1ac7fb80 100644 --- a/src/Libraries/Nop.Services/Orders/IGiftCardService.cs +++ b/src/Libraries/Nop.Services/Orders/IGiftCardService.cs @@ -28,6 +28,7 @@ public partial interface IGiftCardService /// Gets all gift cards /// /// Associated order ID; null to load all records + /// The order ID in which the gift card was used; null to load all records /// Created date from (UTC); null to load all records /// Created date to (UTC); null to load all records /// Value indicating whether gift card is activated; null to load all records @@ -36,7 +37,7 @@ public partial interface IGiftCardService /// Page index /// Page size /// Gift cards - IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, + IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, int? usedWithOrderId = null, DateTime? createdFromUtc = null, DateTime? createdToUtc = null, bool? isGiftCardActivated = null, string giftCardCouponCode = null, string recipientName = null, diff --git a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs index bf38bb4322d..a8913b17aa0 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs @@ -25,6 +25,12 @@ public partial interface IOrderProcessingService /// Place order result PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest); + /// + /// Update order totals + /// + /// Parameters for the updating order + void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters); + /// /// Deletes an order /// diff --git a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs index 9ac887f3fff..1f27e014c07 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs @@ -65,8 +65,9 @@ public partial interface IOrderTotalCalculationService /// Gets a value indicating whether shipping is free /// /// Cart + /// Subtotal amount; pass null to calculate subtotal /// A value indicating whether shipping is free - bool IsFreeShipping(IList cart); + bool IsFreeShipping(IList cart, decimal? subTotal = null); /// /// Gets shopping cart shipping total @@ -161,6 +162,12 @@ public partial interface IOrderTotalCalculationService + /// + /// Update order totals + /// + /// Parameters for the updating order + /// Shopping cart + void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart); /// /// Converts existing reward points to amount diff --git a/src/Libraries/Nop.Services/Orders/IRewardPointService.cs b/src/Libraries/Nop.Services/Orders/IRewardPointService.cs index d8408cc7427..39932d3e188 100644 --- a/src/Libraries/Nop.Services/Orders/IRewardPointService.cs +++ b/src/Libraries/Nop.Services/Orders/IRewardPointService.cs @@ -41,5 +41,11 @@ public partial interface IRewardPointService /// Store identifier; pass /// Balance int GetRewardPointsBalance(int customerId, int storeId); + + /// + /// Updates the reward point history entry + /// + /// Reward point history entry + void UpdateRewardPointsHistoryEntry(RewardPointsHistory rewardPointsHistory); } } diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index abfff472ccd..90988579ab3 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -1395,6 +1395,118 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR return result; } + /// + /// Update order totals + /// + /// Parameters for the updating order + public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters) + { + if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) + return; + + var updatedOrder = updateOrderParameters.UpdatedOrder; + var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; + + //restore shopping cart from order items + var restoredCart = updatedOrder.OrderItems.Select(orderItem => new ShoppingCartItem + { + Id = orderItem.Id, + AttributesXml = orderItem.AttributesXml, + Customer = updatedOrder.Customer, + Product = orderItem.Product, + Quantity = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.Quantity : orderItem.Quantity, + RentalEndDateUtc = orderItem.RentalEndDateUtc, + RentalStartDateUtc = orderItem.RentalStartDateUtc, + ShoppingCartType = ShoppingCartType.ShoppingCart, + StoreId = updatedOrder.StoreId + }).ToList(); + + //get shopping cart item which has been updated + var updatedShoppingCartItem = restoredCart.FirstOrDefault(shoppingCartItem => shoppingCartItem.Id == updatedOrderItem.Id); + var itemDeleted = updatedShoppingCartItem == null; + + //validate shopping cart for warnings + updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartWarnings(restoredCart, string.Empty, false)); + if (!itemDeleted) + updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartItemWarnings(updatedOrder.Customer, updatedShoppingCartItem.ShoppingCartType, + updatedShoppingCartItem.Product, updatedOrder.StoreId, updatedShoppingCartItem.AttributesXml, updatedShoppingCartItem.CustomerEnteredPrice, + updatedShoppingCartItem.RentalStartDateUtc, updatedShoppingCartItem.RentalEndDateUtc, updatedShoppingCartItem.Quantity, false)); + + _orderTotalCalculationService.UpdateOrderTotals(updateOrderParameters, restoredCart); + + if (updateOrderParameters.PickupPoint != null) + { + updatedOrder.PickUpInStore = true; + updatedOrder.PickupAddress = new Address + { + Address1 = updateOrderParameters.PickupPoint.Address, + City = updateOrderParameters.PickupPoint.City, + Country = _countryService.GetCountryByTwoLetterIsoCode(updateOrderParameters.PickupPoint.CountryCode), + ZipPostalCode = updateOrderParameters.PickupPoint.ZipPostalCode, + CreatedOnUtc = DateTime.UtcNow, + }; + updatedOrder.ShippingMethod = string.Format(_localizationService.GetResource("Checkout.PickupPoints.Name"), updateOrderParameters.PickupPoint.Name); + updatedOrder.ShippingRateComputationMethodSystemName = updateOrderParameters.PickupPoint.ProviderSystemName; + } + + if (!itemDeleted) + { + updatedOrderItem.ItemWeight = _shippingService.GetShoppingCartItemWeight(updatedShoppingCartItem); + updatedOrderItem.OriginalProductCost = _priceCalculationService.GetProductCost(updatedShoppingCartItem.Product, updatedShoppingCartItem.AttributesXml); + updatedOrderItem.AttributeDescription = _productAttributeFormatter.FormatAttributes(updatedShoppingCartItem.Product, + updatedShoppingCartItem.AttributesXml, updatedOrder.Customer); + + //gift cards + if (updatedShoppingCartItem.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(updatedShoppingCartItem.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < updatedShoppingCartItem.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = updatedShoppingCartItem.Product.GiftCardType, + PurchasedWithOrderItem = updatedOrderItem, + Amount = updatedShoppingCartItem.Product.OverriddenGiftCardAmount.HasValue ? + updatedShoppingCartItem.Product.OverriddenGiftCardAmount.Value : updatedOrderItem.UnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + } + + _orderService.UpdateOrder(updatedOrder); + + //discount usage history + var discountUsageHistoryForOrder = _discountService.GetAllDiscountUsageHistory(null, updatedOrder.Customer.Id, updatedOrder.Id); + foreach (var discount in updateOrderParameters.AppliedDiscounts) + { + if (!discountUsageHistoryForOrder.Where(history => history.DiscountId == discount.Id).Any()) + _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory + { + Discount = discount, + Order = updatedOrder, + CreatedOnUtc = DateTime.UtcNow + }); + } + + CheckOrderStatus(updatedOrder); + } + /// /// Deletes an order /// diff --git a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs index a43cdcbc1ca..53cc9dd4ccc 100644 --- a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Nop.Core; using Nop.Core.Domain.Catalog; @@ -202,7 +203,7 @@ protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal order return discountAmount; } - + #endregion #region Methods @@ -383,9 +384,436 @@ protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal order subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount); } + /// + /// Update order totals + /// + /// Parameters for the updating order + /// Shopping cart + public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart) + { + var updatedOrder = updateOrderParameters.UpdatedOrder; + var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; + + //get the customer + var customer = restoredCart.GetCustomer(); + + #region Sub total + + var subTotalExclTax = decimal.Zero; + var subTotalInclTax = decimal.Zero; + var subTotalTaxRates = new SortedDictionary(); + + foreach (var shoppingCartItem in restoredCart) + { + var itemSubTotalExclTax = decimal.Zero; + var itemSubTotalInclTax = decimal.Zero; + var taxRate = decimal.Zero; + var itemDiscounts = new List(); + + //calculate subtotal for the updated order item + if (shoppingCartItem.Id == updatedOrderItem.Id) + { + var itemPriceExclTax = updateOrderParameters.PriceExclTax; + var itemPriceInclTax = updateOrderParameters.PriceInclTax; + var itemDiscountAmountExclTax = updateOrderParameters.DiscountAmountExclTax; + var itemDiscountAmountInclTax = updateOrderParameters.DiscountAmountInclTax; + itemSubTotalExclTax = updateOrderParameters.SubTotalExclTax; + itemSubTotalInclTax = updateOrderParameters.SubTotalInclTax; + + //if subtotal values were manually changed get them, else need some calculation + if (!updateOrderParameters.SubTotalChanged) + { + if (updateOrderParameters.DiscountAmountChanged) + { + if (updateOrderParameters.PriceChanged) + { + //price and discount amount were manually changed, not need for the complex calculation + itemSubTotalExclTax = (itemPriceExclTax - itemDiscountAmountExclTax) * shoppingCartItem.Quantity; + itemSubTotalInclTax = (itemPriceInclTax - itemDiscountAmountInclTax) * shoppingCartItem.Quantity; + } + else + { + //discount amount was manually changed, calculate price and subtotal with entered discount amount + var itemPrice = _priceCalculationService.GetUnitPrice(shoppingCartItem, false); + itemPriceExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemPrice, false, customer, out taxRate); + itemPriceInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemPrice, true, customer, out taxRate); + itemSubTotalExclTax = (itemPriceExclTax - itemDiscountAmountExclTax) * shoppingCartItem.Quantity; + itemSubTotalInclTax = (itemPriceInclTax - itemDiscountAmountInclTax) * shoppingCartItem.Quantity; + } + } + else + { + if (updateOrderParameters.PriceChanged) + { + //price was manually changed, calculate discount amount and subtotal + itemSubTotalExclTax = _priceCalculationService.GetSubTotalWithOverridenPrice(shoppingCartItem, updateOrderParameters.PriceExclTax, + out itemDiscountAmountExclTax, out itemDiscounts); + itemSubTotalInclTax = _priceCalculationService.GetSubTotalWithOverridenPrice(shoppingCartItem, updateOrderParameters.PriceInclTax, + out itemDiscountAmountInclTax, out itemDiscounts); + } + else + { + //only quantity was changed or added new order item, calculate all + var itemPrice = _priceCalculationService.GetUnitPrice(shoppingCartItem); + var itemSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem, true, out itemDiscountAmountExclTax, out itemDiscounts); + + itemPriceExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemPrice, false, customer, out taxRate); + itemPriceInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemPrice, true, customer, out taxRate); + itemDiscountAmountExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemDiscountAmountExclTax, false, customer, out taxRate); + itemDiscountAmountInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemDiscountAmountExclTax, true, customer, out taxRate); + itemSubTotalExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemSubTotal, false, customer, out taxRate); + itemSubTotalInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, itemSubTotal, true, customer, out taxRate); + } + } + } + + //update order item + updatedOrderItem.UnitPriceExclTax = itemPriceExclTax; + updatedOrderItem.UnitPriceInclTax = itemPriceInclTax; + updatedOrderItem.DiscountAmountExclTax = itemDiscountAmountExclTax; + updatedOrderItem.DiscountAmountInclTax = itemDiscountAmountInclTax; + updatedOrderItem.PriceExclTax = itemSubTotalExclTax; + updatedOrderItem.PriceInclTax = itemSubTotalInclTax; + updatedOrderItem.Quantity = shoppingCartItem.Quantity; + } + else + { + //get the already calculated subtotal from the order item + itemSubTotalExclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceExclTax; + itemSubTotalInclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceInclTax; + taxRate = Math.Round((100 * (itemSubTotalInclTax - itemSubTotalExclTax)) / itemSubTotalExclTax, 3); + } + + foreach (var discount in itemDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + subTotalExclTax += itemSubTotalExclTax; + subTotalInclTax += itemSubTotalInclTax; + + //tax rates + var itemTaxValue = itemSubTotalInclTax - itemSubTotalExclTax; + if (taxRate > decimal.Zero && itemTaxValue > decimal.Zero) + { + if (!subTotalTaxRates.ContainsKey(taxRate)) + subTotalTaxRates.Add(taxRate, itemTaxValue); + else + subTotalTaxRates[taxRate] = subTotalTaxRates[taxRate] + itemTaxValue; + } + } + + if (subTotalExclTax < decimal.Zero) + subTotalExclTax = decimal.Zero; + + if (subTotalInclTax < decimal.Zero) + subTotalInclTax = decimal.Zero; + + //We calculate discount amount on order subtotal excl tax (discount first) + //calculate discount amount ('Applied to order subtotal' discount) + List subTotalDiscounts; + var discountAmountExclTax = GetOrderSubtotalDiscount(customer, subTotalExclTax, out subTotalDiscounts); + if (subTotalExclTax < discountAmountExclTax) + discountAmountExclTax = subTotalExclTax; + var discountAmountInclTax = discountAmountExclTax; + + //add tax for shopping items + var tempTaxRates = new Dictionary(subTotalTaxRates); + foreach (var kvp in tempTaxRates) + { + if (kvp.Value != decimal.Zero && subTotalExclTax > decimal.Zero) + { + var discountTaxValue = kvp.Value * (discountAmountExclTax / subTotalExclTax); + discountAmountInclTax += discountTaxValue; + subTotalTaxRates[kvp.Key] = kvp.Value - discountTaxValue; + } + } + + //rounding + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + subTotalExclTax = RoundingHelper.RoundPrice(subTotalExclTax); + subTotalInclTax = RoundingHelper.RoundPrice(subTotalInclTax); + discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax); + discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax); + } + + updatedOrder.OrderSubtotalExclTax = subTotalExclTax; + updatedOrder.OrderSubtotalInclTax = subTotalInclTax; + updatedOrder.OrderSubTotalDiscountExclTax = discountAmountExclTax; + updatedOrder.OrderSubTotalDiscountInclTax = discountAmountInclTax; + + foreach (var discount in subTotalDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + #endregion + + #region Shipping + + var shippingTotalExclTax = decimal.Zero; + var shippingTotalInclTax = decimal.Zero; + var shippingTaxRate = decimal.Zero; + + if (restoredCart.RequiresShipping()) + { + if (!IsFreeShipping(restoredCart, _shippingSettings.FreeShippingOverXIncludingTax ? subTotalInclTax : subTotalExclTax)) + { + var shippingTotal = decimal.Zero; + if (!string.IsNullOrEmpty(updatedOrder.ShippingRateComputationMethodSystemName)) + { + //in the updated order were shipping items + if (updatedOrder.PickUpInStore) + { + //customer chose pickup in store method, try to get chosen pickup point + if (_shippingSettings.AllowPickUpInStore) + { + var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, + updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); + if (pickupPointsResponse.Success) + { + var selectedPickupPoint = pickupPointsResponse.PickupPoints.FirstOrDefault(point => updatedOrder.ShippingMethod.Contains(point.Name)); + if (selectedPickupPoint != null) + shippingTotal = selectedPickupPoint.PickupFee; + else + updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); + } + else + updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Pick up in store is not available"); + } + else + { + //customer chose shipping to address, try to get chosen shipping option + var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, + updatedOrder.ShippingAddress, updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); + if (shippingOptionsResponse.Success) + { + var shippingOption = shippingOptionsResponse.ShippingOptions.FirstOrDefault(option => updatedOrder.ShippingMethod.Contains(option.Name)); + if (shippingOption != null) + shippingTotal = shippingOption.Rate; + else + updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); + } + else + updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); + } + } + else + { + //before updating order was without shipping + if (_shippingSettings.AllowPickUpInStore) + { + //try to get the cheapest pickup point + var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, null, _storeContext.CurrentStore.Id); + if (pickupPointsResponse.Success) + { + updateOrderParameters.PickupPoint = pickupPointsResponse.PickupPoints.OrderBy(point => point.PickupFee).First(); + shippingTotal = updateOrderParameters.PickupPoint.PickupFee; + } + else + updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Pick up in store is not available"); + + if (updateOrderParameters.PickupPoint == null) + { + //or try to get the cheapest shipping option for the shipping to the customer address + var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_storeContext.CurrentStore.Id); + if (shippingRateComputationMethods.Any()) + { + var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, customer.ShippingAddress, null, _storeContext.CurrentStore.Id); + if (shippingOptionsResponse.Success) + { + var shippingOption = shippingOptionsResponse.ShippingOptions.OrderBy(option => option.Rate).First(); + updatedOrder.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; + updatedOrder.ShippingMethod = shippingOption.Name; + updatedOrder.ShippingAddress = (Address)customer.ShippingAddress.Clone(); + shippingTotal = shippingOption.Rate; + } + else + updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Shipping rate computation method could not be loaded"); + } + } + + //additional shipping charge + shippingTotal += restoredCart.Where(item => item.IsShipEnabled && !item.IsFreeShipping).Sum(item => item.Product.AdditionalShippingCharge); + + //shipping discounts + List shippingTotalDiscounts; + shippingTotal -= GetShippingDiscount(customer, shippingTotal, out shippingTotalDiscounts); + if (shippingTotal < decimal.Zero) + shippingTotal = decimal.Zero; + + shippingTotalExclTax = _taxService.GetShippingPrice(shippingTotal, false, customer); + shippingTotalInclTax = _taxService.GetShippingPrice(shippingTotal, true, customer, out shippingTaxRate); + + //rounding + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + shippingTotalExclTax = RoundingHelper.RoundPrice(shippingTotalExclTax); + shippingTotalInclTax = RoundingHelper.RoundPrice(shippingTotalInclTax); + } + + //change shipping status + if (updatedOrder.ShippingStatus == ShippingStatus.ShippingNotRequired || updatedOrder.ShippingStatus == ShippingStatus.NotYetShipped) + updatedOrder.ShippingStatus = ShippingStatus.NotYetShipped; + else + updatedOrder.ShippingStatus = ShippingStatus.PartiallyShipped; + + foreach (var discount in shippingTotalDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + } + } + else + updatedOrder.ShippingStatus = ShippingStatus.ShippingNotRequired; + + updatedOrder.OrderShippingExclTax = shippingTotalExclTax; + updatedOrder.OrderShippingInclTax = shippingTotalInclTax; + + #endregion + + #region Tax rates + + var taxRates = new SortedDictionary(); + + //order subtotal taxes + var subTotalTax = decimal.Zero; + foreach (var kvp in subTotalTaxRates) + { + subTotalTax += kvp.Value; + if (kvp.Key > decimal.Zero && kvp.Value > decimal.Zero) + { + if (!taxRates.ContainsKey(kvp.Key)) + taxRates.Add(kvp.Key, kvp.Value); + else + taxRates[kvp.Key] = taxRates[kvp.Key] + kvp.Value; + } + } + + //shipping taxes + var shippingTax = decimal.Zero; + if (_taxSettings.ShippingIsTaxable) + { + shippingTax = shippingTotalInclTax - shippingTotalExclTax; + if (shippingTax < decimal.Zero) + shippingTax = decimal.Zero; + if (shippingTaxRate > decimal.Zero && shippingTax > decimal.Zero) + { + if (!taxRates.ContainsKey(shippingTaxRate)) + taxRates.Add(shippingTaxRate, shippingTax); + else + taxRates[shippingTaxRate] = taxRates[shippingTaxRate] + shippingTax; + } + } + //payment method additional fee tax + var paymentMethodAdditionalFeeTax = decimal.Zero; + if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable) + { + paymentMethodAdditionalFeeTax = updatedOrder.PaymentMethodAdditionalFeeInclTax - updatedOrder.PaymentMethodAdditionalFeeExclTax; + if (paymentMethodAdditionalFeeTax < decimal.Zero) + paymentMethodAdditionalFeeTax = decimal.Zero; + var paymentTaxRate = Math.Round(100 * paymentMethodAdditionalFeeTax / updatedOrder.PaymentMethodAdditionalFeeExclTax, 3); + if (paymentTaxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero) + { + if (!taxRates.ContainsKey(paymentTaxRate)) + taxRates.Add(paymentTaxRate, paymentMethodAdditionalFeeTax); + else + taxRates[paymentTaxRate] = taxRates[paymentTaxRate] + paymentMethodAdditionalFeeTax; + } + } + + //add at least one tax rate (0%) + if (!taxRates.Any()) + taxRates.Add(decimal.Zero, decimal.Zero); + + //summarize taxes + var taxTotal = subTotalTax + shippingTax + paymentMethodAdditionalFeeTax; + if (taxTotal < decimal.Zero) + taxTotal = decimal.Zero; + + //round tax + if (_shoppingCartSettings.RoundPricesDuringCalculation) + taxTotal = RoundingHelper.RoundPrice(taxTotal); + + updatedOrder.OrderTax = taxTotal; + updatedOrder.TaxRates = taxRates.Aggregate(string.Empty, (current, next) => + string.Format("{0}{1}:{2}; ", current, next.Key.ToString(CultureInfo.InvariantCulture), next.Value.ToString(CultureInfo.InvariantCulture))); + + #endregion + + #region Total + + var total = (subTotalExclTax - discountAmountExclTax) + shippingTotalExclTax + updatedOrder.PaymentMethodAdditionalFeeExclTax + taxTotal; + + //get discounts for the order total + List orderAppliedDiscounts; + var discountAmountTotal = GetOrderTotalDiscount(customer, total, out orderAppliedDiscounts); + if (total < discountAmountTotal) + discountAmountTotal = total; + total -= discountAmountTotal; + + //applied giftcards + foreach (var giftCard in _giftCardService.GetAllGiftCards(usedWithOrderId: updatedOrder.Id)) + { + if (total > decimal.Zero) + { + var remainingAmount = giftCard.GiftCardUsageHistory.Where(history => history.UsedWithOrderId == updatedOrder.Id).Sum(history => history.UsedValue); + var amountCanBeUsed = total > remainingAmount ? remainingAmount : total; + total -= amountCanBeUsed; + } + } + + //reward points + var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder); + if (rewardPointsOfOrder != null) + { + var rewardPoints = -rewardPointsOfOrder.Points; + var rewardPointsAmount = ConvertRewardPointsToAmount(rewardPoints); + if (total < rewardPointsAmount) + { + rewardPoints = ConvertAmountToRewardPoints(total); + rewardPointsAmount = total; + } + if (total > decimal.Zero) + total -= rewardPointsAmount; + + //uncomment here for the return unused reward points if new order total less redeemed reward points amount + //if (rewardPoints < -rewardPointsOfOrder.Points) + // _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, _storeContext.CurrentStore.Id, "Return unused reward points"); + + if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount) + { + rewardPointsOfOrder.UsedAmount = rewardPointsAmount; + rewardPointsOfOrder.Points = -rewardPoints; + _rewardPointService.UpdateRewardPointsHistoryEntry(rewardPointsOfOrder); + } + } + + //rounding + if (total < decimal.Zero) + total = decimal.Zero; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + total = RoundingHelper.RoundPrice(total); + + updatedOrder.OrderDiscount = discountAmountTotal; + updatedOrder.OrderTotal = total; + + foreach (var discount in orderAppliedDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + #endregion + } /// /// Gets shopping cart additional shipping charge @@ -411,52 +839,41 @@ public virtual decimal GetShoppingCartAdditionalShippingCharge(IList /// Cart + /// Subtotal amount; pass null to calculate subtotal /// A value indicating whether shipping is free - public virtual bool IsFreeShipping(IList cart) + public virtual bool IsFreeShipping(IList cart, decimal? subTotal = null) { - Customer customer = cart.GetCustomer(); - if (customer != null) - { - //check whether customer is in a customer role with free shipping applied - var customerRoles = customer.CustomerRoles.Where(cr => cr.Active); - foreach (var customerRole in customerRoles) - if (customerRole.FreeShipping) - return true; - } + if (!cart.RequiresShipping()) + return true; - bool shoppingCartRequiresShipping = cart.RequiresShipping(); - if (!shoppingCartRequiresShipping) + //check whether customer is in a customer role with free shipping applied + var customer = cart.GetCustomer(); + if (customer != null && customer.CustomerRoles.Where(role => role.Active).Any(role => role.FreeShipping)) return true; //check whether all shopping cart items are marked as free shipping - bool allItemsAreFreeShipping = true; - foreach (var sc in cart) - { - if (sc.IsShipEnabled && !sc.IsFreeShipping) - { - allItemsAreFreeShipping = false; - break; - } - } - if (allItemsAreFreeShipping) + if (cart.All(item => item.IsShipEnabled && item.IsFreeShipping)) return true; //free shipping over $X if (_shippingSettings.FreeShippingOverXEnabled) { + if (!subTotal.HasValue) + { + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + GetShoppingCartSubTotal(cart, _shippingSettings.FreeShippingOverXIncludingTax, out discountAmount, + out appliedDiscounts, out subTotalWithoutDiscount, out subTotalWithDiscount); + subTotal = subTotalWithDiscount; + } + //check whether we have subtotal enough to have free shipping - decimal subTotalDiscountAmount; - List subTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - GetShoppingCartSubTotal(cart, _shippingSettings.FreeShippingOverXIncludingTax, out subTotalDiscountAmount, - out subTotalAppliedDiscounts, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - - if (subTotalWithDiscountBase > _shippingSettings.FreeShippingOverXValue) + if (subTotal.Value > _shippingSettings.FreeShippingOverXValue) return true; } - //otherwise, return false return false; } diff --git a/src/Libraries/Nop.Services/Orders/RewardPointService.cs b/src/Libraries/Nop.Services/Orders/RewardPointService.cs index a6773e9adb9..d3d579bb05f 100644 --- a/src/Libraries/Nop.Services/Orders/RewardPointService.cs +++ b/src/Libraries/Nop.Services/Orders/RewardPointService.cs @@ -129,6 +129,21 @@ public virtual int GetRewardPointsBalance(int customerId, int storeId) return lastRph != null ? lastRph.PointsBalance : 0; } + /// + /// Updates the reward point history entry + /// + /// Reward point history entry + public virtual void UpdateRewardPointsHistoryEntry(RewardPointsHistory rewardPointsHistory) + { + if (rewardPointsHistory == null) + throw new ArgumentNullException("rewardPointsHistory"); + + _rphRepository.Update(rewardPointsHistory); + + //event notification + _eventPublisher.EntityUpdated(rewardPointsHistory); + } + #endregion } } diff --git a/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs new file mode 100644 index 00000000000..d3cdd36e923 --- /dev/null +++ b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; + +namespace Nop.Services.Orders +{ + /// + /// Parameters for the updating order totals + /// + public class UpdateOrderParameters + { + public UpdateOrderParameters() + { + Warnings = new List(); + AppliedDiscounts = new List(); + } + + /// + /// The updated order + /// + public Order UpdatedOrder { get; set; } + + /// + /// The updated order item + /// + public OrderItem UpdatedOrderItem { get; set; } + + /// + /// The price of item with tax + /// + public decimal PriceInclTax { get; set; } + + /// + /// The price of item without tax + /// + public decimal PriceExclTax { get; set; } + + /// + /// The quantity + /// + public int Quantity { get; set; } + + /// + /// The amount of discount with tax + /// + public decimal DiscountAmountInclTax { get; set; } + + /// + /// The amount of discount without tax + /// + public decimal DiscountAmountExclTax { get; set; } + + /// + /// Subtotal of item with tax + /// + public decimal SubTotalInclTax { get; set; } + + /// + /// Subtotal of item without tax + /// + public decimal SubTotalExclTax { get; set; } + + /// + /// Gets or sets a value indicating whether price was changed + /// + public bool PriceChanged { get; set; } + + /// + /// Gets or sets a value indicating whether discount amount was changed + /// + public bool DiscountAmountChanged { get; set; } + + /// + /// Gets or sets a value indicating whether subtotal was changed + /// + public bool SubTotalChanged { get; set; } + + /// + /// Warnings + /// + public List Warnings { get; set; } + + /// + /// Applied discounts + /// + public List AppliedDiscounts { get; set; } + + /// + /// Pickup point + /// + public PickupPoint PickupPoint { get; set; } + } +} diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index 4aacf2713d3..73144644bf0 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -826,7 +826,8 @@ protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAdd UnitPriceInclTax = presetPriceInclTax, Quantity = presetQty, SubTotalExclTax = presetPriceExclTax, - SubTotalInclTax = presetPriceInclTax + SubTotalInclTax = presetPriceInclTax, + AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder }; //attributes @@ -1783,6 +1784,10 @@ public ActionResult Edit(int id) var model = new OrderModel(); PrepareOrderDetailsModel(model, order); + var warnings = TempData["nop.admin.order.warnings"] as List; + if (warnings != null) + model.Warnings = warnings; + return View(model); } @@ -2138,14 +2143,17 @@ public ActionResult EditOrderItem(int id, FormCollection form) { int qtyDifference = orderItem.Quantity - quantity; - orderItem.UnitPriceInclTax = unitPriceInclTax; - orderItem.UnitPriceExclTax = unitPriceExclTax; - orderItem.Quantity = quantity; - orderItem.DiscountAmountInclTax = discountInclTax; - orderItem.DiscountAmountExclTax = discountExclTax; - orderItem.PriceInclTax = priceInclTax; - orderItem.PriceExclTax = priceExclTax; - _orderService.UpdateOrder(order); + if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) + { + orderItem.UnitPriceInclTax = unitPriceInclTax; + orderItem.UnitPriceExclTax = unitPriceExclTax; + orderItem.Quantity = quantity; + orderItem.DiscountAmountInclTax = discountInclTax; + orderItem.DiscountAmountExclTax = discountExclTax; + orderItem.PriceInclTax = priceInclTax; + orderItem.PriceExclTax = priceExclTax; + _orderService.UpdateOrder(order); + } //adjust inventory _productService.AdjustInventory(orderItem.Product, qtyDifference, orderItem.AttributesXml); @@ -2160,6 +2168,24 @@ public ActionResult EditOrderItem(int id, FormCollection form) _orderService.DeleteOrderItem(orderItem); } + //update order totals + var updateOrderParameters = new UpdateOrderParameters + { + UpdatedOrder = order, + UpdatedOrderItem = orderItem, + PriceInclTax = unitPriceInclTax, + PriceExclTax = unitPriceExclTax, + DiscountAmountInclTax = discountInclTax, + DiscountAmountExclTax = discountExclTax, + SubTotalInclTax = priceInclTax, + SubTotalExclTax = priceExclTax, + Quantity = quantity, + PriceChanged = unitPriceInclTax != orderItem.UnitPriceInclTax || unitPriceExclTax != orderItem.UnitPriceExclTax, + DiscountAmountChanged = discountInclTax != orderItem.DiscountAmountInclTax || discountExclTax != orderItem.DiscountAmountExclTax, + SubTotalChanged = priceInclTax != orderItem.PriceInclTax || priceExclTax != orderItem.PriceExclTax + }; + _orderProcessingService.UpdateOrderTotals(updateOrderParameters); + //add a note order.OrderNotes.Add(new OrderNote { @@ -2172,6 +2198,7 @@ public ActionResult EditOrderItem(int id, FormCollection form) var model = new OrderModel(); PrepareOrderDetailsModel(model, order); + model.Warnings = updateOrderParameters.Warnings; //selected tab SaveSelectedTabName(persistForTheNextRequest: false); @@ -2230,6 +2257,16 @@ public ActionResult DeleteOrderItem(int id, FormCollection form) //delete item _orderService.DeleteOrderItem(orderItem); + //update order totals + var updateOrderParameters = new UpdateOrderParameters + { + UpdatedOrder = order, + UpdatedOrderItem = orderItem + }; + _orderProcessingService.UpdateOrderTotals(updateOrderParameters); + + + //add a note order.OrderNotes.Add(new OrderNote { @@ -2242,6 +2279,7 @@ public ActionResult DeleteOrderItem(int id, FormCollection form) var model = new OrderModel(); PrepareOrderDetailsModel(model, order); + model.Warnings = updateOrderParameters.Warnings; //selected tab SaveSelectedTabName(persistForTheNextRequest: false); @@ -2632,6 +2670,21 @@ public ActionResult AddProductToOrderDetails(int orderId, int productId, FormCol //adjust inventory _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml); + //update order totals + var updateOrderParameters = new UpdateOrderParameters + { + UpdatedOrder = order, + UpdatedOrderItem = orderItem, + PriceInclTax = unitPriceInclTax, + PriceExclTax = unitPriceExclTax, + SubTotalInclTax = priceInclTax, + SubTotalExclTax = priceExclTax, + Quantity = quantity, + PriceChanged = unitPriceInclTax != orderItem.UnitPriceInclTax || unitPriceExclTax != orderItem.UnitPriceExclTax, + SubTotalChanged = priceInclTax != orderItem.PriceInclTax || priceExclTax != orderItem.PriceExclTax + }; + _orderProcessingService.UpdateOrderTotals(updateOrderParameters); + //add a note order.OrderNotes.Add(new OrderNote { @@ -2667,6 +2720,7 @@ public ActionResult AddProductToOrderDetails(int orderId, int productId, FormCol } //redirect to order details page + TempData["nop.admin.order.warnings"] = updateOrderParameters.Warnings; return RedirectToAction("Edit", "Order", new { id = order.Id }); } diff --git a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs index ccbc5d20e0a..7d8a299f411 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs @@ -1058,6 +1058,7 @@ public ActionResult Order() model.MinOrderSubtotalAmount_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.MinOrderSubtotalAmount, storeScope); model.MinOrderSubtotalAmountIncludingTax_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.MinOrderSubtotalAmountIncludingTax, storeScope); model.MinOrderTotalAmount_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.MinOrderTotalAmount, storeScope); + model.AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.AutoUpdateOrderTotalsOnEditingOrder, storeScope); model.AnonymousCheckoutAllowed_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.AnonymousCheckoutAllowed, storeScope); model.TermsOfServiceOnShoppingCartPage_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.TermsOfServiceOnShoppingCartPage, storeScope); model.TermsOfServiceOnOrderConfirmPage_OverrideForStore = _settingService.SettingExists(orderSettings, x => x.TermsOfServiceOnOrderConfirmPage, storeScope); @@ -1107,6 +1108,7 @@ public ActionResult Order(OrderSettingsModel model) _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.MinOrderSubtotalAmount, model.MinOrderSubtotalAmount_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.MinOrderSubtotalAmountIncludingTax, model.MinOrderSubtotalAmountIncludingTax_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.MinOrderTotalAmount, model.MinOrderTotalAmount_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.AutoUpdateOrderTotalsOnEditingOrder, model.AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.AnonymousCheckoutAllowed, model.AnonymousCheckoutAllowed_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.TermsOfServiceOnShoppingCartPage, model.TermsOfServiceOnShoppingCartPage_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.TermsOfServiceOnOrderConfirmPage, model.TermsOfServiceOnOrderConfirmPage_OverrideForStore, storeScope, false); diff --git a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AutoMapperConfiguration.cs b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AutoMapperConfiguration.cs index be02faecf2c..571669a4571 100644 --- a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AutoMapperConfiguration.cs +++ b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AutoMapperConfiguration.cs @@ -871,6 +871,7 @@ public static void Init() .ForMember(dest => dest.MinOrderSubtotalAmount_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.MinOrderSubtotalAmountIncludingTax_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.MinOrderTotalAmount_OverrideForStore, mo => mo.Ignore()) + .ForMember(dest => dest.AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.AnonymousCheckoutAllowed_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.TermsOfServiceOnShoppingCartPage_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.TermsOfServiceOnOrderConfirmPage_OverrideForStore, mo => mo.Ignore()) diff --git a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs index 7d4b4fac6c7..d9b2ed5a84b 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs @@ -19,6 +19,7 @@ public OrderModel() GiftCards = new List(); Items = new List(); UsedDiscounts = new List(); + Warnings = new List(); } public bool IsLoggedInAsVendor { get; set; } @@ -232,6 +233,9 @@ public OrderModel() public bool CanVoid { get; set; } public bool CanVoidOffline { get; set; } + //warnings + public List Warnings { get; set; } + #region Nested Classes public partial class OrderItemModel : BaseNopEntityModel @@ -408,6 +412,7 @@ public ProductDetailsModel() /// public bool HasCondition { get; set; } + public bool AutoUpdateOrderTotals { get; set; } } public partial class ProductAttributeModel : BaseNopEntityModel diff --git a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs index 9b4dd78c079..f64cd816077 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -35,6 +35,10 @@ public OrderSettingsModel() public decimal MinOrderTotalAmount { get; set; } public bool MinOrderTotalAmount_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.Order.AutoUpdateOrderTotalsOnEditingOrder")] + public bool AutoUpdateOrderTotalsOnEditingOrder { get; set; } + public bool AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.Order.AnonymousCheckoutAllowed")] public bool AnonymousCheckoutAllowed { get; set; } public bool AnonymousCheckoutAllowed_OverrideForStore { get; set; } diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml index 1aa92f83a3b..17ef294bc0f 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml @@ -99,13 +99,16 @@
-
-
-

- @T("Admin.Orders.Products.AddNew.UpdateTotals") -

+ @if (!Model.AutoUpdateOrderTotals) + { +
+
+

+ @T("Admin.Orders.Products.AddNew.UpdateTotals") +

+
-
+ }