From 5b141729e1a3ede0f7242a5bae82aae6e3e4bd6e Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Mon, 23 Jan 2017 00:07:25 +0100 Subject: [PATCH] VAT Rounding: Vat for Product Attributes and fixes --- src/Libraries/Nop.Core/Domain/Orders/Order.cs | 1074 +- src/Libraries/Nop.Core/Html/HtmlHelper.cs | 459 +- .../Catalog/IPriceCalculationService.cs | 316 +- .../Catalog/IProductAttributeFormatter.cs | 76 +- .../Catalog/IProductAttributeParser.cs | 261 +- .../Catalog/PriceCalculationService.cs | 1537 +-- .../Catalog/ProductAttributeFormatter.cs | 519 +- .../Catalog/ProductAttributeParser.cs | 1662 +-- .../Nop.Services/Common/PdfService7.cs | 3 +- .../Orders/OrderProcessingService.cs | 6425 ++++++------ .../Orders/OrderTotalCalculationService.cs | 35 +- src/Libraries/Nop.Services/Tax/TaxService.cs | 1673 ++-- src/Libraries/Nop.Services/Tax/TaxSummary.cs | 90 +- .../Controllers/OrderController.cs | 8906 +++++++++-------- .../Views/Order/_ProductAddAttributes.cshtml | 528 +- .../Controllers/ShoppingCartController.cs | 3696 +++---- .../Factories/ShoppingCartModelFactory.cs | 15 +- .../Models/ShoppingCart/ShoppingCartModel.cs | 396 +- .../Nop.Web/Views/Order/Details.cshtml | 1544 +-- .../Views/ShoppingCart/OrderSummary.cshtml | 659 +- .../OrderTotalCalculationServiceTests.cs | 2820 +++--- 21 files changed, 16534 insertions(+), 16160 deletions(-) diff --git a/src/Libraries/Nop.Core/Domain/Orders/Order.cs b/src/Libraries/Nop.Core/Domain/Orders/Order.cs index 9b576f6b608..f11141295f1 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/Order.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/Order.cs @@ -1,535 +1,539 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; - -namespace Nop.Core.Domain.Orders -{ - /// - /// Represents an order - /// - public partial class Order : BaseEntity - { - - private ICollection _discountUsageHistory; - private ICollection _giftCardUsageHistory; - private ICollection _orderNotes; - private ICollection _orderItems; - private ICollection _shipments; - - #region Utilities - - protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) - { - //var taxRatesDictionary = new SortedDictionary(); - var taxRatesDictionary = new SortedDictionary(); - if (String.IsNullOrEmpty(taxRatesStr)) - return taxRatesDictionary; - - string[] lines = taxRatesStr.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string line in lines) - { - if (String.IsNullOrEmpty(line.Trim())) - continue; - - string[] taxes = line.Split(new [] { ':' }); - if (taxes.Length == 6) - { - try - { - decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); - taxRatesDictionary.Add(rate, new TaxRateRec() - { - VatRate = rate, - Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), - DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), - BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), - VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), - AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) - }); - } - catch (Exception exc) - { - Debug.WriteLine(exc.ToString()); - } - } - } - - //add at least one tax rate (0%) - if (!taxRatesDictionary.Any()) - taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() - { - VatRate = decimal.Zero, - Amount = decimal.Zero, - DiscountAmount = decimal.Zero, - BaseAmount = decimal.Zero, - VatAmount = decimal.Zero, - AmountIncludingVAT = decimal.Zero - }); - - return taxRatesDictionary; - } - - #endregion - - #region Properties - - /// - /// Gets or sets the order identifier - /// - public Guid OrderGuid { get; set; } - - /// - /// Gets or sets the store identifier - /// - public int StoreId { get; set; } - - /// - /// Gets or sets the customer identifier - /// - public int CustomerId { get; set; } - - /// - /// Gets or sets the billing address identifier - /// - public int BillingAddressId { get; set; } - - /// - /// Gets or sets the shipping address identifier - /// - public int? ShippingAddressId { get; set; } - - /// - /// Gets or sets the pickup address identifier - /// - public int? PickupAddressId { get; set; } - - /// - /// Gets or sets a value indicating whether a customer chose "pick up in store" shipping option - /// - public bool PickUpInStore { get; set; } - - /// - /// Gets or sets an order status identifier - /// - public int OrderStatusId { get; set; } - - /// - /// Gets or sets the shipping status identifier - /// - public int ShippingStatusId { get; set; } - - /// - /// Gets or sets the payment status identifier - /// - public int PaymentStatusId { get; set; } - - /// - /// Gets or sets the payment method system name - /// - public string PaymentMethodSystemName { get; set; } - - /// - /// Gets or sets the customer currency code (at the moment of order placing) - /// - public string CustomerCurrencyCode { get; set; } - - /// - /// Gets or sets the currency rate - /// - public decimal CurrencyRate { get; set; } - - /// - /// Gets or sets the customer tax display type identifier - /// - public int CustomerTaxDisplayTypeId { get; set; } - - /// - /// Gets or sets the VAT number (the European Union Value Added Tax) - /// - public string VatNumber { get; set; } - - /// - /// Gets or sets the order subtotal (incl tax) - /// - public decimal OrderSubtotalInclTax { get; set; } - - /// - /// Gets or sets the order subtotal (excl tax) - /// - public decimal OrderSubtotalExclTax { get; set; } - - /// - /// Gets or sets the order subtotal discount (incl tax) - /// - public decimal OrderSubTotalDiscountInclTax { get; set; } - - /// - /// Gets or sets the order subtotal discount (excl tax) - /// - public decimal OrderSubTotalDiscountExclTax { get; set; } - - /// - /// Gets or sets the order shipping (incl tax) - /// - public decimal OrderShippingInclTax { get; set; } - - /// - /// Gets or sets the order shipping (excl tax) - /// - public decimal OrderShippingExclTax { get; set; } - - /// - /// Gets or sets the payment method additional fee (incl tax) - /// - public decimal PaymentMethodAdditionalFeeInclTax { get; set; } - - /// - /// Gets or sets the payment method additional fee (excl tax) - /// - public decimal PaymentMethodAdditionalFeeExclTax { get; set; } - - /// - /// Gets or sets the tax rates - /// - public string TaxRates { get; set; } - - /// - /// Gets or sets the order tax - /// - public decimal OrderTax { get; set; } - - /// - /// Gets or sets the order discount (applied to order total) - /// - public decimal OrderDiscount { get; set; } - - /// - /// Gets or sets the order total to pay - /// - public decimal OrderTotal { get; set; } - - /// - /// Gets or sets the refunded amount - /// - public decimal RefundedAmount { get; set; } - - /// - /// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order - /// - public int? RewardPointsHistoryEntryId { get; set; } - - /// - /// Gets or sets the checkout attribute description - /// - public string CheckoutAttributeDescription { get; set; } - - /// - /// Gets or sets the checkout attributes in XML format - /// - public string CheckoutAttributesXml { get; set; } - - /// - /// Gets or sets the customer language identifier - /// - public int CustomerLanguageId { get; set; } - - /// - /// Gets or sets the affiliate identifier - /// - public int AffiliateId { get; set; } - - /// - /// Gets or sets the customer IP address - /// - public string CustomerIp { get; set; } - - /// - /// Gets or sets a value indicating whether storing of credit card number is allowed - /// - public bool AllowStoringCreditCardNumber { get; set; } - - /// - /// Gets or sets the card type - /// - public string CardType { get; set; } - - /// - /// Gets or sets the card name - /// - public string CardName { get; set; } - - /// - /// Gets or sets the card number - /// - public string CardNumber { get; set; } - - /// - /// Gets or sets the masked credit card number - /// - public string MaskedCreditCardNumber { get; set; } - - /// - /// Gets or sets the card CVV2 - /// - public string CardCvv2 { get; set; } - - /// - /// Gets or sets the card expiration month - /// - public string CardExpirationMonth { get; set; } - - /// - /// Gets or sets the card expiration year - /// - public string CardExpirationYear { get; set; } - - /// - /// Gets or sets the authorization transaction identifier - /// - public string AuthorizationTransactionId { get; set; } - - /// - /// Gets or sets the authorization transaction code - /// - public string AuthorizationTransactionCode { get; set; } - - /// - /// Gets or sets the authorization transaction result - /// - public string AuthorizationTransactionResult { get; set; } - - /// - /// Gets or sets the capture transaction identifier - /// - public string CaptureTransactionId { get; set; } - - /// - /// Gets or sets the capture transaction result - /// - public string CaptureTransactionResult { get; set; } - - /// - /// Gets or sets the subscription transaction identifier - /// - public string SubscriptionTransactionId { get; set; } - - /// - /// Gets or sets the paid date and time - /// - public DateTime? PaidDateUtc { get; set; } - - /// - /// Gets or sets the shipping method - /// - public string ShippingMethod { get; set; } - - /// - /// Gets or sets the shipping rate computation method identifier or the pickup point provider identifier (if PickUpInStore is true) - /// - public string ShippingRateComputationMethodSystemName { get; set; } - - /// - /// Gets or sets the serialized CustomValues (values from ProcessPaymentRequest) - /// - public string CustomValuesXml { get; set; } - - /// - /// Gets or sets a value indicating whether the entity has been deleted - /// - public bool Deleted { get; set; } - - /// - /// Gets or sets the date and time of order creation - /// - public DateTime CreatedOnUtc { get; set; } - - /// - /// Gets or sets the invoice ID - /// - public string InvoiceId { get; set; } - /// - /// Gets or sets the invoice date UTC - /// - public DateTime? InvoiceDateUtc { get; set; } - - /// - /// Gets or sets the order total base amount excl. tax - /// - public decimal OrderAmount { get; set; } //MF 09.12.16 - - /// - /// Gets or sets the order total amount incl. tax - /// - public decimal OrderAmountIncl { get; set; } //MF 09.12.16 - #endregion - - #region Navigation properties - - /// - /// Gets or sets the customer - /// - public virtual Customer Customer { get; set; } - - /// - /// Gets or sets the billing address - /// - public virtual Address BillingAddress { get; set; } - - /// - /// Gets or sets the shipping address - /// - public virtual Address ShippingAddress { get; set; } - - /// - /// Gets or sets the pickup address - /// - public virtual Address PickupAddress { get; set; } - - /// - /// Gets or sets the reward points history record (spent by a customer when placing this order) - /// - public virtual RewardPointsHistory RedeemedRewardPointsEntry { get; set; } - - /// - /// Gets or sets discount usage history - /// - public virtual ICollection DiscountUsageHistory - { - get { return _discountUsageHistory ?? (_discountUsageHistory = new List()); } - protected set { _discountUsageHistory = value; } - } - - /// - /// Gets or sets gift card usage history (gift card that were used with this order) - /// - public virtual ICollection GiftCardUsageHistory - { - get { return _giftCardUsageHistory ?? (_giftCardUsageHistory = new List()); } - protected set { _giftCardUsageHistory = value; } - } - - /// - /// Gets or sets order notes - /// - public virtual ICollection OrderNotes - { - get { return _orderNotes ?? (_orderNotes = new List()); } - protected set { _orderNotes = value; } - } - - /// - /// Gets or sets order items - /// - public virtual ICollection OrderItems - { - get { return _orderItems ?? (_orderItems = new List()); } - protected set { _orderItems = value; } - } - - /// - /// Gets or sets shipments - /// - public virtual ICollection Shipments - { - get { return _shipments ?? (_shipments = new List()); } - protected set { _shipments = value; } - } - - #endregion - - #region Custom properties - - /// - /// Gets or sets the order status - /// - public OrderStatus OrderStatus - { - get - { - return (OrderStatus)this.OrderStatusId; - } - set - { - this.OrderStatusId = (int)value; - } - } - - /// - /// Gets or sets the payment status - /// - public PaymentStatus PaymentStatus - { - get - { - return (PaymentStatus)this.PaymentStatusId; - } - set - { - this.PaymentStatusId = (int)value; - } - } - - /// - /// Gets or sets the shipping status - /// - public ShippingStatus ShippingStatus - { - get - { - return (ShippingStatus)this.ShippingStatusId; - } - set - { - this.ShippingStatusId = (int)value; - } - } - - /// - /// Gets or sets the customer tax display type - /// - public TaxDisplayType CustomerTaxDisplayType - { - get - { - return (TaxDisplayType)this.CustomerTaxDisplayTypeId; - } - set - { - this.CustomerTaxDisplayTypeId = (int)value; - } - } - - /// - /// Gets the applied tax rates - /// - public SortedDictionary TaxRatesDictionary - { - get - { - return ParseTaxRates(this.TaxRates); - } - } - - #endregion - } - - #region Nested classes - public partial class TaxRateRec - { - public decimal VatRate { get; set; } - public decimal Amount { get; set; } - public decimal DiscountAmount { get; set; } - public decimal BaseAmount { get; set; } - public decimal VatAmount { get; set; } - public decimal AmountIncludingVAT { get; set; } - } - #endregion -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; + +namespace Nop.Core.Domain.Orders +{ + /// + /// Represents an order + /// + public partial class Order : BaseEntity + { + + private ICollection _discountUsageHistory; + private ICollection _giftCardUsageHistory; + private ICollection _orderNotes; + private ICollection _orderItems; + private ICollection _shipments; + + #region Utilities + + /// + /// Parses order.TaxRates string + /// + /// + /// Returns the sorted dictionary of taxrateEntries + protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) + { + var taxRatesDictionary = new SortedDictionary(); + if (String.IsNullOrEmpty(taxRatesStr)) + return taxRatesDictionary; + + string[] lines = taxRatesStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string line in lines) + { + if (String.IsNullOrEmpty(line.Trim())) + continue; + + string[] taxes = line.Split(new[] { ':' }); + if (taxes.Length == 6) + { + try + { + decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); + taxRatesDictionary.Add(rate, new TaxRateRec() + { + VatRate = rate, + Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), + DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), + BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), + VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), + AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) + }); + } + catch (Exception exc) + { + Debug.WriteLine(exc.ToString()); + } + } + } + + //add at least one tax rate (0%) + if (!taxRatesDictionary.Any()) + taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() + { + VatRate = decimal.Zero, + Amount = decimal.Zero, + DiscountAmount = decimal.Zero, + BaseAmount = decimal.Zero, + VatAmount = decimal.Zero, + AmountIncludingVAT = decimal.Zero + }); + + return taxRatesDictionary; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the order identifier + /// + public Guid OrderGuid { get; set; } + + /// + /// Gets or sets the store identifier + /// + public int StoreId { get; set; } + + /// + /// Gets or sets the customer identifier + /// + public int CustomerId { get; set; } + + /// + /// Gets or sets the billing address identifier + /// + public int BillingAddressId { get; set; } + + /// + /// Gets or sets the shipping address identifier + /// + public int? ShippingAddressId { get; set; } + + /// + /// Gets or sets the pickup address identifier + /// + public int? PickupAddressId { get; set; } + + /// + /// Gets or sets a value indicating whether a customer chose "pick up in store" shipping option + /// + public bool PickUpInStore { get; set; } + + /// + /// Gets or sets an order status identifier + /// + public int OrderStatusId { get; set; } + + /// + /// Gets or sets the shipping status identifier + /// + public int ShippingStatusId { get; set; } + + /// + /// Gets or sets the payment status identifier + /// + public int PaymentStatusId { get; set; } + + /// + /// Gets or sets the payment method system name + /// + public string PaymentMethodSystemName { get; set; } + + /// + /// Gets or sets the customer currency code (at the moment of order placing) + /// + public string CustomerCurrencyCode { get; set; } + + /// + /// Gets or sets the currency rate + /// + public decimal CurrencyRate { get; set; } + + /// + /// Gets or sets the customer tax display type identifier + /// + public int CustomerTaxDisplayTypeId { get; set; } + + /// + /// Gets or sets the VAT number (the European Union Value Added Tax) + /// + public string VatNumber { get; set; } + + /// + /// Gets or sets the order subtotal (incl tax) + /// + public decimal OrderSubtotalInclTax { get; set; } + + /// + /// Gets or sets the order subtotal (excl tax) + /// + public decimal OrderSubtotalExclTax { get; set; } + + /// + /// Gets or sets the order subtotal discount (incl tax) + /// + public decimal OrderSubTotalDiscountInclTax { get; set; } + + /// + /// Gets or sets the order subtotal discount (excl tax) + /// + public decimal OrderSubTotalDiscountExclTax { get; set; } + + /// + /// Gets or sets the order shipping (incl tax) + /// + public decimal OrderShippingInclTax { get; set; } + + /// + /// Gets or sets the order shipping (excl tax) + /// + public decimal OrderShippingExclTax { get; set; } + + /// + /// Gets or sets the payment method additional fee (incl tax) + /// + public decimal PaymentMethodAdditionalFeeInclTax { get; set; } + + /// + /// Gets or sets the payment method additional fee (excl tax) + /// + public decimal PaymentMethodAdditionalFeeExclTax { get; set; } + + /// + /// Gets or sets the tax rates + /// + public string TaxRates { get; set; } + + /// + /// Gets or sets the order tax + /// + public decimal OrderTax { get; set; } + + /// + /// Gets or sets the order discount (applied to order total) + /// + public decimal OrderDiscount { get; set; } + + /// + /// Gets or sets the order total to pay + /// + public decimal OrderTotal { get; set; } + + /// + /// Gets or sets the refunded amount + /// + public decimal RefundedAmount { get; set; } + + /// + /// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order + /// + public int? RewardPointsHistoryEntryId { get; set; } + + /// + /// Gets or sets the checkout attribute description + /// + public string CheckoutAttributeDescription { get; set; } + + /// + /// Gets or sets the checkout attributes in XML format + /// + public string CheckoutAttributesXml { get; set; } + + /// + /// Gets or sets the customer language identifier + /// + public int CustomerLanguageId { get; set; } + + /// + /// Gets or sets the affiliate identifier + /// + public int AffiliateId { get; set; } + + /// + /// Gets or sets the customer IP address + /// + public string CustomerIp { get; set; } + + /// + /// Gets or sets a value indicating whether storing of credit card number is allowed + /// + public bool AllowStoringCreditCardNumber { get; set; } + + /// + /// Gets or sets the card type + /// + public string CardType { get; set; } + + /// + /// Gets or sets the card name + /// + public string CardName { get; set; } + + /// + /// Gets or sets the card number + /// + public string CardNumber { get; set; } + + /// + /// Gets or sets the masked credit card number + /// + public string MaskedCreditCardNumber { get; set; } + + /// + /// Gets or sets the card CVV2 + /// + public string CardCvv2 { get; set; } + + /// + /// Gets or sets the card expiration month + /// + public string CardExpirationMonth { get; set; } + + /// + /// Gets or sets the card expiration year + /// + public string CardExpirationYear { get; set; } + + /// + /// Gets or sets the authorization transaction identifier + /// + public string AuthorizationTransactionId { get; set; } + + /// + /// Gets or sets the authorization transaction code + /// + public string AuthorizationTransactionCode { get; set; } + + /// + /// Gets or sets the authorization transaction result + /// + public string AuthorizationTransactionResult { get; set; } + + /// + /// Gets or sets the capture transaction identifier + /// + public string CaptureTransactionId { get; set; } + + /// + /// Gets or sets the capture transaction result + /// + public string CaptureTransactionResult { get; set; } + + /// + /// Gets or sets the subscription transaction identifier + /// + public string SubscriptionTransactionId { get; set; } + + /// + /// Gets or sets the paid date and time + /// + public DateTime? PaidDateUtc { get; set; } + + /// + /// Gets or sets the shipping method + /// + public string ShippingMethod { get; set; } + + /// + /// Gets or sets the shipping rate computation method identifier or the pickup point provider identifier (if PickUpInStore is true) + /// + public string ShippingRateComputationMethodSystemName { get; set; } + + /// + /// Gets or sets the serialized CustomValues (values from ProcessPaymentRequest) + /// + public string CustomValuesXml { get; set; } + + /// + /// Gets or sets a value indicating whether the entity has been deleted + /// + public bool Deleted { get; set; } + + /// + /// Gets or sets the date and time of order creation + /// + public DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the invoice ID + /// + public string InvoiceId { get; set; } + /// + /// Gets or sets the invoice date UTC + /// + public DateTime? InvoiceDateUtc { get; set; } + + /// + /// Gets or sets the order total base amount excl. tax + /// + public decimal OrderAmount { get; set; } //MF 09.12.16 + + /// + /// Gets or sets the order total amount incl. tax + /// + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 + #endregion + + #region Navigation properties + + /// + /// Gets or sets the customer + /// + public virtual Customer Customer { get; set; } + + /// + /// Gets or sets the billing address + /// + public virtual Address BillingAddress { get; set; } + + /// + /// Gets or sets the shipping address + /// + public virtual Address ShippingAddress { get; set; } + + /// + /// Gets or sets the pickup address + /// + public virtual Address PickupAddress { get; set; } + + /// + /// Gets or sets the reward points history record (spent by a customer when placing this order) + /// + public virtual RewardPointsHistory RedeemedRewardPointsEntry { get; set; } + + /// + /// Gets or sets discount usage history + /// + public virtual ICollection DiscountUsageHistory + { + get { return _discountUsageHistory ?? (_discountUsageHistory = new List()); } + protected set { _discountUsageHistory = value; } + } + + /// + /// Gets or sets gift card usage history (gift card that were used with this order) + /// + public virtual ICollection GiftCardUsageHistory + { + get { return _giftCardUsageHistory ?? (_giftCardUsageHistory = new List()); } + protected set { _giftCardUsageHistory = value; } + } + + /// + /// Gets or sets order notes + /// + public virtual ICollection OrderNotes + { + get { return _orderNotes ?? (_orderNotes = new List()); } + protected set { _orderNotes = value; } + } + + /// + /// Gets or sets order items + /// + public virtual ICollection OrderItems + { + get { return _orderItems ?? (_orderItems = new List()); } + protected set { _orderItems = value; } + } + + /// + /// Gets or sets shipments + /// + public virtual ICollection Shipments + { + get { return _shipments ?? (_shipments = new List()); } + protected set { _shipments = value; } + } + + #endregion + + #region Custom properties + + /// + /// Gets or sets the order status + /// + public OrderStatus OrderStatus + { + get + { + return (OrderStatus)this.OrderStatusId; + } + set + { + this.OrderStatusId = (int)value; + } + } + + /// + /// Gets or sets the payment status + /// + public PaymentStatus PaymentStatus + { + get + { + return (PaymentStatus)this.PaymentStatusId; + } + set + { + this.PaymentStatusId = (int)value; + } + } + + /// + /// Gets or sets the shipping status + /// + public ShippingStatus ShippingStatus + { + get + { + return (ShippingStatus)this.ShippingStatusId; + } + set + { + this.ShippingStatusId = (int)value; + } + } + + /// + /// Gets or sets the customer tax display type + /// + public TaxDisplayType CustomerTaxDisplayType + { + get + { + return (TaxDisplayType)this.CustomerTaxDisplayTypeId; + } + set + { + this.CustomerTaxDisplayTypeId = (int)value; + } + } + + /// + /// Gets the applied tax rates + /// + public SortedDictionary TaxRatesDictionary + { + get + { + return ParseTaxRates(this.TaxRates); + } + } + + #endregion + } + + #region Nested classes + public partial class TaxRateRec + { + public decimal VatRate { get; set; } + public decimal Amount { get; set; } + public decimal DiscountAmount { get; set; } + public decimal BaseAmount { get; set; } + public decimal VatAmount { get; set; } + public decimal AmountIncludingVAT { get; set; } + } + #endregion +} diff --git a/src/Libraries/Nop.Core/Html/HtmlHelper.cs b/src/Libraries/Nop.Core/Html/HtmlHelper.cs index af886767b2a..5defb54fe6c 100644 --- a/src/Libraries/Nop.Core/Html/HtmlHelper.cs +++ b/src/Libraries/Nop.Core/Html/HtmlHelper.cs @@ -1,227 +1,232 @@ -using System; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; - -namespace Nop.Core.Html -{ - /// - /// Represents a HTML helper - /// - public partial class HtmlHelper - { - #region Fields - private readonly static Regex paragraphStartRegex = new Regex("

", RegexOptions.IgnoreCase); - private readonly static Regex paragraphEndRegex = new Regex("

", RegexOptions.IgnoreCase); - //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - #endregion - - #region Utilities - - private static string EnsureOnlyAllowedHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - const string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; - - var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase); - for (int i = m.Count - 1; i >= 0; i--) - { - string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower(); - - if (!IsValidTag(tag, allowedTags)) - { - text = text.Remove(m[i].Index, m[i].Length); - } - } - - return text; - } - - private static bool IsValidTag(string tag, string tags) - { - string[] allowedTags = tags.Split(','); - if (tag.IndexOf("javascript") >= 0) return false; - if (tag.IndexOf("vbscript") >= 0) return false; - if (tag.IndexOf("onclick") >= 0) return false; - - var endchars = new [] { ' ', '>', '/', '\t' }; - - int pos = tag.IndexOfAny(endchars, 1); - if (pos > 0) tag = tag.Substring(0, pos); - if (tag[0] == '/') tag = tag.Substring(1); - - foreach (string aTag in allowedTags) - { - if (tag == aTag) return true; - } - - return false; - } - #endregion - - #region Methods - /// - /// Formats the text - /// - /// Text - /// A value indicating whether to strip tags - /// A value indicating whether HTML is allowed - /// A value indicating whether HTML is allowed - /// A value indicating whether BBCode is allowed - /// A value indicating whether to resolve links - /// A value indicating whether to add "noFollow" tag - /// Formatted text - public static string FormatText(string text, bool stripTags, - bool convertPlainTextToHtml, bool allowHtml, - bool allowBBCode, bool resolveLinks, bool addNoFollowTag) - { - - if (String.IsNullOrEmpty(text)) - return string.Empty; - - try - { - if (stripTags) - { - text = StripTags(text); - } - - text = allowHtml ? EnsureOnlyAllowedHtml(text) : HttpUtility.HtmlEncode(text); - - if (convertPlainTextToHtml) - { - text = ConvertPlainTextToHtml(text); - } - - if (allowBBCode) - { - text = BBCodeHelper.FormatText(text, true, true, true, true, true, true, true); - } - - if (resolveLinks) - { - text = ResolveLinksHelper.FormatText(text); - } - - if (addNoFollowTag) - { - //add noFollow tag. not implemented - } - } - catch (Exception exc) - { - text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); - } - return text; - } - - /// - /// Strips tags - /// - /// Text - /// Formatted text - public static string StripTags(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><"); - text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2"); - text = Regex.Replace(text, "(&#x?[0-9]{2,4};|"|&| |<|>|€|©|®|‰|‡|†|‹|›|„|”|“|‚|’|‘|—|–|‏|‎|‍|‌| | | |˜|ˆ|Ÿ|š|Š)", "@"); - - return text; - } - - /// - /// replace anchor text (remove a tag from the following url Name and output only the string "Name") - /// - /// Text - /// Text - public static string ReplaceAnchorTags(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = Regex.Replace(text, @"]+>([^<]*(?:(?!", "$1", RegexOptions.IgnoreCase); - return text; - } - - /// - /// Converts plain text to HTML - /// - /// Text - /// Formatted text - public static string ConvertPlainTextToHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = text.Replace("\r\n", "
"); - text = text.Replace("\r", "
"); - text = text.Replace("\n", "
"); - text = text.Replace("\t", "  "); - text = text.Replace(" ", "  "); - - return text; - } - - /// - /// Converts HTML to plain text - /// - /// Text - /// A value indicating whether to decode text - /// A value indicating whether to replace anchor text (remove a tag from the following url Name and output only the string "Name") - /// Formatted text - public static string ConvertHtmlToPlainText(string text, - bool decode = false, bool replaceAnchorTags = false) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - if (decode) - text = HttpUtility.HtmlDecode(text); - - text = text.Replace("
", "\n"); - text = text.Replace("
", "\n"); - text = text.Replace("
", "\n"); - text = text.Replace("  ", "\t"); - text = text.Replace("  ", " "); - - if (replaceAnchorTags) - text = ReplaceAnchorTags(text); - - return text; - } - - /// - /// Converts text to paragraph - /// - /// Text - /// Formatted text - public static string ConvertPlainTextToParagraph(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = paragraphStartRegex.Replace(text, string.Empty); - text = paragraphEndRegex.Replace(text, "\n"); - text = text.Replace("\r\n", "\n").Replace("\r", "\n"); - text = text + "\n\n"; - text = text.Replace("\n\n", "\n"); - var strArray = text.Split(new [] { '\n' }); - var builder = new StringBuilder(); - foreach (string str in strArray) - { - if ((str != null) && (str.Trim().Length > 0)) - { - builder.AppendFormat("

{0}

\n", str); - } - } - return builder.ToString(); - } - #endregion - } -} +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace Nop.Core.Html +{ + /// + /// Represents a HTML helper + /// + public partial class HtmlHelper + { + #region Fields + private readonly static Regex paragraphStartRegex = new Regex("

", RegexOptions.IgnoreCase); + private readonly static Regex paragraphEndRegex = new Regex("

", RegexOptions.IgnoreCase); + //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + #endregion + + #region Utilities + + private static string EnsureOnlyAllowedHtml(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + const string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; + + var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase); + for (int i = m.Count - 1; i >= 0; i--) + { + string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower(); + + if (!IsValidTag(tag, allowedTags)) + { + text = text.Remove(m[i].Index, m[i].Length); + } + } + + return text; + } + + private static bool IsValidTag(string tag, string tags) + { + string[] allowedTags = tags.Split(','); + if (tag.IndexOf("javascript") >= 0) return false; + if (tag.IndexOf("vbscript") >= 0) return false; + if (tag.IndexOf("onclick") >= 0) return false; + + var endchars = new [] { ' ', '>', '/', '\t' }; + + int pos = tag.IndexOfAny(endchars, 1); + if (pos > 0) tag = tag.Substring(0, pos); + if (tag[0] == '/') tag = tag.Substring(1); + + foreach (string aTag in allowedTags) + { + if (tag == aTag) return true; + } + + return false; + } + #endregion + + #region Methods + /// + /// Formats the text + /// + /// Text + /// A value indicating whether to strip tags + /// A value indicating whether HTML is allowed + /// A value indicating whether HTML is allowed + /// A value indicating whether BBCode is allowed + /// A value indicating whether to resolve links + /// A value indicating whether to add "noFollow" tag + /// Formatted text + public static string FormatText(string text, bool stripTags, + bool convertPlainTextToHtml, bool allowHtml, + bool allowBBCode, bool resolveLinks, bool addNoFollowTag) + { + + if (String.IsNullOrEmpty(text)) + return string.Empty; + + try + { + if (stripTags) + { + text = StripTags(text); + } + + text = allowHtml ? EnsureOnlyAllowedHtml(text) : HttpUtility.HtmlEncode(text); + + if (convertPlainTextToHtml) + { + text = ConvertPlainTextToHtml(text); + } + + if (allowBBCode) + { + text = BBCodeHelper.FormatText(text, true, true, true, true, true, true, true); + } + + if (resolveLinks) + { + text = ResolveLinksHelper.FormatText(text); + } + + if (addNoFollowTag) + { + //add noFollow tag. not implemented + } + } + catch (Exception exc) + { + text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); + } + return text; + } + + /// + /// Strips tags + /// + /// Text + /// Formatted text + public static string StripTags(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><"); + text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2"); + text = Regex.Replace(text, "(&#x?[0-9]{2,4};|"|&| |<|>|€|©|®|‰|‡|†|‹|›|„|”|“|‚|’|‘|—|–|‏|‎|‍|‌| | | |˜|ˆ|Ÿ|š|Š)", "@"); + + return text; + } + + /// + /// replace anchor text (remove a tag from the following url Name and output only the string "Name") + /// + /// Text + /// Text + public static string ReplaceAnchorTags(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = Regex.Replace(text, @"]+>([^<]*(?:(?!", "$1", RegexOptions.IgnoreCase); + return text; + } + + /// + /// Converts plain text to HTML + /// + /// Text + /// Formatted text + public static string ConvertPlainTextToHtml(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = text.Replace("\r\n", "
"); + text = text.Replace("\r", "
"); + text = text.Replace("\n", "
"); + text = text.Replace("\t", "  "); + text = text.Replace(" ", "  "); + + return text; + } + + /// + /// Converts HTML to plain text + /// + /// Text + /// A value indicating whether to decode text + /// A value indicating whether to replace anchor text (remove a tag from the following url Name and output only the string "Name") + /// Formatted text + public static string ConvertHtmlToPlainText(string text, + bool decode = false, bool replaceAnchorTags = false) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + if (decode) + text = HttpUtility.HtmlDecode(text); + + text = text.Replace("
", "\n"); + text = text.Replace("
", "\n"); + text = text.Replace("
", "\n"); + text = text.Replace("  ", "\t"); + text = text.Replace("  ", " "); + //attribute Vat tags to remove + text = text.Replace("", ""); + text = text.Replace("", "\t"); + text = text.Replace("", ""); + + + if (replaceAnchorTags) + text = ReplaceAnchorTags(text); + + return text; + } + + /// + /// Converts text to paragraph + /// + /// Text + /// Formatted text + public static string ConvertPlainTextToParagraph(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = paragraphStartRegex.Replace(text, string.Empty); + text = paragraphEndRegex.Replace(text, "\n"); + text = text.Replace("\r\n", "\n").Replace("\r", "\n"); + text = text + "\n\n"; + text = text.Replace("\n\n", "\n"); + var strArray = text.Split(new [] { '\n' }); + var builder = new StringBuilder(); + foreach (string str in strArray) + { + if ((str != null) && (str.Trim().Length > 0)) + { + builder.AppendFormat("

{0}

\n", str); + } + } + return builder.ToString(); + } + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs index 9012932b96e..6e03c43f27d 100644 --- a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs @@ -1,158 +1,158 @@ -using System; -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Services.Discounts; - -namespace Nop.Services.Catalog -{ - /// - /// Price calculation service - /// - public partial interface IPriceCalculationService - { - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge = decimal.Zero, - bool includeDiscounts = true, - int quantity = 1); - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Applied discount amount - /// Applied discounts - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts); - - - - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart unit price (one item) - decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true); - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// Gets the shopping cart unit price (one item) - /// - /// Product - /// Customer - /// Shopping cart type - /// Quantity - /// Product atrributes (XML format) - /// Customer entered price (if specified) - /// Rental start date (null for not rental products) - /// Rental end date (null for not rental products) - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - decimal GetUnitPrice(Product product, - Customer customer, - ShoppingCartType shoppingCartType, - int quantity, - string attributesXml, - decimal customerEnteredPrice, - DateTime? rentalStartDate, DateTime? rentalEndDate, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart item sub total - decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true); - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items - /// Shopping cart item sub total - decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts, - out int? maximumDiscountQty); - - /// - /// Gets the product cost (one item) - /// - /// Product - /// Shopping cart item attributes in XML - /// Product cost (one item) - decimal GetProductCost(Product product, string attributesXml); - - - - - /// - /// Get a price adjustment of a product attribute value - /// - /// Product attribute value - /// Price adjustment - decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value); - } -} +using System; +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Services.Discounts; + +namespace Nop.Services.Catalog +{ + /// + /// Price calculation service + /// + public partial interface IPriceCalculationService + { + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge = decimal.Zero, + bool includeDiscounts = true, + int quantity = 1); + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Applied discount amount + /// Applied discounts + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts); + + + + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart unit price (one item) + decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true); + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// Gets the shopping cart unit price (one item) + /// + /// Product + /// Customer + /// Shopping cart type + /// Quantity + /// Product atrributes (XML format) + /// Customer entered price (if specified) + /// Rental start date (null for not rental products) + /// Rental end date (null for not rental products) + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + decimal GetUnitPrice(Product product, + Customer customer, + ShoppingCartType shoppingCartType, + int quantity, + ref string attributesXml, + decimal customerEnteredPrice, + DateTime? rentalStartDate, DateTime? rentalEndDate, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart item sub total + decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true); + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items + /// Shopping cart item sub total + decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts, + out int? maximumDiscountQty); + + /// + /// Gets the product cost (one item) + /// + /// Product + /// Shopping cart item attributes in XML + /// Product cost (one item) + decimal GetProductCost(Product product, string attributesXml); + + + + + /// + /// Get a price adjustment of a product attribute value + /// + /// Product attribute value + /// Price adjustment + decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value); + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs index 25996258a40..0848706934d 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs @@ -1,37 +1,39 @@ -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute formatter interface - /// - public partial interface IProductAttributeFormatter - { - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Attributes - string FormatAttributes(Product product, string attributesXml); - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Customer - /// Serapator - /// A value indicating whether to encode (HTML) values - /// A value indicating whether to render prices - /// A value indicating whether to render product attributes - /// A value indicating whether to render gift card attributes - /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// Attributes - string FormatAttributes(Product product, string attributesXml, - Customer customer, string serapator = "
", bool htmlEncode = true, bool renderPrices = true, - bool renderProductAttributes = true, bool renderGiftCardAttributes = true, - bool allowHyperlinks = true); - } -} +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute formatter interface + /// + public partial interface IProductAttributeFormatter + { + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Attributes + string FormatAttributes(Product product, string attributesXml); + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Customer + /// Serapator + /// A value indicating whether to encode (HTML) values + /// A value indicating whether to render prices + /// A value indicating whether to render product attributes + /// A value indicating whether to render gift card attributes + /// A value indicating whether to HTML hyperink tags could be rendered (if required) + /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// Attributes + string FormatAttributes(Product product, string attributesXml, + Customer customer, string serapator = "
", bool htmlEncode = true, bool renderPrices = true, + bool renderProductAttributes = true, bool renderGiftCardAttributes = true, + bool allowHyperlinks = true, + decimal subTotal = decimal.MinusOne); + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs index e68aa2553de..fe2ef5d3007 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs @@ -1,122 +1,139 @@ -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute parser interface - /// - public partial interface IProductAttributeParser - { - #region Product attributes - - /// - /// Gets selected product attribute mappings - /// - /// Attributes in XML format - /// Selected product attribute mappings - IList ParseProductAttributeMappings(string attributesXml); - - /// - /// Get product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier; pass 0 to load all values - /// Product attribute values - IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0); - - /// - /// Gets selected product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Product attribute values - IList ParseValues(string attributesXml, int productAttributeMappingId); - - /// - /// Adds an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Value - /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) - /// Updated result (XML format) - string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null); - - /// - /// Remove an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Updated result (XML format) - string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping); - - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// A value indicating whether we should ignore non-combinable attributes - /// A value indicating whether we should ignore the quantity of attribute value entered by the customer - /// Result - bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true); - - /// - /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified - /// - /// Product attribute - /// Selected attributes (XML format) - /// Result - bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml); - - /// - /// Finds a product attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// A value indicating whether we should ignore non-combinable attributes - /// Found product attribute combination - ProductAttributeCombination FindProductAttributeCombination(Product product, - string attributesXml, bool ignoreNonCombinableAttributes = true); - - /// - /// Generate all combinations - /// - /// Product - /// A value indicating whether we should ignore non-combinable attributes - /// Attribute combinations in XML format - IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false); - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - string AddGiftCardAttribute(string attributesXml, string recipientName, - string recipientEmail, string senderName, string senderEmail, string giftCardMessage); - - /// - /// Get gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - void GetGiftCardAttribute(string attributesXml, out string recipientName, - out string recipientEmail, out string senderName, - out string senderEmail, out string giftCardMessage); - - #endregion - } -} +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Services.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute parser interface + /// + public partial interface IProductAttributeParser + { + #region Product attributes + + /// + /// Gets selected product attribute mappings + /// + /// Attributes in XML format + /// Selected product attribute mappings + IList ParseProductAttributeMappings(string attributesXml); + + /// + /// Get product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier; pass 0 to load all values + /// Product attribute values + IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0); + + /// + /// Gets selected product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Product attribute values + IList ParseValues(string attributesXml, int productAttributeMappingId); + + /// + /// Adds an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Value + /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) + /// Updated result (XML format) + string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null); + + /// + /// Remove an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Updated result (XML format) + string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping); + + /// + /// Are attributes equal + /// + /// The attributes of the first product + /// The attributes of the second product + /// A value indicating whether we should ignore non-combinable attributes + /// A value indicating whether we should ignore the quantity of attribute value entered by the customer + /// Result + bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true); + + /// + /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified + /// + /// Product attribute + /// Selected attributes (XML format) + /// Result + bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml); + + /// + /// Finds a product attribute combination by attributes stored in XML + /// + /// Product + /// Attributes in XML format + /// A value indicating whether we should ignore non-combinable attributes + /// Found product attribute combination + ProductAttributeCombination FindProductAttributeCombination(Product product, + string attributesXml, bool ignoreNonCombinableAttributes = true); + + /// + /// Generate all combinations + /// + /// Product + /// A value indicating whether we should ignore non-combinable attributes + /// Attribute combinations in XML format + IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false); + + #endregion + #region taxAttribute + /// + /// Adds tax subdivision to existing attributesXml + /// + /// Attributes in XML format + /// Set Product tax subdivision + /// Updated result (XML format) + string AddTaxAttribute(string attributesXml, TaxSummary taxSummary); + + /// + /// Parse ProductAttributesTax + /// + /// Attributes in XML format + /// SortedDictionary with vatRate and vatRateWeight + SortedDictionary ParseTaxAttribute(string attributesXml); + #endregion + + #region Gift card attributes + + /// + /// Add gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + /// Attributes + string AddGiftCardAttribute(string attributesXml, string recipientName, + string recipientEmail, string senderName, string senderEmail, string giftCardMessage); + + /// + /// Get gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + void GetGiftCardAttribute(string attributesXml, out string recipientName, + out string recipientEmail, out string senderName, + out string senderEmail, out string giftCardMessage); + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 4ce411057ee..7a7fd52d136 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -1,747 +1,790 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Orders; -using Nop.Services.Catalog.Cache; -using Nop.Services.Customers; -using Nop.Services.Discounts; - -namespace Nop.Services.Catalog -{ - /// - /// Price calculation service - /// - public partial class PriceCalculationService : IPriceCalculationService - { - #region Fields - - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IDiscountService _discountService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductService _productService; - private readonly ICacheManager _cacheManager; - private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly CatalogSettings _catalogSettings; - - #endregion - - #region Ctor - - public PriceCalculationService(IWorkContext workContext, - IStoreContext storeContext, - IDiscountService discountService, - ICategoryService categoryService, - IManufacturerService manufacturerService, - IProductAttributeParser productAttributeParser, - IProductService productService, - ICacheManager cacheManager, - ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) - { - this._workContext = workContext; - this._storeContext = storeContext; - this._discountService = discountService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productAttributeParser = productAttributeParser; - this._productService = productService; - this._cacheManager = cacheManager; - this._shoppingCartSettings = shoppingCartSettings; - this._catalogSettings = catalogSettings; - } - - #endregion - - #region Nested classes - - [Serializable] - protected class ProductPriceForCaching - { - public ProductPriceForCaching() - { - this.AppliedDiscounts = new List(); - } - - public decimal Price { get; set; } - public decimal AppliedDiscountAmount { get; set; } - public List AppliedDiscounts { get; set; } - } - #endregion - - #region Utilities - - /// - /// Gets allowed discounts applied to product - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToProduct(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - if (product.HasDiscountsApplied) - { - //we use this property ("HasDiscountsApplied") for performance optimization to avoid unnecessary database calls - foreach (var discount in product.AppliedDiscounts) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - discount.DiscountType == DiscountType.AssignedToSkus) - allowedDiscounts.Add(discount.MapDiscount()); - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts applied to categories - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToCategories(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - //load cached discount models (performance optimization) - foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)) - { - //load identifier of categories with this discount applied to - var discountCategoryIds = _discountService.GetAppliedCategoryIds(discount, customer); - - //compare with categories of this product - var productCategoryIds = new List(); - if (discountCategoryIds.Any()) - { - //load identifier of categories of this product - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_CATEGORY_IDS_MODEL_KEY, - product.Id, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - productCategoryIds = _cacheManager.Get(cacheKey, () => - _categoryService - .GetProductCategoriesByProductId(product.Id) - .Select(x => x.CategoryId) - .ToList()); - } - - foreach (var categoryId in productCategoryIds) - { - if (discountCategoryIds.Contains(categoryId)) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - } - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts applied to manufacturers - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToManufacturers(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)) - { - //load identifier of manufacturers with this discount applied to - var discountManufacturerIds = _discountService.GetAppliedManufacturerIds(discount, customer); - - //compare with manufacturers of this product - var productManufacturerIds = new List(); - if (discountManufacturerIds.Any()) - { - //load identifier of manufacturers of this product - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_MANUFACTURER_IDS_MODEL_KEY, - product.Id, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - productManufacturerIds = _cacheManager.Get(cacheKey, () => - _manufacturerService - .GetProductManufacturersByProductId(product.Id) - .Select(x => x.ManufacturerId) - .ToList()); - } - - foreach (var manufacturerId in productManufacturerIds) - { - if (discountManufacturerIds.Contains(manufacturerId)) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - } - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscounts(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - //discounts applied to products - foreach (var discount in GetAllowedDiscountsAppliedToProduct(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - //discounts applied to categories - foreach (var discount in GetAllowedDiscountsAppliedToCategories(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - //discounts applied to manufacturers - foreach (var discount in GetAllowedDiscountsAppliedToManufacturers(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - return allowedDiscounts; - } - - /// - /// Gets discount amount - /// - /// Product - /// The customer - /// Already calculated product price without discount - /// Applied discounts - /// Discount amount - protected virtual decimal GetDiscountAmount(Product product, - Customer customer, - decimal productPriceWithoutDiscount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - appliedDiscounts = null; - decimal appliedDiscountAmount = decimal.Zero; - - //we don't apply discounts to products with price entered by a customer - if (product.CustomerEntersPrice) - return appliedDiscountAmount; - - //discounts are disabled - if (_catalogSettings.IgnoreDiscounts) - return appliedDiscountAmount; - - var allowedDiscounts = GetAllowedDiscounts(product, customer); - - //no discounts - if (!allowedDiscounts.Any()) - return appliedDiscountAmount; - - appliedDiscounts = allowedDiscounts.GetPreferredDiscount(productPriceWithoutDiscount, out appliedDiscountAmount); - return appliedDiscountAmount; - } - - #endregion - - #region Methods - - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge = decimal.Zero, - bool includeDiscounts = true, - int quantity = 1) - { - decimal discountAmount; - List appliedDiscounts; - return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, - quantity, out discountAmount, out appliedDiscounts); - } - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - out decimal discountAmount, - out List appliedDiscounts) - { - return GetFinalPrice(product, customer, - additionalCharge, includeDiscounts, quantity, - null, null, - out discountAmount, out appliedDiscounts); - } - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts) - { - return GetFinalPrice(product, customer, null, additionalCharge, includeDiscounts, quantity, - rentalStartDate, rentalEndDate, out discountAmount, out appliedDiscounts); - } - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal? overriddenProductPrice, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - discountAmount = decimal.Zero; - appliedDiscounts = new List(); - - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY, - product.Id, - overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, - additionalCharge.ToString(CultureInfo.InvariantCulture), - includeDiscounts, - quantity, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0; - //we do not cache price for rental products - //otherwise, it can cause memory leaks (to store all possible date period combinations) - if (product.IsRental) - cacheTime = 0; - var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () => - { - var result = new ProductPriceForCaching(); - - //initial price - decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price; - - //tier prices - price = product.GetPreferredTierPrice(customer, _storeContext.CurrentStore.Id, quantity) ?? price; - - //additional charge - price = price + additionalCharge; - - //rental products - if (product.IsRental) - if (rentalStartDate.HasValue && rentalEndDate.HasValue) - price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value); - - if (includeDiscounts) - { - //discount - List tmpAppliedDiscounts; - decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts); - price = price - tmpDiscountAmount; - - if (tmpAppliedDiscounts != null) - { - result.AppliedDiscounts = tmpAppliedDiscounts; - result.AppliedDiscountAmount = tmpDiscountAmount; - } - } - - if (price < decimal.Zero) - price = decimal.Zero; - - result.Price = price; - return result; - }); - - if (includeDiscounts) - { - if (cachedPrice.AppliedDiscounts.Any()) - { - appliedDiscounts.AddRange(cachedPrice.AppliedDiscounts); - discountAmount = cachedPrice.AppliedDiscountAmount; - } - } - - return cachedPrice.Price; - } - - - - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true) - { - decimal discountAmount; - List appliedDiscounts; - return GetUnitPrice(shoppingCartItem, includeDiscounts, - out discountAmount, out appliedDiscounts); - } - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts) - { - if (shoppingCartItem == null) - throw new ArgumentNullException("shoppingCartItem"); - - return GetUnitPrice(shoppingCartItem.Product, - shoppingCartItem.Customer, - shoppingCartItem.ShoppingCartType, - shoppingCartItem.Quantity, - shoppingCartItem.AttributesXml, - shoppingCartItem.CustomerEnteredPrice, - shoppingCartItem.RentalStartDateUtc, - shoppingCartItem.RentalEndDateUtc, - includeDiscounts, - out discountAmount, - out appliedDiscounts); - } - /// - /// Gets the shopping cart unit price (one item) - /// - /// Product - /// Customer - /// Shopping cart type - /// Quantity - /// Product atrributes (XML format) - /// Customer entered price (if specified) - /// Rental start date (null for not rental products) - /// Rental end date (null for not rental products) - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(Product product, - Customer customer, - ShoppingCartType shoppingCartType, - int quantity, - string attributesXml, - decimal customerEnteredPrice, - DateTime? rentalStartDate, DateTime? rentalEndDate, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - if (customer == null) - throw new ArgumentNullException("customer"); - - discountAmount = decimal.Zero; - appliedDiscounts = new List(); - - decimal finalPrice; - - var combination = _productAttributeParser.FindProductAttributeCombination(product, attributesXml); - if (combination != null && combination.OverriddenPrice.HasValue) - { - finalPrice = GetFinalPrice(product, - customer, - combination.OverriddenPrice.Value, - decimal.Zero, - includeDiscounts, - quantity, - product.IsRental ? rentalStartDate : null, - product.IsRental ? rentalEndDate : null, - out discountAmount, out appliedDiscounts); - } - else - { - //summarize price of all attributes - decimal attributesTotalPrice = decimal.Zero; - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - if (attributeValues != null) - { - foreach (var attributeValue in attributeValues) - { - attributesTotalPrice += GetProductAttributeValuePriceAdjustment(attributeValue); - } - } - - //get price of a product (with previously calculated price of all attributes) - if (product.CustomerEntersPrice) - { - finalPrice = customerEnteredPrice; - } - else - { - int qty; - if (_shoppingCartSettings.GroupTierPricesForDistinctShoppingCartItems) - { - //the same products with distinct product attributes could be stored as distinct "ShoppingCartItem" records - //so let's find how many of the current products are in the cart - qty = customer.ShoppingCartItems - .Where(x => x.ProductId == product.Id) - .Where(x => x.ShoppingCartType == shoppingCartType) - .Sum(x => x.Quantity); - if (qty == 0) - { - qty = quantity; - } - } - else - { - qty = quantity; - } - finalPrice = GetFinalPrice(product, - customer, - attributesTotalPrice, - includeDiscounts, - qty, - product.IsRental ? rentalStartDate : null, - product.IsRental ? rentalEndDate : null, - out discountAmount, out appliedDiscounts); - } - } - - //rounding - if (_shoppingCartSettings.RoundPricesDuringCalculation) - finalPrice = RoundingHelper.RoundPrice(finalPrice); - - return finalPrice; - } - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart item sub total - public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true) - { - decimal discountAmount; - List appliedDiscounts; - int? maximumDiscountQty; - return GetSubTotal(shoppingCartItem, includeDiscounts, out discountAmount, out appliedDiscounts, out maximumDiscountQty); - } - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items - /// Shopping cart item sub total - public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts, - out int? maximumDiscountQty) - { - if (shoppingCartItem == null) - throw new ArgumentNullException("shoppingCartItem"); - - decimal subTotal; - maximumDiscountQty = null; - - //unit price - var unitPrice = GetUnitPrice(shoppingCartItem, includeDiscounts, - out discountAmount, out appliedDiscounts); - - //discount - if (appliedDiscounts.Any()) - { - //we can properly use "MaximumDiscountedQuantity" property only for one discount (not cumulative ones) - DiscountForCaching oneAndOnlyDiscount = null; - if (appliedDiscounts.Count == 1) - oneAndOnlyDiscount = appliedDiscounts.First(); - - if (oneAndOnlyDiscount != null && - oneAndOnlyDiscount.MaximumDiscountedQuantity.HasValue && - shoppingCartItem.Quantity > oneAndOnlyDiscount.MaximumDiscountedQuantity.Value) - { - maximumDiscountQty = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; - //we cannot apply discount for all shopping cart items - var discountedQuantity = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; - var discountedSubTotal = unitPrice * discountedQuantity; - discountAmount = discountAmount * discountedQuantity; - - var notDiscountedQuantity = shoppingCartItem.Quantity - discountedQuantity; - var notDiscountedUnitPrice = GetUnitPrice(shoppingCartItem, false); - var notDiscountedSubTotal = notDiscountedUnitPrice*notDiscountedQuantity; - - subTotal = discountedSubTotal + notDiscountedSubTotal; - } - else - { - //discount is applied to all items (quantity) - //calculate discount amount for all items - discountAmount = discountAmount * shoppingCartItem.Quantity; - - subTotal = unitPrice * shoppingCartItem.Quantity; - } - } - else - { - subTotal = unitPrice * shoppingCartItem.Quantity; - } - return subTotal; - } - - - /// - /// Gets the product cost (one item) - /// - /// Product - /// Shopping cart item attributes in XML - /// Product cost (one item) - public virtual decimal GetProductCost(Product product, string attributesXml) - { - if (product == null) - throw new ArgumentNullException("product"); - - decimal cost = product.ProductCost; - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - foreach (var attributeValue in attributeValues) - { - switch (attributeValue.AttributeValueType) - { - case AttributeValueType.Simple: - { - //simple attribute - cost += attributeValue.Cost; - } - break; - case AttributeValueType.AssociatedToProduct: - { - //bundled product - var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); - if (associatedProduct != null) - cost += associatedProduct.ProductCost * attributeValue.Quantity; - } - break; - default: - break; - } - } - - return cost; - } - - - - /// - /// Get a price adjustment of a product attribute value - /// - /// Product attribute value - /// Price adjustment - public virtual decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value) - { - if (value == null) - throw new ArgumentNullException("value"); - - var adjustment = decimal.Zero; - switch (value.AttributeValueType) - { - case AttributeValueType.Simple: - { - //simple attribute - adjustment = value.PriceAdjustment; - } - break; - case AttributeValueType.AssociatedToProduct: - { - //bundled product - var associatedProduct = _productService.GetProductById(value.AssociatedProductId); - if (associatedProduct != null) - { - adjustment = GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, includeDiscounts: true) * value.Quantity; - } - } - break; - default: - break; - } - - return adjustment; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Services.Catalog.Cache; +using Nop.Services.Customers; +using Nop.Services.Discounts; +using Nop.Services.Tax; +using Nop.Core.Domain.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Price calculation service + /// + public partial class PriceCalculationService : IPriceCalculationService + { + #region Fields + + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IDiscountService _discountService; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductService _productService; + private readonly ICacheManager _cacheManager; + private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly CatalogSettings _catalogSettings; + private readonly ITaxService _taxService; + + #endregion + + #region Ctor + + public PriceCalculationService(IWorkContext workContext, + IStoreContext storeContext, + IDiscountService discountService, + ICategoryService categoryService, + IManufacturerService manufacturerService, + IProductAttributeParser productAttributeParser, + IProductService productService, + ICacheManager cacheManager, + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings, + ITaxService taxService) + { + this._workContext = workContext; + this._storeContext = storeContext; + this._discountService = discountService; + this._categoryService = categoryService; + this._manufacturerService = manufacturerService; + this._productAttributeParser = productAttributeParser; + this._productService = productService; + this._cacheManager = cacheManager; + this._shoppingCartSettings = shoppingCartSettings; + this._catalogSettings = catalogSettings; + this._taxService = taxService; + } + + #endregion + + #region Nested classes + + [Serializable] + protected class ProductPriceForCaching + { + public ProductPriceForCaching() + { + this.AppliedDiscounts = new List(); + } + + public decimal Price { get; set; } + public decimal AppliedDiscountAmount { get; set; } + public List AppliedDiscounts { get; set; } + } + #endregion + + #region Utilities + + /// + /// Gets allowed discounts applied to product + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToProduct(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + if (product.HasDiscountsApplied) + { + //we use this property ("HasDiscountsApplied") for performance optimization to avoid unnecessary database calls + foreach (var discount in product.AppliedDiscounts) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + discount.DiscountType == DiscountType.AssignedToSkus) + allowedDiscounts.Add(discount.MapDiscount()); + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts applied to categories + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToCategories(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + //load cached discount models (performance optimization) + foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)) + { + //load identifier of categories with this discount applied to + var discountCategoryIds = _discountService.GetAppliedCategoryIds(discount, customer); + + //compare with categories of this product + var productCategoryIds = new List(); + if (discountCategoryIds.Any()) + { + //load identifier of categories of this product + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_CATEGORY_IDS_MODEL_KEY, + product.Id, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + productCategoryIds = _cacheManager.Get(cacheKey, () => + _categoryService + .GetProductCategoriesByProductId(product.Id) + .Select(x => x.CategoryId) + .ToList()); + } + + foreach (var categoryId in productCategoryIds) + { + if (discountCategoryIds.Contains(categoryId)) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + } + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts applied to manufacturers + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToManufacturers(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)) + { + //load identifier of manufacturers with this discount applied to + var discountManufacturerIds = _discountService.GetAppliedManufacturerIds(discount, customer); + + //compare with manufacturers of this product + var productManufacturerIds = new List(); + if (discountManufacturerIds.Any()) + { + //load identifier of manufacturers of this product + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_MANUFACTURER_IDS_MODEL_KEY, + product.Id, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + productManufacturerIds = _cacheManager.Get(cacheKey, () => + _manufacturerService + .GetProductManufacturersByProductId(product.Id) + .Select(x => x.ManufacturerId) + .ToList()); + } + + foreach (var manufacturerId in productManufacturerIds) + { + if (discountManufacturerIds.Contains(manufacturerId)) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + } + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscounts(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + //discounts applied to products + foreach (var discount in GetAllowedDiscountsAppliedToProduct(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + //discounts applied to categories + foreach (var discount in GetAllowedDiscountsAppliedToCategories(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + //discounts applied to manufacturers + foreach (var discount in GetAllowedDiscountsAppliedToManufacturers(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + return allowedDiscounts; + } + + /// + /// Gets discount amount + /// + /// Product + /// The customer + /// Already calculated product price without discount + /// Applied discounts + /// Discount amount + protected virtual decimal GetDiscountAmount(Product product, + Customer customer, + decimal productPriceWithoutDiscount, + out List appliedDiscounts) + { + if (product == null) + throw new ArgumentNullException("product"); + + appliedDiscounts = null; + decimal appliedDiscountAmount = decimal.Zero; + + //we don't apply discounts to products with price entered by a customer + if (product.CustomerEntersPrice) + return appliedDiscountAmount; + + //discounts are disabled + if (_catalogSettings.IgnoreDiscounts) + return appliedDiscountAmount; + + var allowedDiscounts = GetAllowedDiscounts(product, customer); + + //no discounts + if (!allowedDiscounts.Any()) + return appliedDiscountAmount; + + appliedDiscounts = allowedDiscounts.GetPreferredDiscount(productPriceWithoutDiscount, out appliedDiscountAmount); + return appliedDiscountAmount; + } + + #endregion + + #region Methods + + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge = decimal.Zero, + bool includeDiscounts = true, + int quantity = 1) + { + decimal discountAmount; + List appliedDiscounts; + return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, + quantity, out discountAmount, out appliedDiscounts); + } + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Applied discount amount + /// Applied discounts + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + out decimal discountAmount, + out List appliedDiscounts) + { + return GetFinalPrice(product, customer, + additionalCharge, includeDiscounts, quantity, + null, null, + out discountAmount, out appliedDiscounts); + } + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts) + { + return GetFinalPrice(product, customer, null, additionalCharge, includeDiscounts, quantity, + rentalStartDate, rentalEndDate, out discountAmount, out appliedDiscounts); + } + /// + /// Gets the final price + /// + /// Product + /// The customer + /// Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations + /// Additional charge + /// A value indicating whether include discounts or not for final price computation + /// Shopping cart item quantity + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal? overriddenProductPrice, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts) + { + if (product == null) + throw new ArgumentNullException("product"); + + discountAmount = decimal.Zero; + appliedDiscounts = new List(); + + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY, + product.Id, + overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, + additionalCharge.ToString(CultureInfo.InvariantCulture), + includeDiscounts, + quantity, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0; + //we do not cache price for rental products + //otherwise, it can cause memory leaks (to store all possible date period combinations) + if (product.IsRental) + cacheTime = 0; + var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () => + { + var result = new ProductPriceForCaching(); + + //initial price + decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price; + + //tier prices + price = product.GetPreferredTierPrice(customer, _storeContext.CurrentStore.Id, quantity) ?? price; + + //additional charge + price = price + additionalCharge; + + //rental products + if (product.IsRental) + if (rentalStartDate.HasValue && rentalEndDate.HasValue) + price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value); + + if (includeDiscounts) + { + //discount + List tmpAppliedDiscounts; + decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts); + price = price - tmpDiscountAmount; + + if (tmpAppliedDiscounts != null) + { + result.AppliedDiscounts = tmpAppliedDiscounts; + result.AppliedDiscountAmount = tmpDiscountAmount; + } + } + + if (price < decimal.Zero) + price = decimal.Zero; + + result.Price = price; + return result; + }); + + if (includeDiscounts) + { + if (cachedPrice.AppliedDiscounts.Any()) + { + appliedDiscounts.AddRange(cachedPrice.AppliedDiscounts); + discountAmount = cachedPrice.AppliedDiscountAmount; + } + } + + return cachedPrice.Price; + } + + + + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true) + { + decimal discountAmount; + List appliedDiscounts; + return GetUnitPrice(shoppingCartItem, includeDiscounts, + out discountAmount, out appliedDiscounts); + } + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts) + { + if (shoppingCartItem == null) + throw new ArgumentNullException("shoppingCartItem"); + var attributesXml = shoppingCartItem.AttributesXml; + var unitPrice = GetUnitPrice(shoppingCartItem.Product, + shoppingCartItem.Customer, + shoppingCartItem.ShoppingCartType, + shoppingCartItem.Quantity, + ref attributesXml, + shoppingCartItem.CustomerEnteredPrice, + shoppingCartItem.RentalStartDateUtc, + shoppingCartItem.RentalEndDateUtc, + includeDiscounts, + out discountAmount, + out appliedDiscounts); + shoppingCartItem.AttributesXml = attributesXml; + return unitPrice; + } + /// + /// Gets the shopping cart unit price (one item) + /// + /// Product + /// Customer + /// Shopping cart type + /// Quantity + /// Product atrributes (XML format) + /// Customer entered price (if specified) + /// Rental start date (null for not rental products) + /// Rental end date (null for not rental products) + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(Product product, + Customer customer, + ShoppingCartType shoppingCartType, + int quantity, + ref string attributesXml, + decimal customerEnteredPrice, + DateTime? rentalStartDate, DateTime? rentalEndDate, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts) + { + if (product == null) + throw new ArgumentNullException("product"); + + if (customer == null) + throw new ArgumentNullException("customer"); + + discountAmount = decimal.Zero; + appliedDiscounts = new List(); + + var taxWeightSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); + + decimal finalPrice = decimal.Zero; + + var combination = _productAttributeParser.FindProductAttributeCombination(product, attributesXml); + if (combination != null && combination.OverriddenPrice.HasValue) + { + finalPrice = GetFinalPrice(product, + customer, + combination.OverriddenPrice.Value, + decimal.Zero, + includeDiscounts, + quantity, + product.IsRental ? rentalStartDate : null, + product.IsRental ? rentalEndDate : null, + out discountAmount, out appliedDiscounts); + } + + //summarize price of all attributes and build up taxSummary + //needed for VAT calculation of product bundles + decimal attributesTotalPrice = decimal.Zero; + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + if (attributeValues != null) + { + decimal taxRate; + foreach (var attributeValue in attributeValues) + { + var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); + attributesTotalPrice += attributePrice; + + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) + { + //build taxSummary for tax subdivision + var attributePriceTax = _taxService.GetProductPrice(associatedProduct, attributePrice, customer, out taxRate); + taxWeightSummary.AddRate(taxRate, attributePrice); + } + } + } + } + + if (combination == null || !combination.OverriddenPrice.HasValue) + { + //get price of a product (with previously calculated price of all attributes) + if (product.CustomerEntersPrice) + { + finalPrice = customerEnteredPrice; + } + else + { + int qty; + if (_shoppingCartSettings.GroupTierPricesForDistinctShoppingCartItems) + { + //the same products with distinct product attributes could be stored as distinct "ShoppingCartItem" records + //so let's find how many of the current products are in the cart + qty = customer.ShoppingCartItems + .Where(x => x.ProductId == product.Id) + .Where(x => x.ShoppingCartType == shoppingCartType) + .Sum(x => x.Quantity); + if (qty == 0) + { + qty = quantity; + } + } + else + { + qty = quantity; + } + finalPrice = GetFinalPrice(product, + customer, + attributesTotalPrice, + includeDiscounts, + qty, + product.IsRental ? rentalStartDate : null, + product.IsRental ? rentalEndDate : null, + out discountAmount, out appliedDiscounts); + } + } + + //rounding + if (_shoppingCartSettings.RoundPricesDuringCalculation) + finalPrice = RoundingHelper.RoundPrice(finalPrice); + + //add tax attributes + if (taxWeightSummary != null && taxWeightSummary.TaxRates.Any()) + { + decimal taxRate; + var itemAmount = _taxService.GetProductPrice(product, 0, customer, out taxRate); + var baseProductPrice = finalPrice - attributesTotalPrice; + if (baseProductPrice > decimal.Zero) + taxWeightSummary.AddRate(taxRate, baseProductPrice); + + if (taxWeightSummary.TaxRates.Count() > 1 || + (taxWeightSummary.TaxRates.FirstOrDefault().Key != taxRate && baseProductPrice != finalPrice) //use associated product taxRate when different + ) + { + taxWeightSummary.CalculateAmounts(); + attributesXml = _productAttributeParser.AddTaxAttribute(attributesXml, taxWeightSummary); + } + } + + return finalPrice; + } + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart item sub total + public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true) + { + decimal discountAmount; + List appliedDiscounts; + int? maximumDiscountQty; + return GetSubTotal(shoppingCartItem, includeDiscounts, out discountAmount, out appliedDiscounts, out maximumDiscountQty); + } + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items + /// Shopping cart item sub total + public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts, + out int? maximumDiscountQty) + { + if (shoppingCartItem == null) + throw new ArgumentNullException("shoppingCartItem"); + + decimal subTotal; + maximumDiscountQty = null; + + //unit price + var unitPrice = GetUnitPrice(shoppingCartItem, includeDiscounts, + out discountAmount, out appliedDiscounts); + + //discount + if (appliedDiscounts.Any()) + { + //we can properly use "MaximumDiscountedQuantity" property only for one discount (not cumulative ones) + DiscountForCaching oneAndOnlyDiscount = null; + if (appliedDiscounts.Count == 1) + oneAndOnlyDiscount = appliedDiscounts.First(); + + if (oneAndOnlyDiscount != null && + oneAndOnlyDiscount.MaximumDiscountedQuantity.HasValue && + shoppingCartItem.Quantity > oneAndOnlyDiscount.MaximumDiscountedQuantity.Value) + { + maximumDiscountQty = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; + //we cannot apply discount for all shopping cart items + var discountedQuantity = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; + var discountedSubTotal = unitPrice * discountedQuantity; + discountAmount = discountAmount * discountedQuantity; + + var notDiscountedQuantity = shoppingCartItem.Quantity - discountedQuantity; + var notDiscountedUnitPrice = GetUnitPrice(shoppingCartItem, false); + var notDiscountedSubTotal = notDiscountedUnitPrice*notDiscountedQuantity; + + subTotal = discountedSubTotal + notDiscountedSubTotal; + } + else + { + //discount is applied to all items (quantity) + //calculate discount amount for all items + discountAmount = discountAmount * shoppingCartItem.Quantity; + + subTotal = unitPrice * shoppingCartItem.Quantity; + } + } + else + { + subTotal = unitPrice * shoppingCartItem.Quantity; + } + return subTotal; + } + + + /// + /// Gets the product cost (one item) + /// + /// Product + /// Shopping cart item attributes in XML + /// Product cost (one item) + public virtual decimal GetProductCost(Product product, string attributesXml) + { + if (product == null) + throw new ArgumentNullException("product"); + + decimal cost = product.ProductCost; + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + foreach (var attributeValue in attributeValues) + { + switch (attributeValue.AttributeValueType) + { + case AttributeValueType.Simple: + { + //simple attribute + cost += attributeValue.Cost; + } + break; + case AttributeValueType.AssociatedToProduct: + { + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) + cost += associatedProduct.ProductCost * attributeValue.Quantity; + } + break; + default: + break; + } + } + + return cost; + } + + + + /// + /// Get a price adjustment of a product attribute value + /// + /// Product attribute value + /// Price adjustment + public virtual decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value) + { + if (value == null) + throw new ArgumentNullException("value"); + + var adjustment = decimal.Zero; + switch (value.AttributeValueType) + { + case AttributeValueType.Simple: + { + //simple attribute + adjustment = value.PriceAdjustment; + } + break; + case AttributeValueType.AssociatedToProduct: + { + //bundled product + var associatedProduct = _productService.GetProductById(value.AssociatedProductId); + if (associatedProduct != null) + { + adjustment = GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, includeDiscounts: true) * value.Quantity; + } + } + break; + default: + break; + } + + return adjustment; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs index 1b06a5c15db..7ec66ba7c1c 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs @@ -1,243 +1,276 @@ -using System; -using System.Text; -using System.Web; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Core.Html; -using Nop.Services.Directory; -using Nop.Services.Localization; -using Nop.Services.Media; -using Nop.Services.Tax; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute formatter - /// - public partial class ProductAttributeFormatter : IProductAttributeFormatter - { - private readonly IWorkContext _workContext; - private readonly IProductAttributeService _productAttributeService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly ICurrencyService _currencyService; - private readonly ILocalizationService _localizationService; - private readonly ITaxService _taxService; - private readonly IPriceFormatter _priceFormatter; - private readonly IDownloadService _downloadService; - private readonly IWebHelper _webHelper; - private readonly IPriceCalculationService _priceCalculationService; - private readonly ShoppingCartSettings _shoppingCartSettings; - - public ProductAttributeFormatter(IWorkContext workContext, - IProductAttributeService productAttributeService, - IProductAttributeParser productAttributeParser, - ICurrencyService currencyService, - ILocalizationService localizationService, - ITaxService taxService, - IPriceFormatter priceFormatter, - IDownloadService downloadService, - IWebHelper webHelper, - IPriceCalculationService priceCalculationService, - ShoppingCartSettings shoppingCartSettings) - { - this._workContext = workContext; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._currencyService = currencyService; - this._localizationService = localizationService; - this._taxService = taxService; - this._priceFormatter = priceFormatter; - this._downloadService = downloadService; - this._webHelper = webHelper; - this._priceCalculationService = priceCalculationService; - this._shoppingCartSettings = shoppingCartSettings; - } - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Attributes - public virtual string FormatAttributes(Product product, string attributesXml) - { - var customer = _workContext.CurrentCustomer; - return FormatAttributes(product, attributesXml, customer); - } - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Customer - /// Serapator - /// A value indicating whether to encode (HTML) values - /// A value indicating whether to render prices - /// A value indicating whether to render product attributes - /// A value indicating whether to render gift card attributes - /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// Attributes - public virtual string FormatAttributes(Product product, string attributesXml, - Customer customer, string serapator = "
", bool htmlEncode = true, bool renderPrices = true, - bool renderProductAttributes = true, bool renderGiftCardAttributes = true, - bool allowHyperlinks = true) - { - var result = new StringBuilder(); - - //attributes - if (renderProductAttributes) - { - foreach (var attribute in _productAttributeParser.ParseProductAttributeMappings(attributesXml)) - { - //attributes without values - if (!attribute.ShouldHaveValues()) - { - foreach (var value in _productAttributeParser.ParseValues(attributesXml, attribute.Id)) - { - var formattedAttribute = string.Empty; - if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox) - { - //multiline textbox - var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); - - //encode (if required) - if (htmlEncode) - attributeName = HttpUtility.HtmlEncode(attributeName); - - //we never encode multiline textbox input - formattedAttribute = string.Format("{0}: {1}", attributeName, HtmlHelper.FormatText(value, false, true, false, false, false, false)); - } - else if (attribute.AttributeControlType == AttributeControlType.FileUpload) - { - //file upload - Guid downloadGuid; - Guid.TryParse(value, out downloadGuid); - var download = _downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - var fileName = string.Format("{0}{1}", download.Filename ?? download.DownloadGuid.ToString(), download.Extension); - - //encode (if required) - if (htmlEncode) - fileName = HttpUtility.HtmlEncode(fileName); - - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var attributeText = allowHyperlinks ? string.Format("{2}", - _webHelper.GetStoreLocation(false), download.DownloadGuid, fileName) : fileName; - - var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); - - //encode (if required) - if (htmlEncode) - attributeName = HttpUtility.HtmlEncode(attributeName); - - formattedAttribute = string.Format("{0}: {1}", attributeName, attributeText); - } - } - else - { - //other attributes (textbox, datepicker) - formattedAttribute = string.Format("{0}: {1}", attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), value); - - //encode (if required) - if (htmlEncode) - formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); - } - - if (!string.IsNullOrEmpty(formattedAttribute)) - { - if (result.Length > 0) - result.Append(serapator); - result.Append(formattedAttribute); - } - } - } - //product attribute values - else - { - foreach (var attributeValue in _productAttributeParser.ParseProductAttributeValues(attributesXml, attribute.Id)) - { - var formattedAttribute = string.Format("{0}: {1}", - attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), - attributeValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); - - if (renderPrices) - { - decimal taxRate; - var attributeValuePriceAdjustment = _priceCalculationService.GetProductAttributeValuePriceAdjustment(attributeValue); - var priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, customer, out taxRate); - var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); - if (priceAdjustmentBase > 0) - formattedAttribute += string.Format(" [+{0}]", _priceFormatter.FormatPrice(priceAdjustment, false, false)); - else if (priceAdjustmentBase < decimal.Zero) - formattedAttribute += string.Format(" [-{0}]", _priceFormatter.FormatPrice(-priceAdjustment, false, false)); - } - - //display quantity - if (_shoppingCartSettings.RenderAssociatedAttributeValueQuantity && attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) - { - //render only when more than 1 - if (attributeValue.Quantity > 1) - formattedAttribute += string.Format(_localizationService.GetResource("ProductAttributes.Quantity"), attributeValue.Quantity); - } - - //encode (if required) - if (htmlEncode) - formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); - - if (!string.IsNullOrEmpty(formattedAttribute)) - { - if (result.Length > 0) - result.Append(serapator); - result.Append(formattedAttribute); - } - } - } - } - } - - //gift cards - if (renderGiftCardAttributes) - { - if (product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - _productAttributeParser.GetGiftCardAttribute(attributesXml, out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - //sender - var giftCardFrom = product.GiftCardType == GiftCardType.Virtual ? - string.Format(_localizationService.GetResource("GiftCardAttribute.From.Virtual"), giftCardSenderName, giftCardSenderEmail) : - string.Format(_localizationService.GetResource("GiftCardAttribute.From.Physical"), giftCardSenderName); - //recipient - var giftCardFor = product.GiftCardType == GiftCardType.Virtual ? - string.Format(_localizationService.GetResource("GiftCardAttribute.For.Virtual"), giftCardRecipientName, giftCardRecipientEmail) : - string.Format(_localizationService.GetResource("GiftCardAttribute.For.Physical"), giftCardRecipientName); - - //encode (if required) - if (htmlEncode) - { - giftCardFrom = HttpUtility.HtmlEncode(giftCardFrom); - giftCardFor = HttpUtility.HtmlEncode(giftCardFor); - } - - if (!String.IsNullOrEmpty(result.ToString())) - { - result.Append(serapator); - } - result.Append(giftCardFrom); - result.Append(serapator); - result.Append(giftCardFor); - } - } - return result.ToString(); - } - } -} +using System; +using System.Text; +using System.Web; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Core.Html; +using Nop.Services.Directory; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Tax; +using System.Collections.Generic; +using Nop.Core.Domain.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute formatter + /// + public partial class ProductAttributeFormatter : IProductAttributeFormatter + { + private readonly IWorkContext _workContext; + private readonly IProductAttributeService _productAttributeService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly ICurrencyService _currencyService; + private readonly ILocalizationService _localizationService; + private readonly ITaxService _taxService; + private readonly IPriceFormatter _priceFormatter; + private readonly IDownloadService _downloadService; + private readonly IWebHelper _webHelper; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ShoppingCartSettings _shoppingCartSettings; + + public ProductAttributeFormatter(IWorkContext workContext, + IProductAttributeService productAttributeService, + IProductAttributeParser productAttributeParser, + ICurrencyService currencyService, + ILocalizationService localizationService, + ITaxService taxService, + IPriceFormatter priceFormatter, + IDownloadService downloadService, + IWebHelper webHelper, + IPriceCalculationService priceCalculationService, + ShoppingCartSettings shoppingCartSettings) + { + this._workContext = workContext; + this._productAttributeService = productAttributeService; + this._productAttributeParser = productAttributeParser; + this._currencyService = currencyService; + this._localizationService = localizationService; + this._taxService = taxService; + this._priceFormatter = priceFormatter; + this._downloadService = downloadService; + this._webHelper = webHelper; + this._priceCalculationService = priceCalculationService; + this._shoppingCartSettings = shoppingCartSettings; + } + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Attributes + public virtual string FormatAttributes(Product product, string attributesXml) + { + var customer = _workContext.CurrentCustomer; + return FormatAttributes(product, attributesXml, customer); + } + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Customer + /// Serapator + /// A value indicating whether to encode (HTML) values + /// A value indicating whether to render prices + /// A value indicating whether to render product attributes + /// A value indicating whether to render gift card attributes + /// A value indicating whether to HTML hyperink tags could be rendered (if required) + /// /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// Attributes + public virtual string FormatAttributes(Product product, string attributesXml, + Customer customer, string serapator = "
", bool htmlEncode = true, bool renderPrices = true, + bool renderProductAttributes = true, bool renderGiftCardAttributes = true, + bool allowHyperlinks = true, + decimal subTotal = decimal.MinusOne) + { + var result = new StringBuilder(); + + //attributes + if (renderProductAttributes) + { + foreach (var attribute in _productAttributeParser.ParseProductAttributeMappings(attributesXml)) + { + //attributes without values + if (!attribute.ShouldHaveValues()) + { + foreach (var value in _productAttributeParser.ParseValues(attributesXml, attribute.Id)) + { + var formattedAttribute = string.Empty; + if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox) + { + //multiline textbox + var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + + //encode (if required) + if (htmlEncode) + attributeName = HttpUtility.HtmlEncode(attributeName); + + //we never encode multiline textbox input + formattedAttribute = string.Format("{0}: {1}", attributeName, HtmlHelper.FormatText(value, false, true, false, false, false, false)); + } + else if (attribute.AttributeControlType == AttributeControlType.FileUpload) + { + //file upload + Guid downloadGuid; + Guid.TryParse(value, out downloadGuid); + var download = _downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + var fileName = string.Format("{0}{1}", download.Filename ?? download.DownloadGuid.ToString(), download.Extension); + + //encode (if required) + if (htmlEncode) + fileName = HttpUtility.HtmlEncode(fileName); + + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + var attributeText = allowHyperlinks ? string.Format("{2}", + _webHelper.GetStoreLocation(false), download.DownloadGuid, fileName) : fileName; + + var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + + //encode (if required) + if (htmlEncode) + attributeName = HttpUtility.HtmlEncode(attributeName); + + formattedAttribute = string.Format("{0}: {1}", attributeName, attributeText); + } + } + else + { + //other attributes (textbox, datepicker) + formattedAttribute = string.Format("{0}: {1}", attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), value); + + //encode (if required) + if (htmlEncode) + formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); + } + + if (!string.IsNullOrEmpty(formattedAttribute)) + { + if (result.Length > 0) + result.Append(serapator); + result.Append(formattedAttribute); + } + } + } + //product attribute values + else + { + foreach (var attributeValue in _productAttributeParser.ParseProductAttributeValues(attributesXml, attribute.Id)) + { + var formattedAttribute = string.Format("{0}: {1}", + attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), + attributeValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); + + if (renderPrices) + { + decimal taxRate; + var attributeValuePriceAdjustment = _priceCalculationService.GetProductAttributeValuePriceAdjustment(attributeValue); + var priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, customer, out taxRate); + var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); + if (priceAdjustmentBase > 0) + formattedAttribute += string.Format(" [+{0}]", _priceFormatter.FormatPrice(priceAdjustment, false, false)); + else if (priceAdjustmentBase < decimal.Zero) + formattedAttribute += string.Format(" [-{0}]", _priceFormatter.FormatPrice(-priceAdjustment, false, false)); + } + + //display quantity + if (_shoppingCartSettings.RenderAssociatedAttributeValueQuantity && attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + //render only when more than 1 + if (attributeValue.Quantity > 1) + formattedAttribute += string.Format(_localizationService.GetResource("ProductAttributes.Quantity"), attributeValue.Quantity); + } + + //encode (if required) + if (htmlEncode) + formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); + + if (!string.IsNullOrEmpty(formattedAttribute)) + { + if (result.Length > 0) + result.Append(serapator); + result.Append(formattedAttribute); + } + } + } + } + } + + //gift cards + if (renderGiftCardAttributes) + { + if (product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(attributesXml, out giftCardRecipientName, out giftCardRecipientEmail, + out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + //sender + var giftCardFrom = product.GiftCardType == GiftCardType.Virtual ? + string.Format(_localizationService.GetResource("GiftCardAttribute.From.Virtual"), giftCardSenderName, giftCardSenderEmail) : + string.Format(_localizationService.GetResource("GiftCardAttribute.From.Physical"), giftCardSenderName); + //recipient + var giftCardFor = product.GiftCardType == GiftCardType.Virtual ? + string.Format(_localizationService.GetResource("GiftCardAttribute.For.Virtual"), giftCardRecipientName, giftCardRecipientEmail) : + string.Format(_localizationService.GetResource("GiftCardAttribute.For.Physical"), giftCardRecipientName); + + //encode (if required) + if (htmlEncode) + { + giftCardFrom = HttpUtility.HtmlEncode(giftCardFrom); + giftCardFor = HttpUtility.HtmlEncode(giftCardFor); + } + + if (!String.IsNullOrEmpty(result.ToString())) + { + result.Append(serapator); + } + result.Append(giftCardFrom); + result.Append(serapator); + result.Append(giftCardFor); + } + } + + //attribute tax + if (subTotal != decimal.MinusOne) + { + var taxAttributes = _productAttributeParser.ParseTaxAttribute(attributesXml); + var attribTaxSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); + var attribTax = attribTaxSummary.ParseAttributeRate(subTotal, taxAttributes); + string formattedAttribute = ""; + if (result.Length > 0) + result.Append(serapator); + result.Append(String.Format("{0} {1}
", + _localizationService.GetResource("ShoppingCart.VatRate"), + _localizationService.GetResource("Shoppingcart.Totals.OrderAmount"))); + + foreach (KeyValuePair kvp in attribTax) + { + decimal vatpercentage = kvp.Key; + decimal rateAmount = kvp.Value; + decimal taxRate; + var priceAdjustmentBase = _taxService.GetProductPrice(product, rateAmount, customer, out taxRate); + var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); + var price = _priceFormatter.FormatPrice(priceAdjustment, false, false); + price = htmlEncode ? HttpUtility.HtmlEncode(price) : price; + formattedAttribute = String.Format("{0} {1}
", kvp.Key.ToString(), price); + result.Append(formattedAttribute); + } + + + } + return result.ToString(); + } + } +} diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs index df203866037..919e09b352f 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs @@ -1,783 +1,879 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Xml; -using Nop.Core.Domain.Catalog; -using Nop.Data; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser - { - #region Fields - - private readonly IDbContext _context; - private readonly IProductAttributeService _productAttributeService; - - #endregion - - #region Ctor - - public ProductAttributeParser(IDbContext context, - IProductAttributeService productAttributeService) - { - this._context = context; - this._productAttributeService = productAttributeService; - } - - #endregion - - #region Product attributes - - /// - /// Gets selected product attribute mapping identifiers - /// - /// Attributes in XML format - /// Selected product attribute mapping identifiers - protected virtual IList ParseProductAttributeMappingIds(string attributesXml) - { - var ids = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return ids; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - ids.Add(id); - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return ids; - } - - /// - /// Gets selected product attribute values with the quantity entered by the customer - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Collections of pairs of product attribute values and their quantity - protected IList> ParseValuesWithQuantity(string attributesXml, int productAttributeMappingId) - { - var selectedValues = new List>(); - if (string.IsNullOrEmpty(attributesXml)) - return selectedValues; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - foreach (XmlNode attributeNode in xmlDoc.SelectNodes(@"//Attributes/ProductAttribute")) - { - if (attributeNode.Attributes != null && attributeNode.Attributes["ID"] != null) - { - int attributeId; - if (int.TryParse(attributeNode.Attributes["ID"].InnerText.Trim(), out attributeId) && attributeId == productAttributeMappingId) - { - foreach (XmlNode attributeValue in attributeNode.SelectNodes("ProductAttributeValue")) - { - var value = attributeValue.SelectSingleNode("Value").InnerText.Trim(); - var quantityNode = attributeValue.SelectSingleNode("Quantity"); - selectedValues.Add(new Tuple(value, quantityNode != null ? quantityNode.InnerText.Trim() : string.Empty)); - } - } - } - } - } - catch { } - - return selectedValues; - } - - /// - /// Gets selected product attribute mappings - /// - /// Attributes in XML format - /// Selected product attribute mappings - public virtual IList ParseProductAttributeMappings(string attributesXml) - { - var result = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return result; - - var ids = ParseProductAttributeMappingIds(attributesXml); - foreach (int id in ids) - { - var attribute = _productAttributeService.GetProductAttributeMappingById(id); - if (attribute != null) - { - result.Add(attribute); - } - } - return result; - } - - /// - /// Get product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier; pass 0 to load all values - /// Product attribute values - public virtual IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0) - { - var values = new List(); - if (string.IsNullOrEmpty(attributesXml)) - return values; - - var attributes = ParseProductAttributeMappings(attributesXml); - - //to load values only for the passed product attribute mapping - if (productAttributeMappingId > 0) - attributes = attributes.Where(attribute => attribute.Id == productAttributeMappingId).ToList(); - - foreach (var attribute in attributes) - { - if (!attribute.ShouldHaveValues()) - continue; - - foreach (var attributeValue in ParseValuesWithQuantity(attributesXml, attribute.Id)) - { - int attributeValueId; - if (!string.IsNullOrEmpty(attributeValue.Item1) && int.TryParse(attributeValue.Item1, out attributeValueId)) - { - var value = _productAttributeService.GetProductAttributeValueById(attributeValueId); - if (value != null) - { - int quantity; - if (!string.IsNullOrEmpty(attributeValue.Item2) && int.TryParse(attributeValue.Item2, out quantity) && quantity != value.Quantity) - { - //if customer enters quantity, use new entity with new quantity - var oldValue = _context.LoadOriginalCopy(value); - oldValue.ProductAttributeMapping = attribute; - oldValue.Quantity = quantity; - values.Add(oldValue); - } - else - values.Add(value); - } - } - } - } - return values; - } - - /// - /// Gets selected product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Product attribute values - public virtual IList ParseValues(string attributesXml, int productAttributeMappingId) - { - var selectedValues = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return selectedValues; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 =node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMappingId) - { - var nodeList2 = node1.SelectNodes(@"ProductAttributeValue/Value"); - foreach (XmlNode node2 in nodeList2) - { - string value = node2.InnerText.Trim(); - selectedValues.Add(value); - } - } - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return selectedValues; - } - - /// - /// Adds an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Value - /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) - /// Updated result (XML format) - public virtual string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null) - { - string result = string.Empty; - try - { - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - XmlElement attributeElement = null; - //find existing - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 =node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMapping.Id) - { - attributeElement = (XmlElement)node1; - break; - } - } - } - } - - //create new one if not found - if (attributeElement == null) - { - attributeElement = xmlDoc.CreateElement("ProductAttribute"); - attributeElement.SetAttribute("ID", productAttributeMapping.Id.ToString()); - rootElement.AppendChild(attributeElement); - } - var attributeValueElement = xmlDoc.CreateElement("ProductAttributeValue"); - attributeElement.AppendChild(attributeValueElement); - - var attributeValueValueElement = xmlDoc.CreateElement("Value"); - attributeValueValueElement.InnerText = value; - attributeValueElement.AppendChild(attributeValueValueElement); - - //the quantity entered by the customer - if (quantity.HasValue) - { - var attributeValueQuantity = xmlDoc.CreateElement("Quantity"); - attributeValueQuantity.InnerText = quantity.ToString(); - attributeValueElement.AppendChild(attributeValueQuantity); - } - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Remove an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Updated result (XML format) - public virtual string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping) - { - string result = string.Empty; - try - { - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - XmlElement attributeElement = null; - //find existing - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMapping.Id) - { - attributeElement = (XmlElement)node1; - break; - } - } - } - } - - //found - if (attributeElement != null) - { - rootElement.RemoveChild(attributeElement); - } - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// A value indicating whether we should ignore non-combinable attributes - /// A value indicating whether we should ignore the quantity of attribute value entered by the customer - /// Result - public virtual bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true) - { - var attributes1 = ParseProductAttributeMappings(attributesXml1); - if (ignoreNonCombinableAttributes) - { - attributes1 = attributes1.Where(x => !x.IsNonCombinable()).ToList(); - } - var attributes2 = ParseProductAttributeMappings(attributesXml2); - if (ignoreNonCombinableAttributes) - { - attributes2 = attributes2.Where(x => !x.IsNonCombinable()).ToList(); - } - if (attributes1.Count != attributes2.Count) - return false; - - bool attributesEqual = true; - foreach (var a1 in attributes1) - { - bool hasAttribute = false; - foreach (var a2 in attributes2) - { - if (a1.Id == a2.Id) - { - hasAttribute = true; - var values1Str = ParseValuesWithQuantity(attributesXml1, a1.Id); - var values2Str = ParseValuesWithQuantity(attributesXml2, a2.Id); - if (values1Str.Count == values2Str.Count) - { - foreach (var str1 in values1Str) - { - bool hasValue = false; - foreach (var str2 in values2Str) - { - //case insensitive? - //if (str1.Trim().ToLower() == str2.Trim().ToLower()) - if (str1.Item1.Trim() == str2.Item1.Trim()) - { - hasValue = ignoreQuantity ? true : str1.Item2.Trim() == str2.Item2.Trim(); - break; - } - } - - if (!hasValue) - { - attributesEqual = false; - break; - } - } - } - else - { - attributesEqual = false; - break; - } - } - } - - if (hasAttribute == false) - { - attributesEqual = false; - break; - } - } - - return attributesEqual; - } - - /// - /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified - /// - /// Product attribute - /// Selected attributes (XML format) - /// Result - public virtual bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml) - { - if (pam == null) - throw new ArgumentNullException("pam"); - - var conditionAttributeXml = pam.ConditionAttributeXml; - if (String.IsNullOrEmpty(conditionAttributeXml)) - //no condition - return null; - - //load an attribute this one depends on - var dependOnAttribute = ParseProductAttributeMappings(conditionAttributeXml).FirstOrDefault(); - if (dependOnAttribute == null) - return true; - - var valuesThatShouldBeSelected = ParseValues(conditionAttributeXml, dependOnAttribute.Id) - //a workaround here: - //ConditionAttributeXml can contain "empty" values (nothing is selected) - //but in other cases (like below) we do not store empty values - //that's why we remove empty values here - .Where(x => !String.IsNullOrEmpty(x)) - .ToList(); - var selectedValues = ParseValues(selectedAttributesXml, dependOnAttribute.Id); - if (valuesThatShouldBeSelected.Count != selectedValues.Count) - return false; - - //compare values - var allFound = true; - foreach (var t1 in valuesThatShouldBeSelected) - { - bool found = false; - foreach (var t2 in selectedValues) - if (t1 == t2) - found = true; - if (!found) - allFound = false; - } - - return allFound; - } - - /// - /// Finds a product attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// A value indicating whether we should ignore non-combinable attributes - /// Found product attribute combination - public virtual ProductAttributeCombination FindProductAttributeCombination(Product product, - string attributesXml, bool ignoreNonCombinableAttributes = true) - { - if (product == null) - throw new ArgumentNullException("product"); - - var combinations = _productAttributeService.GetAllProductAttributeCombinations(product.Id); - return combinations.FirstOrDefault(x => - AreProductAttributesEqual(x.AttributesXml, attributesXml, ignoreNonCombinableAttributes)); - } - - /// - /// Generate all combinations - /// - /// Product - /// A value indicating whether we should ignore non-combinable attributes - /// Attribute combinations in XML format - public virtual IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false) - { - if (product == null) - throw new ArgumentNullException("product"); - - var allProductAttributMappings = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - if (ignoreNonCombinableAttributes) - { - allProductAttributMappings = allProductAttributMappings.Where(x => !x.IsNonCombinable()).ToList(); - } - var allPossibleAttributeCombinations = new List>(); - for (int counter = 0; counter < (1 << allProductAttributMappings.Count); ++counter) - { - var combination = new List(); - for (int i = 0; i < allProductAttributMappings.Count; ++i) - { - if ((counter & (1 << i)) == 0) - { - combination.Add(allProductAttributMappings[i]); - } - } - - allPossibleAttributeCombinations.Add(combination); - } - - var allAttributesXml = new List(); - foreach (var combination in allPossibleAttributeCombinations) - { - var attributesXml = new List(); - foreach (var pam in combination) - { - if (!pam.ShouldHaveValues()) - continue; - - var attributeValues = _productAttributeService.GetProductAttributeValues(pam.Id); - if (!attributeValues.Any()) - continue; - - //checkboxes could have several values ticked - var allPossibleCheckboxCombinations = new List>(); - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - for (int counter = 0; counter < (1 << attributeValues.Count); ++counter) - { - var checkboxCombination = new List(); - for (int i = 0; i < attributeValues.Count; ++i) - { - if ((counter & (1 << i)) == 0) - { - checkboxCombination.Add(attributeValues[i]); - } - } - - allPossibleCheckboxCombinations.Add(checkboxCombination); - } - } - - if (!attributesXml.Any()) - { - //first set of values - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - //checkboxes could have several values ticked - foreach (var checkboxCombination in allPossibleCheckboxCombinations) - { - var tmp1 = ""; - foreach (var checkboxValue in checkboxCombination) - { - tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); - } - if (!String.IsNullOrEmpty(tmp1)) - { - attributesXml.Add(tmp1); - } - } - } - else - { - //other attribute types (dropdownlist, radiobutton, color squares) - foreach (var attributeValue in attributeValues) - { - var tmp1 = AddProductAttribute("", pam, attributeValue.Id.ToString()); - attributesXml.Add(tmp1); - } - } - } - else - { - //next values. let's "append" them to already generated attribute combinations in XML format - var attributesXmlTmp = new List(); - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - //checkboxes could have several values ticked - foreach (var str1 in attributesXml) - { - foreach (var checkboxCombination in allPossibleCheckboxCombinations) - { - var tmp1 = str1; - foreach (var checkboxValue in checkboxCombination) - { - tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); - } - if (!String.IsNullOrEmpty(tmp1)) - { - attributesXmlTmp.Add(tmp1); - } - } - } - } - else - { - //other attribute types (dropdownlist, radiobutton, color squares) - foreach (var attributeValue in attributeValues) - { - foreach (var str1 in attributesXml) - { - var tmp1 = AddProductAttribute(str1, pam, attributeValue.Id.ToString()); - attributesXmlTmp.Add(tmp1); - } - } - } - attributesXml.Clear(); - attributesXml.AddRange(attributesXmlTmp); - } - } - allAttributesXml.AddRange(attributesXml); - } - - //validate conditional attributes (if specified) - //minor workaround: - //once it's done (validation), then we could have some duplicated combinations in result - //we don't remove them here (for performance optimization) because anyway it'll be done in the "GenerateAllAttributeCombinations" method of ProductController - for (int i = 0; i < allAttributesXml.Count; i++) - { - var attributesXml = allAttributesXml[i]; - foreach (var attribute in allProductAttributMappings) - { - var conditionMet = IsConditionMet(attribute, attributesXml); - if (conditionMet.HasValue && !conditionMet.Value) - { - allAttributesXml[i] = RemoveProductAttribute(attributesXml, attribute); - } - } - } - return allAttributesXml; - } - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - public string AddGiftCardAttribute(string attributesXml, string recipientName, - string recipientEmail, string senderName, string senderEmail, string giftCardMessage) - { - string result = string.Empty; - try - { - recipientName = recipientName.Trim(); - recipientEmail = recipientEmail.Trim(); - senderName = senderName.Trim(); - senderEmail = senderEmail.Trim(); - - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - var giftCardElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo"); - if (giftCardElement == null) - { - giftCardElement = xmlDoc.CreateElement("GiftCardInfo"); - rootElement.AppendChild(giftCardElement); - } - - var recipientNameElement = xmlDoc.CreateElement("RecipientName"); - recipientNameElement.InnerText = recipientName; - giftCardElement.AppendChild(recipientNameElement); - - var recipientEmailElement = xmlDoc.CreateElement("RecipientEmail"); - recipientEmailElement.InnerText = recipientEmail; - giftCardElement.AppendChild(recipientEmailElement); - - var senderNameElement = xmlDoc.CreateElement("SenderName"); - senderNameElement.InnerText = senderName; - giftCardElement.AppendChild(senderNameElement); - - var senderEmailElement = xmlDoc.CreateElement("SenderEmail"); - senderEmailElement.InnerText = senderEmail; - giftCardElement.AppendChild(senderEmailElement); - - var messageElement = xmlDoc.CreateElement("Message"); - messageElement.InnerText = giftCardMessage; - giftCardElement.AppendChild(messageElement); - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Get gift card attrbibutes - /// - /// Attributes - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - public void GetGiftCardAttribute(string attributesXml, out string recipientName, - out string recipientEmail, out string senderName, - out string senderEmail, out string giftCardMessage) - { - recipientName = string.Empty; - recipientEmail = string.Empty; - senderName = string.Empty; - senderEmail = string.Empty; - giftCardMessage = string.Empty; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var recipientNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientName"); - var recipientEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientEmail"); - var senderNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderName"); - var senderEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderEmail"); - var messageElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/Message"); - - if (recipientNameElement != null) - recipientName = recipientNameElement.InnerText; - if (recipientEmailElement != null) - recipientEmail = recipientEmailElement.InnerText; - if (senderNameElement != null) - senderName = senderNameElement.InnerText; - if (senderEmailElement != null) - senderEmail = senderEmailElement.InnerText; - if (messageElement != null) - giftCardMessage = messageElement.InnerText; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Nop.Core.Domain.Catalog; +using Nop.Data; +using Nop.Services.Tax; +using System.Globalization; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute parser + /// + public partial class ProductAttributeParser : IProductAttributeParser + { + #region Fields + + private readonly IDbContext _context; + private readonly IProductAttributeService _productAttributeService; + + #endregion + + #region Ctor + + public ProductAttributeParser(IDbContext context, + IProductAttributeService productAttributeService) + { + this._context = context; + this._productAttributeService = productAttributeService; + } + + #endregion + + #region Product attributes + + /// + /// Gets selected product attribute mapping identifiers + /// + /// Attributes in XML format + /// Selected product attribute mapping identifiers + protected virtual IList ParseProductAttributeMappingIds(string attributesXml) + { + var ids = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return ids; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 = node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + ids.Add(id); + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return ids; + } + + /// + /// Gets selected product attribute values with the quantity entered by the customer + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Collections of pairs of product attribute values and their quantity + protected IList> ParseValuesWithQuantity(string attributesXml, int productAttributeMappingId) + { + var selectedValues = new List>(); + if (string.IsNullOrEmpty(attributesXml)) + return selectedValues; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + foreach (XmlNode attributeNode in xmlDoc.SelectNodes(@"//Attributes/ProductAttribute")) + { + if (attributeNode.Attributes != null && attributeNode.Attributes["ID"] != null) + { + int attributeId; + if (int.TryParse(attributeNode.Attributes["ID"].InnerText.Trim(), out attributeId) && attributeId == productAttributeMappingId) + { + foreach (XmlNode attributeValue in attributeNode.SelectNodes("ProductAttributeValue")) + { + var value = attributeValue.SelectSingleNode("Value").InnerText.Trim(); + var quantityNode = attributeValue.SelectSingleNode("Quantity"); + selectedValues.Add(new Tuple(value, quantityNode != null ? quantityNode.InnerText.Trim() : string.Empty)); + } + } + } + } + } + catch { } + + return selectedValues; + } + + /// + /// Gets selected product attribute mappings + /// + /// Attributes in XML format + /// Selected product attribute mappings + public virtual IList ParseProductAttributeMappings(string attributesXml) + { + var result = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return result; + + var ids = ParseProductAttributeMappingIds(attributesXml); + foreach (int id in ids) + { + var attribute = _productAttributeService.GetProductAttributeMappingById(id); + if (attribute != null) + { + result.Add(attribute); + } + } + return result; + } + + /// + /// Get product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier; pass 0 to load all values + /// Product attribute values + public virtual IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0) + { + var values = new List(); + if (string.IsNullOrEmpty(attributesXml)) + return values; + + var attributes = ParseProductAttributeMappings(attributesXml); + + //to load values only for the passed product attribute mapping + if (productAttributeMappingId > 0) + attributes = attributes.Where(attribute => attribute.Id == productAttributeMappingId).ToList(); + + foreach (var attribute in attributes) + { + if (!attribute.ShouldHaveValues()) + continue; + + foreach (var attributeValue in ParseValuesWithQuantity(attributesXml, attribute.Id)) + { + int attributeValueId; + if (!string.IsNullOrEmpty(attributeValue.Item1) && int.TryParse(attributeValue.Item1, out attributeValueId)) + { + var value = _productAttributeService.GetProductAttributeValueById(attributeValueId); + if (value != null) + { + int quantity; + if (!string.IsNullOrEmpty(attributeValue.Item2) && int.TryParse(attributeValue.Item2, out quantity) && quantity != value.Quantity) + { + //if customer enters quantity, use new entity with new quantity + var oldValue = _context.LoadOriginalCopy(value); + oldValue.ProductAttributeMapping = attribute; + oldValue.Quantity = quantity; + values.Add(oldValue); + } + else + values.Add(value); + } + } + } + } + return values; + } + + /// + /// Gets selected product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Product attribute values + public virtual IList ParseValues(string attributesXml, int productAttributeMappingId) + { + var selectedValues = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return selectedValues; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 =node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMappingId) + { + var nodeList2 = node1.SelectNodes(@"ProductAttributeValue/Value"); + foreach (XmlNode node2 in nodeList2) + { + string value = node2.InnerText.Trim(); + selectedValues.Add(value); + } + } + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return selectedValues; + } + + /// + /// Adds an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Value + /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) + /// Updated result (XML format) + public virtual string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + XmlElement attributeElement = null; + //find existing + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 =node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMapping.Id) + { + attributeElement = (XmlElement)node1; + break; + } + } + } + } + + //create new one if not found + if (attributeElement == null) + { + attributeElement = xmlDoc.CreateElement("ProductAttribute"); + attributeElement.SetAttribute("ID", productAttributeMapping.Id.ToString()); + rootElement.AppendChild(attributeElement); + } + var attributeValueElement = xmlDoc.CreateElement("ProductAttributeValue"); + attributeElement.AppendChild(attributeValueElement); + + var attributeValueValueElement = xmlDoc.CreateElement("Value"); + attributeValueValueElement.InnerText = value; + attributeValueElement.AppendChild(attributeValueValueElement); + + //the quantity entered by the customer + if (quantity.HasValue) + { + var attributeValueQuantity = xmlDoc.CreateElement("Quantity"); + attributeValueQuantity.InnerText = quantity.ToString(); + attributeValueElement.AppendChild(attributeValueQuantity); + } + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Remove an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Updated result (XML format) + public virtual string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + XmlElement attributeElement = null; + //find existing + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 = node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMapping.Id) + { + attributeElement = (XmlElement)node1; + break; + } + } + } + } + + //found + if (attributeElement != null) + { + rootElement.RemoveChild(attributeElement); + } + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Are attributes equal + /// + /// The attributes of the first product + /// The attributes of the second product + /// A value indicating whether we should ignore non-combinable attributes + /// A value indicating whether we should ignore the quantity of attribute value entered by the customer + /// Result + public virtual bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true) + { + var attributes1 = ParseProductAttributeMappings(attributesXml1); + if (ignoreNonCombinableAttributes) + { + attributes1 = attributes1.Where(x => !x.IsNonCombinable()).ToList(); + } + var attributes2 = ParseProductAttributeMappings(attributesXml2); + if (ignoreNonCombinableAttributes) + { + attributes2 = attributes2.Where(x => !x.IsNonCombinable()).ToList(); + } + if (attributes1.Count != attributes2.Count) + return false; + + bool attributesEqual = true; + foreach (var a1 in attributes1) + { + bool hasAttribute = false; + foreach (var a2 in attributes2) + { + if (a1.Id == a2.Id) + { + hasAttribute = true; + var values1Str = ParseValuesWithQuantity(attributesXml1, a1.Id); + var values2Str = ParseValuesWithQuantity(attributesXml2, a2.Id); + if (values1Str.Count == values2Str.Count) + { + foreach (var str1 in values1Str) + { + bool hasValue = false; + foreach (var str2 in values2Str) + { + //case insensitive? + //if (str1.Trim().ToLower() == str2.Trim().ToLower()) + if (str1.Item1.Trim() == str2.Item1.Trim()) + { + hasValue = ignoreQuantity ? true : str1.Item2.Trim() == str2.Item2.Trim(); + break; + } + } + + if (!hasValue) + { + attributesEqual = false; + break; + } + } + } + else + { + attributesEqual = false; + break; + } + } + } + + if (hasAttribute == false) + { + attributesEqual = false; + break; + } + } + + return attributesEqual; + } + + /// + /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified + /// + /// Product attribute + /// Selected attributes (XML format) + /// Result + public virtual bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml) + { + if (pam == null) + throw new ArgumentNullException("pam"); + + var conditionAttributeXml = pam.ConditionAttributeXml; + if (String.IsNullOrEmpty(conditionAttributeXml)) + //no condition + return null; + + //load an attribute this one depends on + var dependOnAttribute = ParseProductAttributeMappings(conditionAttributeXml).FirstOrDefault(); + if (dependOnAttribute == null) + return true; + + var valuesThatShouldBeSelected = ParseValues(conditionAttributeXml, dependOnAttribute.Id) + //a workaround here: + //ConditionAttributeXml can contain "empty" values (nothing is selected) + //but in other cases (like below) we do not store empty values + //that's why we remove empty values here + .Where(x => !String.IsNullOrEmpty(x)) + .ToList(); + var selectedValues = ParseValues(selectedAttributesXml, dependOnAttribute.Id); + if (valuesThatShouldBeSelected.Count != selectedValues.Count) + return false; + + //compare values + var allFound = true; + foreach (var t1 in valuesThatShouldBeSelected) + { + bool found = false; + foreach (var t2 in selectedValues) + if (t1 == t2) + found = true; + if (!found) + allFound = false; + } + + return allFound; + } + + /// + /// Finds a product attribute combination by attributes stored in XML + /// + /// Product + /// Attributes in XML format + /// A value indicating whether we should ignore non-combinable attributes + /// Found product attribute combination + public virtual ProductAttributeCombination FindProductAttributeCombination(Product product, + string attributesXml, bool ignoreNonCombinableAttributes = true) + { + if (product == null) + throw new ArgumentNullException("product"); + + var combinations = _productAttributeService.GetAllProductAttributeCombinations(product.Id); + return combinations.FirstOrDefault(x => + AreProductAttributesEqual(x.AttributesXml, attributesXml, ignoreNonCombinableAttributes)); + } + + /// + /// Generate all combinations + /// + /// Product + /// A value indicating whether we should ignore non-combinable attributes + /// Attribute combinations in XML format + public virtual IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false) + { + if (product == null) + throw new ArgumentNullException("product"); + + var allProductAttributMappings = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + if (ignoreNonCombinableAttributes) + { + allProductAttributMappings = allProductAttributMappings.Where(x => !x.IsNonCombinable()).ToList(); + } + var allPossibleAttributeCombinations = new List>(); + for (int counter = 0; counter < (1 << allProductAttributMappings.Count); ++counter) + { + var combination = new List(); + for (int i = 0; i < allProductAttributMappings.Count; ++i) + { + if ((counter & (1 << i)) == 0) + { + combination.Add(allProductAttributMappings[i]); + } + } + + allPossibleAttributeCombinations.Add(combination); + } + + var allAttributesXml = new List(); + foreach (var combination in allPossibleAttributeCombinations) + { + var attributesXml = new List(); + foreach (var pam in combination) + { + if (!pam.ShouldHaveValues()) + continue; + + var attributeValues = _productAttributeService.GetProductAttributeValues(pam.Id); + if (!attributeValues.Any()) + continue; + + //checkboxes could have several values ticked + var allPossibleCheckboxCombinations = new List>(); + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + for (int counter = 0; counter < (1 << attributeValues.Count); ++counter) + { + var checkboxCombination = new List(); + for (int i = 0; i < attributeValues.Count; ++i) + { + if ((counter & (1 << i)) == 0) + { + checkboxCombination.Add(attributeValues[i]); + } + } + + allPossibleCheckboxCombinations.Add(checkboxCombination); + } + } + + if (!attributesXml.Any()) + { + //first set of values + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + //checkboxes could have several values ticked + foreach (var checkboxCombination in allPossibleCheckboxCombinations) + { + var tmp1 = ""; + foreach (var checkboxValue in checkboxCombination) + { + tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); + } + if (!String.IsNullOrEmpty(tmp1)) + { + attributesXml.Add(tmp1); + } + } + } + else + { + //other attribute types (dropdownlist, radiobutton, color squares) + foreach (var attributeValue in attributeValues) + { + var tmp1 = AddProductAttribute("", pam, attributeValue.Id.ToString()); + attributesXml.Add(tmp1); + } + } + } + else + { + //next values. let's "append" them to already generated attribute combinations in XML format + var attributesXmlTmp = new List(); + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + //checkboxes could have several values ticked + foreach (var str1 in attributesXml) + { + foreach (var checkboxCombination in allPossibleCheckboxCombinations) + { + var tmp1 = str1; + foreach (var checkboxValue in checkboxCombination) + { + tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); + } + if (!String.IsNullOrEmpty(tmp1)) + { + attributesXmlTmp.Add(tmp1); + } + } + } + } + else + { + //other attribute types (dropdownlist, radiobutton, color squares) + foreach (var attributeValue in attributeValues) + { + foreach (var str1 in attributesXml) + { + var tmp1 = AddProductAttribute(str1, pam, attributeValue.Id.ToString()); + attributesXmlTmp.Add(tmp1); + } + } + } + attributesXml.Clear(); + attributesXml.AddRange(attributesXmlTmp); + } + } + allAttributesXml.AddRange(attributesXml); + } + + //validate conditional attributes (if specified) + //minor workaround: + //once it's done (validation), then we could have some duplicated combinations in result + //we don't remove them here (for performance optimization) because anyway it'll be done in the "GenerateAllAttributeCombinations" method of ProductController + for (int i = 0; i < allAttributesXml.Count; i++) + { + var attributesXml = allAttributesXml[i]; + foreach (var attribute in allProductAttributMappings) + { + var conditionMet = IsConditionMet(attribute, attributesXml); + if (conditionMet.HasValue && !conditionMet.Value) + { + allAttributesXml[i] = RemoveProductAttribute(attributesXml, attribute); + } + } + } + return allAttributesXml; + } + + #endregion + #region taxAttribute + /// + /// Adds tax subdivision to existing attributesXml + /// + /// Attributes in XML format + /// Set Product tax subdivision + /// Updated result (XML format) + public virtual string AddTaxAttribute(string attributesXml, TaxSummary taxSummary) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + var attributeTaxElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/ProductAttributesTax"); + + if (taxSummary.TaxRates.Any()) + { + //create new ProductAttributesTax element if not found + if (attributeTaxElement == null) + { + attributeTaxElement = xmlDoc.CreateElement("ProductAttributesTax"); + rootElement.AppendChild(attributeTaxElement); + } + + foreach (KeyValuePair kvp in taxSummary.TaxRates) + { + decimal vatpercentage = kvp.Key; + TaxRateEntry taxrate = kvp.Value; + + //find existing + var strVatPer = vatpercentage.ToString(CultureInfo.InvariantCulture); + var strNode = String.Format("//Attributes/ProductAttributesTax/TaxRate[@Rate='{0}']", strVatPer); + var attributeTaxRateElement = (XmlElement)xmlDoc.SelectSingleNode(@strNode); + + //create new one if not found + if (attributeTaxRateElement == null) + { + attributeTaxRateElement = xmlDoc.CreateElement("TaxRate"); + attributeTaxRateElement.SetAttribute("Rate", strVatPer); + attributeTaxElement.AppendChild(attributeTaxRateElement); + } + attributeTaxRateElement.SetAttribute("RateWeight", taxrate.VatRateWeight.ToString(CultureInfo.InvariantCulture)); + + } + } + else + { + //remove attributeTaxElement when no taxRates + if (attributeTaxElement != null) + { + rootElement.RemoveChild(attributeTaxElement); + } + } + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + /// + /// Parse ProductAttributesTax + /// + /// Attributes in XML format + /// SortedDictionary with vatRate and vatRateWeight + public SortedDictionary ParseTaxAttribute(string attributesXml) + { + var taxDict = new SortedDictionary(); + var xmlDoc = new XmlDocument(); + if (!String.IsNullOrEmpty(attributesXml)) + { + xmlDoc.LoadXml(attributesXml); + foreach (XmlElement e in xmlDoc.SelectNodes("//Attributes/ProductAttributesTax/TaxRate")) + { + var style = NumberStyles.AllowDecimalPoint; + decimal rate = decimal.Zero; decimal rateWeight = decimal.Zero; + if (decimal.TryParse(e.GetAttribute("Rate"), style, CultureInfo.InvariantCulture, out rate) && decimal.TryParse(e.GetAttribute("RateWeight"), style, CultureInfo.InvariantCulture, out rateWeight)) + taxDict.Add(rate, rateWeight); + } + + } + return taxDict; + } + #endregion + #region Gift card attributes + + /// + /// Add gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + /// Attributes + public string AddGiftCardAttribute(string attributesXml, string recipientName, + string recipientEmail, string senderName, string senderEmail, string giftCardMessage) + { + string result = string.Empty; + try + { + recipientName = recipientName.Trim(); + recipientEmail = recipientEmail.Trim(); + senderName = senderName.Trim(); + senderEmail = senderEmail.Trim(); + + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + var giftCardElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo"); + if (giftCardElement == null) + { + giftCardElement = xmlDoc.CreateElement("GiftCardInfo"); + rootElement.AppendChild(giftCardElement); + } + + var recipientNameElement = xmlDoc.CreateElement("RecipientName"); + recipientNameElement.InnerText = recipientName; + giftCardElement.AppendChild(recipientNameElement); + + var recipientEmailElement = xmlDoc.CreateElement("RecipientEmail"); + recipientEmailElement.InnerText = recipientEmail; + giftCardElement.AppendChild(recipientEmailElement); + + var senderNameElement = xmlDoc.CreateElement("SenderName"); + senderNameElement.InnerText = senderName; + giftCardElement.AppendChild(senderNameElement); + + var senderEmailElement = xmlDoc.CreateElement("SenderEmail"); + senderEmailElement.InnerText = senderEmail; + giftCardElement.AppendChild(senderEmailElement); + + var messageElement = xmlDoc.CreateElement("Message"); + messageElement.InnerText = giftCardMessage; + giftCardElement.AppendChild(messageElement); + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Get gift card attrbibutes + /// + /// Attributes + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + public void GetGiftCardAttribute(string attributesXml, out string recipientName, + out string recipientEmail, out string senderName, + out string senderEmail, out string giftCardMessage) + { + recipientName = string.Empty; + recipientEmail = string.Empty; + senderName = string.Empty; + senderEmail = string.Empty; + giftCardMessage = string.Empty; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var recipientNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientName"); + var recipientEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientEmail"); + var senderNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderName"); + var senderEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderEmail"); + var messageElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/Message"); + + if (recipientNameElement != null) + recipientName = recipientNameElement.InnerText; + if (recipientEmailElement != null) + recipientEmail = recipientEmailElement.InnerText; + if (senderNameElement != null) + senderName = senderNameElement.InnerText; + if (senderEmailElement != null) + senderEmail = senderEmailElement.InnerText; + if (messageElement != null) + giftCardMessage = messageElement.InnerText; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Common/PdfService7.cs b/src/Libraries/Nop.Services/Common/PdfService7.cs index e8c112a4652..56ccedd41a5 100644 --- a/src/Libraries/Nop.Services/Common/PdfService7.cs +++ b/src/Libraries/Nop.Services/Common/PdfService7.cs @@ -685,7 +685,8 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); } //vatrate - paraPdf = new Paragraph(_priceFormatter.FormatTaxRate(orderItem.VatRate)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.CENTER); + bool hasAttribVat = !String.IsNullOrEmpty(orderItem.AttributesXml) && orderItem.AttributesXml.Contains(" - /// Order processing service - /// - public partial class OrderProcessingService : IOrderProcessingService - { - #region Fields - - private readonly IOrderService _orderService; - private readonly IWebHelper _webHelper; - private readonly ILocalizationService _localizationService; - private readonly ILanguageService _languageService; - private readonly IProductService _productService; - private readonly IPaymentService _paymentService; - private readonly ILogger _logger; - private readonly IOrderTotalCalculationService _orderTotalCalculationService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly IPriceFormatter _priceFormatter; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductAttributeFormatter _productAttributeFormatter; - private readonly IGiftCardService _giftCardService; - private readonly IShoppingCartService _shoppingCartService; - private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; - private readonly IShippingService _shippingService; - private readonly IShipmentService _shipmentService; - private readonly ITaxService _taxService; - private readonly ICustomerService _customerService; - private readonly IDiscountService _discountService; - private readonly IEncryptionService _encryptionService; - private readonly IWorkContext _workContext; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IVendorService _vendorService; - private readonly ICustomerActivityService _customerActivityService; - private readonly ICurrencyService _currencyService; - private readonly IAffiliateService _affiliateService; - private readonly IEventPublisher _eventPublisher; - private readonly IPdfService _pdfService; - private readonly IRewardPointService _rewardPointService; - private readonly IGenericAttributeService _genericAttributeService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - - private readonly ShippingSettings _shippingSettings; - private readonly PaymentSettings _paymentSettings; - private readonly RewardPointsSettings _rewardPointsSettings; - private readonly OrderSettings _orderSettings; - private readonly TaxSettings _taxSettings; - private readonly LocalizationSettings _localizationSettings; - private readonly CurrencySettings _currencySettings; - - private readonly ISettingService _settingService; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Order service - /// Web helper - /// Localization service - /// Language service - /// Product service - /// Payment service - /// Logger - /// Order total calculationservice - /// Price calculation service - /// Price formatter - /// Product attribute parser - /// Product attribute formatter - /// Gift card service - /// Shopping cart service - /// Checkout attribute service - /// Shipping service - /// Shipment service - /// Tax service - /// Customer service - /// Discount service - /// Encryption service - /// Work context - /// Workflow message service - /// Vendor service - /// Customer activity service - /// Currency service - /// Affiliate service - /// Event published - /// PDF service - /// Reward point service - /// Generic attribute service - /// Country service - /// Payment settings - /// Shipping settings - /// Reward points settings - /// Order settings - /// Tax settings - /// Localization settings - /// Currency settings - /// Setting service - public OrderProcessingService(IOrderService orderService, - IWebHelper webHelper, - ILocalizationService localizationService, - ILanguageService languageService, - IProductService productService, - IPaymentService paymentService, - ILogger logger, - IOrderTotalCalculationService orderTotalCalculationService, - IPriceCalculationService priceCalculationService, - IPriceFormatter priceFormatter, - IProductAttributeParser productAttributeParser, - IProductAttributeFormatter productAttributeFormatter, - IGiftCardService giftCardService, - IShoppingCartService shoppingCartService, - ICheckoutAttributeFormatter checkoutAttributeFormatter, - IShippingService shippingService, - IShipmentService shipmentService, - ITaxService taxService, - ICustomerService customerService, - IDiscountService discountService, - IEncryptionService encryptionService, - IWorkContext workContext, - IWorkflowMessageService workflowMessageService, - IVendorService vendorService, - ICustomerActivityService customerActivityService, - ICurrencyService currencyService, - IAffiliateService affiliateService, - IEventPublisher eventPublisher, - IPdfService pdfService, - IRewardPointService rewardPointService, - IGenericAttributeService genericAttributeService, - ICountryService countryService, - IStateProvinceService stateProvinceService, - ShippingSettings shippingSettings, - PaymentSettings paymentSettings, - RewardPointsSettings rewardPointsSettings, - OrderSettings orderSettings, - TaxSettings taxSettings, - LocalizationSettings localizationSettings, - CurrencySettings currencySettings, - ISettingService settingService) - { - this._orderService = orderService; - this._webHelper = webHelper; - this._localizationService = localizationService; - this._languageService = languageService; - this._productService = productService; - this._paymentService = paymentService; - this._logger = logger; - this._orderTotalCalculationService = orderTotalCalculationService; - this._priceCalculationService = priceCalculationService; - this._priceFormatter = priceFormatter; - this._productAttributeParser = productAttributeParser; - this._productAttributeFormatter = productAttributeFormatter; - this._giftCardService = giftCardService; - this._shoppingCartService = shoppingCartService; - this._checkoutAttributeFormatter = checkoutAttributeFormatter; - this._workContext = workContext; - this._workflowMessageService = workflowMessageService; - this._vendorService = vendorService; - this._shippingService = shippingService; - this._shipmentService = shipmentService; - this._taxService = taxService; - this._customerService = customerService; - this._discountService = discountService; - this._encryptionService = encryptionService; - this._customerActivityService = customerActivityService; - this._currencyService = currencyService; - this._affiliateService = affiliateService; - this._eventPublisher = eventPublisher; - this._pdfService = pdfService; - this._rewardPointService = rewardPointService; - this._genericAttributeService = genericAttributeService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - - this._paymentSettings = paymentSettings; - this._shippingSettings = shippingSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._orderSettings = orderSettings; - this._taxSettings = taxSettings; - this._localizationSettings = localizationSettings; - this._currencySettings = currencySettings; - this._settingService = settingService; - } - - #endregion - - #region Nested classes - - protected class PlaceOrderContainter - { - public PlaceOrderContainter() - { - this.Cart = new List(); - this.AppliedDiscounts = new List(); - this.AppliedGiftCards = new List(); - } - - public Customer Customer { get; set; } - public Language CustomerLanguage { get; set; } - public int AffiliateId { get; set; } - public TaxDisplayType CustomerTaxDisplayType {get; set; } - public string CustomerCurrencyCode { get; set; } - public decimal CustomerCurrencyRate { get; set; } - - public Address BillingAddress { get; set; } - public Address ShippingAddress {get; set; } - public ShippingStatus ShippingStatus { get; set; } - public string ShippingMethodName { get; set; } - public string ShippingRateComputationMethodSystemName { get; set; } - public bool PickUpInStore { get; set; } - public Address PickupAddress { get; set; } - - public bool IsRecurringShoppingCart { get; set; } - //initial order (used with recurring payments) - public Order InitialOrder { get; set; } - - public string CheckoutAttributeDescription { get; set; } - public string CheckoutAttributesXml { get; set; } - - public IList Cart { get; set; } - public List AppliedDiscounts { get; set; } - public List AppliedGiftCards { get; set; } - - public decimal OrderSubTotalInclTax { get; set; } - public decimal OrderSubTotalExclTax { get; set; } - public decimal OrderSubTotalDiscountInclTax { get; set; } - public decimal OrderSubTotalDiscountExclTax { get; set; } - public decimal OrderShippingTotalInclTax { get; set; } - public decimal OrderShippingTotalExclTax { get; set; } - public decimal PaymentAdditionalFeeInclTax {get; set; } - public decimal PaymentAdditionalFeeExclTax { get; set; } - public decimal OrderTaxTotal {get; set; } - public string VatNumber {get; set; } - public string TaxRates {get; set; } - public decimal OrderDiscountAmount { get; set; } - public int RedeemedRewardPoints { get; set; } - public decimal RedeemedRewardPointsAmount { get; set; } - public decimal OrderTotal { get; set; } - public decimal OrderAmount { get; set; } //MF 09.12.16 - public decimal OrderAmountIncl { get; set; } //MF 09.12.16 - } - - #endregion - - #region Utilities - - /// - /// Prepare details to place an order. It also sets some properties to "processPaymentRequest" - /// - /// Process payment request - /// Details - protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRequest processPaymentRequest) - { - var details = new PlaceOrderContainter(); - - //customer - details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - if (details.Customer == null) - throw new ArgumentException("Customer is not set"); - - //affiliate - var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); - if (affiliate != null && affiliate.Active && !affiliate.Deleted) - details.AffiliateId = affiliate.Id; - - //check whether customer is guest - if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new NopException("Anonymous checkout is not allowed"); - - //customer currency - var currencyTmp = _currencyService.GetCurrencyById( - details.Customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); - var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); - details.CustomerCurrencyCode = customerCurrency.CurrencyCode; - details.CustomerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; - - //customer language - details.CustomerLanguage = _languageService.GetLanguageById( - details.Customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); - if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) - details.CustomerLanguage = _workContext.WorkingLanguage; - - //billing address - if (details.Customer.BillingAddress == null) - throw new NopException("Billing address is not provided"); - - if (!CommonHelper.IsValidEmail(details.Customer.BillingAddress.Email)) - throw new NopException("Email is not valid"); - - details.BillingAddress = (Address)details.Customer.BillingAddress.Clone(); - if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) - throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); - - //checkout attributes - details.CheckoutAttributesXml = details.Customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, processPaymentRequest.StoreId); - details.CheckoutAttributeDescription = _checkoutAttributeFormatter.FormatAttributes(details.CheckoutAttributesXml, details.Customer); - - //load shopping cart - details.Cart = details.Customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(processPaymentRequest.StoreId).ToList(); - - if (!details.Cart.Any()) - throw new NopException("Cart is empty"); - - //validate the entire shopping cart - var warnings = _shoppingCartService.GetShoppingCartWarnings(details.Cart, details.CheckoutAttributesXml, true); - if (warnings.Any()) - throw new NopException(warnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); - - //validate individual cart items - foreach (var sci in details.Cart) - { - var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(details.Customer, - sci.ShoppingCartType, sci.Product, processPaymentRequest.StoreId, sci.AttributesXml, - sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, false); - if (sciWarnings.Any()) - throw new NopException(sciWarnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); - } - - //min totals validation - if (!ValidateMinOrderSubtotalAmount(details.Cart)) - { - var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); - throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), - _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); - } - - if (!ValidateMinOrderTotalAmount(details.Cart)) - { - var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); - throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), - _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); - } - - //tax display type - if (_taxSettings.AllowCustomersToSelectTaxDisplayType) - details.CustomerTaxDisplayType = (TaxDisplayType)details.Customer.GetAttribute(SystemCustomerAttributeNames.TaxDisplayTypeId, processPaymentRequest.StoreId); - else - details.CustomerTaxDisplayType = _taxSettings.TaxDisplayType; - - - //order total (and applied discounts, gift cards, reward points) - List appliedGiftCards; - List orderAppliedDiscounts; - List subTotalAppliedDiscounts; - List shippingAppliedDiscounts; - decimal orderDiscountAmount; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - TaxSummary taxSummaryIncl; - TaxSummary taxSummaryExcl; - - //calculate two times to get correct incl./excl. tax - var orderTotalIncl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, - out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, - out taxSummaryIncl, true); - - var orderTotalExcl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, - out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, - out taxSummaryExcl, false); - - //sub total (incl tax) (excl tax) - details.OrderSubTotalInclTax = taxSummaryIncl.TotalSubTotalAmount; - details.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; - - details.OrderSubTotalExclTax = taxSummaryExcl.TotalSubTotalAmount; - details.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; - - - //discount history - foreach (var disc in subTotalAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //shipping info - if (details.Cart.RequiresShipping()) - { - var pickupPoint = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, processPaymentRequest.StoreId); - if (_shippingSettings.AllowPickUpInStore && pickupPoint != null) - { - var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); - var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); - - details.PickUpInStore = true; - details.PickupAddress = new Address - { - Address1 = pickupPoint.Address, - City = pickupPoint.City, - Country = country, - StateProvince = state, - ZipPostalCode = pickupPoint.ZipPostalCode, - CreatedOnUtc = DateTime.UtcNow, - }; - } - else - { - if (details.Customer.ShippingAddress == null) - throw new NopException("Shipping address is not provided"); - - if (!CommonHelper.IsValidEmail(details.Customer.ShippingAddress.Email)) - throw new NopException("Email is not valid"); - - //clone shipping address - details.ShippingAddress = (Address)details.Customer.ShippingAddress.Clone(); - if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) - throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); - } - - var shippingOption = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); - if (shippingOption != null) - { - details.ShippingMethodName = shippingOption.Name; - details.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; - } - - details.ShippingStatus = ShippingStatus.NotYetShipped; - } - else - details.ShippingStatus = ShippingStatus.ShippingNotRequired; - - //shipping total - details.OrderShippingTotalInclTax = taxSummaryIncl.TotalShippingAmount; - details.OrderShippingTotalExclTax = taxSummaryExcl.TotalShippingAmount; - - foreach(var disc in shippingAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //payment total - details.PaymentAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmount; - details.PaymentAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmount; - - //tax amount - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - details.OrderTaxTotal = includingTax? taxSummaryIncl.TotalAmountVAT : taxSummaryExcl.TotalAmountVAT; - - //VAT number - var customerVatStatus = (VatNumberStatus)details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) - details.VatNumber = details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - - //tax rates - //var taxrates = includingTax ? taxSummaryIncl.GenerateOldTaxrateDict() : taxSummaryExcl.GenerateOldTaxrateDict(); - var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; - details.TaxRates = taxrates.TaxRates.Aggregate(string.Empty, (current, next) => - string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, - next.Key.ToString(CultureInfo.InvariantCulture), - (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), - (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), - next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), - next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), - next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) - ) - ); - - //order total (and applied discounts, gift cards, reward points) - details.OrderDiscountAmount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; - details.RedeemedRewardPoints = redeemedRewardPoints; - details.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; - details.AppliedGiftCards = appliedGiftCards; - details.OrderTotal = includingTax ? orderTotalIncl ?? 0 : orderTotalExcl ?? 0; - - //discount history - foreach (var disc in orderAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - processPaymentRequest.OrderTotal = details.OrderTotal; - - //recurring or standard shopping cart? - details.IsRecurringShoppingCart = details.Cart.IsRecurring(); - if (details.IsRecurringShoppingCart) - { - int recurringCycleLength; - RecurringProductCyclePeriod recurringCyclePeriod; - int recurringTotalCycles; - var recurringCyclesError = details.Cart.GetRecurringCycleInfo(_localizationService, - out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); - if (!string.IsNullOrEmpty(recurringCyclesError)) - throw new NopException(recurringCyclesError); - - processPaymentRequest.RecurringCycleLength = recurringCycleLength; - processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; - processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; - } - - return details; - } - - /// - /// Prepare details to place order based on the recurring payment. - /// - /// Process payment request - /// Details - protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPaymentRequest processPaymentRequest) - { - var details = new PlaceOrderContainter(); - details.IsRecurringShoppingCart = true; - - //Load initial order - details.InitialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); - if (details.InitialOrder == null) - throw new ArgumentException("Initial order is not set for recurring payment"); - - processPaymentRequest.PaymentMethodSystemName = details.InitialOrder.PaymentMethodSystemName; - - //customer - details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - if (details.Customer == null) - throw new ArgumentException("Customer is not set"); - - //affiliate - var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); - if (affiliate != null && affiliate.Active && !affiliate.Deleted) - details.AffiliateId = affiliate.Id; - - //check whether customer is guest - if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new NopException("Anonymous checkout is not allowed"); - - //customer currency - details.CustomerCurrencyCode = details.InitialOrder.CustomerCurrencyCode; - details.CustomerCurrencyRate = details.InitialOrder.CurrencyRate; - - //customer language - details.CustomerLanguage = _languageService.GetLanguageById(details.InitialOrder.CustomerLanguageId); - if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) - details.CustomerLanguage = _workContext.WorkingLanguage; - - //billing address - if (details.InitialOrder.BillingAddress == null) - throw new NopException("Billing address is not available"); - - details.BillingAddress = (Address)details.InitialOrder.BillingAddress.Clone(); - if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) - throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); - - //checkout attributes - details.CheckoutAttributesXml = details.InitialOrder.CheckoutAttributesXml; - details.CheckoutAttributeDescription = details.InitialOrder.CheckoutAttributeDescription; - - //tax display type - details.CustomerTaxDisplayType = details.InitialOrder.CustomerTaxDisplayType; - - //sub total - details.OrderSubTotalInclTax = details.InitialOrder.OrderSubtotalInclTax; - details.OrderSubTotalExclTax = details.InitialOrder.OrderSubtotalExclTax; - - //shipping info - if (details.InitialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired) - { - details.PickUpInStore = details.InitialOrder.PickUpInStore; - if (!details.PickUpInStore) - { - if (details.InitialOrder.ShippingAddress == null) - throw new NopException("Shipping address is not available"); - - //clone shipping address - details.ShippingAddress = (Address)details.InitialOrder.ShippingAddress.Clone(); - if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) - throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); - } - else - if (details.InitialOrder.PickupAddress != null) - details.PickupAddress = (Address)details.InitialOrder.PickupAddress.Clone(); - details.ShippingMethodName = details.InitialOrder.ShippingMethod; - details.ShippingRateComputationMethodSystemName = details.InitialOrder.ShippingRateComputationMethodSystemName; - details.ShippingStatus = ShippingStatus.NotYetShipped; - } - else - details.ShippingStatus = ShippingStatus.ShippingNotRequired; - - //shipping total - details.OrderShippingTotalInclTax = details.InitialOrder.OrderShippingInclTax; - details.OrderShippingTotalExclTax = details.InitialOrder.OrderShippingExclTax; - - //payment total - details.PaymentAdditionalFeeInclTax = details.InitialOrder.PaymentMethodAdditionalFeeInclTax; - details.PaymentAdditionalFeeExclTax = details.InitialOrder.PaymentMethodAdditionalFeeExclTax; - - //tax total - details.OrderTaxTotal = details.InitialOrder.OrderTax; - - //VAT number - details.VatNumber = details.InitialOrder.VatNumber; - - //order total - details.OrderDiscountAmount = details.InitialOrder.OrderDiscount; - details.OrderTotal = details.InitialOrder.OrderTotal; - details.OrderAmount = details.InitialOrder.OrderAmount; - details.OrderAmountIncl = details.InitialOrder.OrderAmountIncl; - processPaymentRequest.OrderTotal = details.OrderTotal; - - return details; - } - - /// - /// Save order and add order notes - /// - /// Process payment request - /// Process payment result - /// Details - /// Order - protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, - ProcessPaymentResult processPaymentResult, PlaceOrderContainter details) - { - var order = new Order - { - StoreId = processPaymentRequest.StoreId, - OrderGuid = processPaymentRequest.OrderGuid, - CustomerId = details.Customer.Id, - CustomerLanguageId = details.CustomerLanguage.Id, - CustomerTaxDisplayType = details.CustomerTaxDisplayType, - CustomerIp = _webHelper.GetCurrentIpAddress(), - OrderSubtotalInclTax = details.OrderSubTotalInclTax, - OrderSubtotalExclTax = details.OrderSubTotalExclTax, - OrderSubTotalDiscountInclTax = details.OrderSubTotalDiscountInclTax, - OrderSubTotalDiscountExclTax = details.OrderSubTotalDiscountExclTax, - OrderShippingInclTax = details.OrderShippingTotalInclTax, - OrderShippingExclTax = details.OrderShippingTotalExclTax, - PaymentMethodAdditionalFeeInclTax = details.PaymentAdditionalFeeInclTax, - PaymentMethodAdditionalFeeExclTax = details.PaymentAdditionalFeeExclTax, - TaxRates = details.TaxRates, - OrderTax = details.OrderTaxTotal, - OrderTotal = details.OrderTotal, - OrderAmount = details.OrderAmount, - OrderAmountIncl = details.OrderAmountIncl, - RefundedAmount = decimal.Zero, - OrderDiscount = details.OrderDiscountAmount, - CheckoutAttributeDescription = details.CheckoutAttributeDescription, - CheckoutAttributesXml = details.CheckoutAttributesXml, - CustomerCurrencyCode = details.CustomerCurrencyCode, - CurrencyRate = details.CustomerCurrencyRate, - AffiliateId = details.AffiliateId, - OrderStatus = OrderStatus.Pending, - AllowStoringCreditCardNumber = processPaymentResult.AllowStoringCreditCardNumber, - CardType = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardType) : string.Empty, - CardName = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardName) : string.Empty, - CardNumber = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardNumber) : string.Empty, - MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(processPaymentRequest.CreditCardNumber)), - CardCvv2 = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardCvv2) : string.Empty, - CardExpirationMonth = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireMonth.ToString()) : string.Empty, - CardExpirationYear = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireYear.ToString()) : string.Empty, - PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName, - AuthorizationTransactionId = processPaymentResult.AuthorizationTransactionId, - AuthorizationTransactionCode = processPaymentResult.AuthorizationTransactionCode, - AuthorizationTransactionResult = processPaymentResult.AuthorizationTransactionResult, - CaptureTransactionId = processPaymentResult.CaptureTransactionId, - CaptureTransactionResult = processPaymentResult.CaptureTransactionResult, - SubscriptionTransactionId = processPaymentResult.SubscriptionTransactionId, - PaymentStatus = processPaymentResult.NewPaymentStatus, - PaidDateUtc = null, - BillingAddress = details.BillingAddress, - ShippingAddress = details.ShippingAddress, - ShippingStatus = details.ShippingStatus, - ShippingMethod = details.ShippingMethodName, - PickUpInStore = details.PickUpInStore, - PickupAddress = details.PickupAddress, - ShippingRateComputationMethodSystemName = details.ShippingRateComputationMethodSystemName, - CustomValuesXml = processPaymentRequest.SerializeCustomValues(), - VatNumber = details.VatNumber, - CreatedOnUtc = DateTime.UtcNow, - InvoiceId = null, - InvoiceDateUtc = null - }; - _orderService.InsertOrder(order); - - //reward points history - if (details.RedeemedRewardPointsAmount > decimal.Zero) - { - _rewardPointService.AddRewardPointsHistoryEntry(details.Customer, -details.RedeemedRewardPoints, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.Id), - order, details.RedeemedRewardPointsAmount); - _customerService.UpdateCustomer(details.Customer); - } - - return order; - } - - /// - /// Send "order placed" notifications and save order notes - /// - /// Order - protected virtual void SendNotificationsAndSaveNotes(Order order) - { - //notes, messages - if (_workContext.OriginalCustomerIfImpersonated != null) - //this order is placed by a store administrator impersonating a customer - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order placed by a store owner ('{0}'. ID = {1}) impersonating the customer.", - _workContext.OriginalCustomerIfImpersonated.Email, _workContext.OriginalCustomerIfImpersonated.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - else - order.OrderNotes.Add(new OrderNote - { - Note = "Order placed", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //send email notifications - var orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); - if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - var orderPlacedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderPlacedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? - "order.pdf" : null; - var orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService - .SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId, orderPlacedAttachmentFilePath, orderPlacedAttachmentFileName); - if (orderPlacedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - var vendors = GetVendorsInOrder(order); - foreach (var vendor in vendors) - { - var orderPlacedVendorNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); - if (orderPlacedVendorNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to vendor) has been queued. Queued email identifier: {0}.", orderPlacedVendorNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - } - /// - /// Award (earn) reward points (for placing a new order) - /// - /// Order - protected virtual void AwardRewardPoints(Order order) - { - var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); - int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); - if (points == 0) - return; - - //Ensure that reward points were not added (earned) before. We should not add reward points if they were already earned for this order - if (order.RewardPointsHistoryEntryId.HasValue) - return; - - //check whether delay is set - DateTime? activatingDate = null; - if (_rewardPointsSettings.ActivationDelay > 0) - { - var delayPeriod = (RewardPointsActivatingDelayPeriod)_rewardPointsSettings.ActivationDelayPeriodId; - var delayInHours = delayPeriod.ToHours(_rewardPointsSettings.ActivationDelay); - activatingDate = DateTime.UtcNow.AddHours(delayInHours); - } - - //add reward points - order.RewardPointsHistoryEntryId = _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.Id), activatingDate: activatingDate); - - _orderService.UpdateOrder(order); - } - - /// - /// Reduce (cancel) reward points (previously awarded for placing an order) - /// - /// Order - protected virtual void ReduceRewardPoints(Order order) - { - var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); - int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); - if (points == 0) - return; - - //ensure that reward points were already earned for this order before - if (!order.RewardPointsHistoryEntryId.HasValue) - return; - - //get appropriate history entry - var rewardPointsHistoryEntry = _rewardPointService.GetRewardPointsHistoryEntryById(order.RewardPointsHistoryEntryId.Value); - if (rewardPointsHistoryEntry != null && rewardPointsHistoryEntry.CreatedOnUtc > DateTime.UtcNow) - { - //just delete the upcoming entry (points were not granted yet) - _rewardPointService.DeleteRewardPointsHistoryEntry(rewardPointsHistoryEntry); - } - else - { - //or reduce reward points if the entry already exists - _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.Id)); - } - - _orderService.UpdateOrder(order); - } - - /// - /// Return back redeemded reward points to a customer (spent when placing an order) - /// - /// Order - protected virtual void ReturnBackRedeemedRewardPoints(Order order) - { - //were some points redeemed when placing an order? - if (order.RedeemedRewardPointsEntry == null) - return; - - //return back - _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -order.RedeemedRewardPointsEntry.Points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.ReturnedForOrder"), order.Id)); - _orderService.UpdateOrder(order); - } - - - /// - /// Set IsActivated value for purchase gift cards for particular order - /// - /// Order - /// A value indicating whether to activate gift cards; true - activate, false - deactivate - protected virtual void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) - { - var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, - isGiftCardActivated: !activate); - foreach (var gc in giftCards) - { - if (activate) - { - //activate - bool isRecipientNotified = gc.IsRecipientNotified; - if (gc.GiftCardType == GiftCardType.Virtual) - { - //send email for virtual gift card - if (!String.IsNullOrEmpty(gc.RecipientEmail) && - !String.IsNullOrEmpty(gc.SenderEmail)) - { - var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); - if (customerLang == null) - customerLang = _languageService.GetAllLanguages().FirstOrDefault(); - if (customerLang == null) - throw new Exception("No languages could be loaded"); - int queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); - if (queuedEmailId > 0) - isRecipientNotified = true; - } - } - gc.IsGiftCardActivated = true; - gc.IsRecipientNotified = isRecipientNotified; - _giftCardService.UpdateGiftCard(gc); - } - else - { - //deactivate - gc.IsGiftCardActivated = false; - _giftCardService.UpdateGiftCard(gc); - } - } - } - - /// - /// Sets an order status - /// - /// Order - /// New order status - /// True to notify customer - protected virtual void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) - { - if (order == null) - throw new ArgumentNullException("order"); - - OrderStatus prevOrderStatus = order.OrderStatus; - if (prevOrderStatus == os) - return; - - //set and save new order status - order.OrderStatusId = (int)os; - _orderService.UpdateOrder(order); - - //order notes, notifications - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order status has been changed to {0}", os.ToString()), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - - if (prevOrderStatus != OrderStatus.Complete && - os == OrderStatus.Complete - && notifyCustomer) - { - //notification - var orderCompletedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderCompletedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? - "order.pdf" : null; - int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService - .SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId, orderCompletedAttachmentFilePath, - orderCompletedAttachmentFileName); - if (orderCompletedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order completed\" email (to customer) has been queued. Queued email identifier: {0}.", orderCompletedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - if (prevOrderStatus != OrderStatus.Cancelled && - os == OrderStatus.Cancelled - && notifyCustomer) - { - //notification - int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); - if (orderCancelledCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order cancelled\" email (to customer) has been queued. Queued email identifier: {0}.", orderCancelledCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //reward points - if (order.OrderStatus == OrderStatus.Complete) - { - AwardRewardPoints(order); - } - if (order.OrderStatus == OrderStatus.Cancelled) - { - ReduceRewardPoints(order); - } - - //gift cards activation - if (_orderSettings.ActivateGiftCardsAfterCompletingOrder && order.OrderStatus == OrderStatus.Complete) - { - SetActivatedValueForPurchasedGiftCards(order, true); - } - - //gift cards deactivation - if (_orderSettings.DeactivateGiftCardsAfterCancellingOrder && order.OrderStatus == OrderStatus.Cancelled) - { - SetActivatedValueForPurchasedGiftCards(order, false); - } - } - - /// - /// Process order paid status - /// - /// Order - protected virtual void ProcessOrderPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //raise event - _eventPublisher.Publish(new OrderPaidEvent(order)); - - //order paid email notification - if (order.OrderTotal != decimal.Zero) - { - //we should not send it for free ($0 total) orders? - //remove this "if" statement if you want to send it in this case - - var orderPaidAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderPaidAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? - "order.pdf" : null; - _workflowMessageService.SendOrderPaidCustomerNotification(order, order.CustomerLanguageId, - orderPaidAttachmentFilePath, orderPaidAttachmentFileName); - - _workflowMessageService.SendOrderPaidStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); - var vendors = GetVendorsInOrder(order); - foreach (var vendor in vendors) - { - _workflowMessageService.SendOrderPaidVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); - } - //TODO add "order paid email sent" order note - } - - //customer roles with "purchased with product" specified - ProcessCustomerRolesWithPurchasedProductSpecified(order, true); - } - - /// - /// Process customer roles with "Purchased with Product" property configured - /// - /// Order - /// A value indicating whether to add configured customer role; true - add, false - remove - protected virtual void ProcessCustomerRolesWithPurchasedProductSpecified(Order order, bool add) - { - if (order == null) - throw new ArgumentNullException("order"); - - //purchased product identifiers - var purchasedProductIds = new List(); - foreach (var orderItem in order.OrderItems) - { - //standard items - purchasedProductIds.Add(orderItem.ProductId); - - //bundled (associated) products - var attributeValues = _productAttributeParser.ParseProductAttributeValues(orderItem.AttributesXml); - foreach (var attributeValue in attributeValues) - { - if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) - { - purchasedProductIds.Add(attributeValue.AssociatedProductId); - } - } - } - - //list of customer roles - var customerRoles = _customerService - .GetAllCustomerRoles(true) - .Where(cr => purchasedProductIds.Contains(cr.PurchasedWithProductId)) - .ToList(); - - if (customerRoles.Any()) - { - var customer = order.Customer; - foreach (var customerRole in customerRoles) - { - if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) - { - //not in the list yet - if (add) - { - //add - customer.CustomerRoles.Add(customerRole); - } - } - else - { - //already in the list - if (!add) - { - //remove - customer.CustomerRoles.Remove(customerRole); - } - } - } - _customerService.UpdateCustomer(customer); - } - } - - /// - /// Get a list of vendors in order (order items) - /// - /// Order - /// Vendors - protected virtual IList GetVendorsInOrder(Order order) - { - var vendors = new List(); - foreach (var orderItem in order.OrderItems) - { - var vendorId = orderItem.Product.VendorId; - //find existing - var vendor = vendors.FirstOrDefault(v => v.Id == vendorId); - if (vendor == null) - { - //not found. load by Id - vendor = _vendorService.GetVendorById(vendorId); - if (vendor != null && !vendor.Deleted && vendor.Active) - { - vendors.Add(vendor); - } - } - } - - return vendors; - } - - #endregion - - #region Methods - - /// - /// Checks order status - /// - /// Order - /// Validated order - public virtual void CheckOrderStatus(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) - { - //ensure that paid date is set - order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - } - - //set invoice id - if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) - { - order.InvoiceDateUtc = DateTime.UtcNow; - order.InvoiceId = GetInvoiceId(); - _orderService.UpdateOrder(order); - } - - if (order.OrderStatus == OrderStatus.Pending) - { - if (order.PaymentStatus == PaymentStatus.Authorized || - order.PaymentStatus == PaymentStatus.Paid) - { - SetOrderStatus(order, OrderStatus.Processing, false); - } - } - - if (order.OrderStatus == OrderStatus.Pending) - { - if (order.ShippingStatus == ShippingStatus.PartiallyShipped || - order.ShippingStatus == ShippingStatus.Shipped || - order.ShippingStatus == ShippingStatus.Delivered) - { - SetOrderStatus(order, OrderStatus.Processing, false); - } - } - - //is order complete? - if (order.OrderStatus != OrderStatus.Cancelled && - order.OrderStatus != OrderStatus.Complete) - { - if (order.PaymentStatus == PaymentStatus.Paid) - { - var completed = false; - if (order.ShippingStatus == ShippingStatus.ShippingNotRequired) - { - //shipping is not required - completed = true; - } - else - { - //shipping is required - if (_orderSettings.CompleteOrderWhenDelivered) - { - completed = order.ShippingStatus == ShippingStatus.Delivered; - } - else - { - completed = order.ShippingStatus == ShippingStatus.Shipped || - order.ShippingStatus == ShippingStatus.Delivered; - } - } - - if (completed) - { - SetOrderStatus(order, OrderStatus.Complete, true); - } - } - } - } - - /// - /// Places an order - /// - /// Process payment request - /// Place order result - public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest) - { - if (processPaymentRequest == null) - throw new ArgumentNullException("processPaymentRequest"); - - var result = new PlaceOrderResult(); - try - { - if (processPaymentRequest.OrderGuid == Guid.Empty) - processPaymentRequest.OrderGuid = Guid.NewGuid(); - - //prepare order details - var details = PreparePlaceOrderDetails(processPaymentRequest); - - #region Payment workflow - - - //process payment - ProcessPaymentResult processPaymentResult = null; - //skip payment workflow if order total equals zero - var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; - if (!skipPaymentWorkflow) - { - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - - //ensure that payment method is active - if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new NopException("Payment method is not active"); - - if (details.IsRecurringShoppingCart) - { - //recurring cart - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - throw new NopException("Recurring payments are not supported by selected payment method"); - case RecurringPaymentType.Manual: - case RecurringPaymentType.Automatic: - processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); - break; - default: - throw new NopException("Not supported recurring payment type"); - } - } - else - //standard cart - processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); - } - else - //payment is not required - processPaymentResult = new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; - - if (processPaymentResult == null) - throw new NopException("processPaymentResult is not available"); - - #endregion - - if (processPaymentResult.Success) - { - #region Save order details - - var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); - result.PlacedOrder = order; - - //move shopping cart items to order items - foreach (var sc in details.Cart) - { - //prices - decimal taxRate; - List scDiscounts; - decimal discountAmount; - int? maximumDiscountQty; - var scUnitPrice = _priceCalculationService.GetUnitPrice(sc); - var scSubTotal = _priceCalculationService.GetSubTotal(sc, true, out discountAmount, out scDiscounts, out maximumDiscountQty); - var scUnitPriceInclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, true, details.Customer, out taxRate); - var scUnitPriceExclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, false, details.Customer, out taxRate); - var scSubTotalInclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, true, details.Customer, out taxRate); - var scSubTotalExclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, false, details.Customer, out taxRate); - var discountAmountInclTax = _taxService.GetProductPrice(sc.Product, discountAmount, true, details.Customer, out taxRate); - var discountAmountExclTax = _taxService.GetProductPrice(sc.Product, discountAmount, false, details.Customer, out taxRate); - foreach (var disc in scDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //attributes - var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer); - - var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); - - //save order item - var orderItem = new OrderItem - { - OrderItemGuid = Guid.NewGuid(), - Order = order, - ProductId = sc.ProductId, - UnitPriceInclTax = scUnitPriceInclTax, - UnitPriceExclTax = scUnitPriceExclTax, - PriceInclTax = scSubTotalInclTax, - PriceExclTax = scSubTotalExclTax, - OriginalProductCost = _priceCalculationService.GetProductCost(sc.Product, sc.AttributesXml), - AttributeDescription = attributeDescription, - AttributesXml = sc.AttributesXml, - Quantity = sc.Quantity, - DiscountAmountInclTax = discountAmountInclTax, - DiscountAmountExclTax = discountAmountExclTax, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = itemWeight, - RentalStartDateUtc = sc.RentalStartDateUtc, - RentalEndDateUtc = sc.RentalEndDateUtc, - VatRate = taxRate //MF 25.11.16 - }; - order.OrderItems.Add(orderItem); - _orderService.UpdateOrder(order); - - //gift cards - if (sc.Product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - _productAttributeParser.GetGiftCardAttribute(sc.AttributesXml, out giftCardRecipientName, - out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - for (var i = 0; i < sc.Quantity; i++) - { - _giftCardService.InsertGiftCard(new GiftCard - { - GiftCardType = sc.Product.GiftCardType, - PurchasedWithOrderItem = orderItem, - Amount = sc.Product.OverriddenGiftCardAmount.HasValue ? sc.Product.OverriddenGiftCardAmount.Value : scUnitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = giftCardRecipientName, - RecipientEmail = giftCardRecipientEmail, - SenderName = giftCardSenderName, - SenderEmail = giftCardSenderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //inventory - _productService.AdjustInventory(sc.Product, -sc.Quantity, sc.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); - } - - //clear shopping cart - details.Cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci, false)); - - //discount usage history - foreach (var discount in details.AppliedDiscounts) - { - var d = _discountService.GetDiscountById(discount.Id); - if (d != null) - { - _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory - { - Discount = d, - Order = order, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //gift card usage history - if (details.AppliedGiftCards != null) - foreach (var agc in details.AppliedGiftCards) - { - agc.GiftCard.GiftCardUsageHistory.Add(new GiftCardUsageHistory - { - GiftCard = agc.GiftCard, - UsedWithOrder = order, - UsedValue = agc.AmountCanBeUsed, - CreatedOnUtc = DateTime.UtcNow - }); - _giftCardService.UpdateGiftCard(agc.GiftCard); - } - - //recurring orders - if (details.IsRecurringShoppingCart) - { - //create recurring payment (the first payment) - var rp = new RecurringPayment - { - CycleLength = processPaymentRequest.RecurringCycleLength, - CyclePeriod = processPaymentRequest.RecurringCyclePeriod, - TotalCycles = processPaymentRequest.RecurringTotalCycles, - StartDateUtc = DateTime.UtcNow, - IsActive = true, - CreatedOnUtc = DateTime.UtcNow, - InitialOrder = order, - }; - _orderService.InsertRecurringPayment(rp); - - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - //not supported - break; - case RecurringPaymentType.Manual: - rp.RecurringPaymentHistory.Add(new RecurringPaymentHistory - { - RecurringPayment = rp, - CreatedOnUtc = DateTime.UtcNow, - OrderId = order.Id, - }); - _orderService.UpdateRecurringPayment(rp); - break; - case RecurringPaymentType.Automatic: - //will be created later (process is automated) - break; - default: - break; - } - } - - #endregion - - //notifications - SendNotificationsAndSaveNotes(order); - - //reset checkout data - _customerService.ResetCheckoutData(details.Customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); - _customerActivityService.InsertActivity("PublicStore.PlaceOrder", _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), order.Id); - - //check order status - CheckOrderStatus(order); - - //raise event - _eventPublisher.Publish(new OrderPlacedEvent(order)); - - if (order.PaymentStatus == PaymentStatus.Paid) - ProcessOrderPaid(order); - } - else - foreach (var paymentError in processPaymentResult.Errors) - result.AddError(string.Format(_localizationService.GetResource("Checkout.PaymentError"), paymentError)); - } - catch (Exception exc) - { - _logger.Error(exc.Message, exc); - result.AddError(exc.Message); - } - - #region Process errors - - if (!result.Success) - { - //log errors - var logError = result.Errors.Aggregate("Error while placing order. ", - (current, next) => string.Format("{0}Error {1}: {2}. ", current, result.Errors.IndexOf(next) + 1, next)); - var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - _logger.Error(logError, customer: customer); - } - - #endregion - - 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, - VatRate = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.VatRate : orderItem.VatRate, - SubTotalInclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalInclTax : orderItem.PriceInclTax, - SubTotalExclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalExclTax : orderItem.PriceExclTax - }).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.Any(history => history.DiscountId == discount.Id)) - { - var d = _discountService.GetDiscountById(discount.Id); - if (d != null) - { - _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory - { - Discount = d, - Order = updatedOrder, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - } - - CheckOrderStatus(updatedOrder); - } - - /// - /// Deletes an order - /// - /// The order - public virtual void DeleteOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //check whether the order wasn't cancelled before - //if it already was cancelled, then there's no need to make the following adjustments - //(such as reward points, inventory, recurring payments) - //they already was done when cancelling the order - if (order.OrderStatus != OrderStatus.Cancelled) - { - //return (add) back redeemded reward points - ReturnBackRedeemedRewardPoints(order); - //reduce (cancel) back reward points (previously awarded for this order) - ReduceRewardPoints(order); - - //cancel recurring payments - var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); - foreach (var rp in recurringPayments) - { - var errors = CancelRecurringPayment(rp); - //use "errors" variable? - } - - //Adjust inventory for already shipped shipments - //only products with "use multiple warehouses" - foreach (var shipment in order.Shipments) - { - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); - } - } - - //Adjust inventory - foreach (var orderItem in order.OrderItems) - { - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); - } - - } - - //deactivate gift cards - if (_orderSettings.DeactivateGiftCardsAfterDeletingOrder) - SetActivatedValueForPurchasedGiftCards(order, false); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been deleted", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //now delete an order - _orderService.DeleteOrder(order); - } - - /// - /// Process next recurring payment - /// - /// Recurring payment - /// Process payment result (info about last payment for automatic recurring payments) - /// Collection of errors - public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment recurringPayment, ProcessPaymentResult paymentResult = null) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - try - { - if (!recurringPayment.IsActive) - throw new NopException("Recurring payment is not active"); - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - throw new NopException("Initial order could not be loaded"); - - var customer = initialOrder.Customer; - if (customer == null) - throw new NopException("Customer could not be loaded"); - - var nextPaymentDate = recurringPayment.NextPaymentDate; - if (!nextPaymentDate.HasValue) - throw new NopException("Next payment date could not be calculated"); - - //payment info - var processPaymentRequest = new ProcessPaymentRequest - { - StoreId = initialOrder.StoreId, - CustomerId = customer.Id, - OrderGuid = Guid.NewGuid(), - InitialOrderId = initialOrder.Id, - RecurringCycleLength = recurringPayment.CycleLength, - RecurringCyclePeriod = recurringPayment.CyclePeriod, - RecurringTotalCycles = recurringPayment.TotalCycles, - CustomValues = initialOrder.DeserializeCustomValues() - }; - - //prepare order details - var details = PrepareRecurringOrderDetails(processPaymentRequest); - - ProcessPaymentResult processPaymentResult; - //skip payment workflow if order total equals zero - var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; - if (!skipPaymentWorkflow) - { - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - - if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new NopException("Payment method is not active"); - - //Old credit card info - if (details.InitialOrder.AllowStoringCreditCardNumber) - { - processPaymentRequest.CreditCardType = _encryptionService.DecryptText(details.InitialOrder.CardType); - processPaymentRequest.CreditCardName = _encryptionService.DecryptText(details.InitialOrder.CardName); - processPaymentRequest.CreditCardNumber = _encryptionService.DecryptText(details.InitialOrder.CardNumber); - processPaymentRequest.CreditCardCvv2 = _encryptionService.DecryptText(details.InitialOrder.CardCvv2); - try - { - processPaymentRequest.CreditCardExpireMonth = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationMonth)); - processPaymentRequest.CreditCardExpireYear = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationYear)); - } - catch { } - } - - //payment type - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - throw new NopException("Recurring payments are not supported by selected payment method"); - case RecurringPaymentType.Manual: - processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); - break; - case RecurringPaymentType.Automatic: - //payment is processed on payment gateway site, info about last transaction in paymentResult parameter - processPaymentResult = paymentResult ?? new ProcessPaymentResult(); - break; - default: - throw new NopException("Not supported recurring payment type"); - } - } - else - processPaymentResult = paymentResult ?? new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; - - if (processPaymentResult == null) - throw new NopException("processPaymentResult is not available"); - - if (processPaymentResult.Success) - { - //save order details - var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); - - foreach (var orderItem in details.InitialOrder.OrderItems) - { - //save item - var newOrderItem = new OrderItem - { - OrderItemGuid = Guid.NewGuid(), - Order = order, - ProductId = orderItem.ProductId, - UnitPriceInclTax = orderItem.UnitPriceInclTax, - UnitPriceExclTax = orderItem.UnitPriceExclTax, - PriceInclTax = orderItem.PriceInclTax, - PriceExclTax = orderItem.PriceExclTax, - OriginalProductCost = orderItem.OriginalProductCost, - AttributeDescription = orderItem.AttributeDescription, - AttributesXml = orderItem.AttributesXml, - Quantity = orderItem.Quantity, - DiscountAmountInclTax = orderItem.DiscountAmountInclTax, - DiscountAmountExclTax = orderItem.DiscountAmountExclTax, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = orderItem.ItemWeight, - RentalStartDateUtc = orderItem.RentalStartDateUtc, - RentalEndDateUtc = orderItem.RentalEndDateUtc, - VatRate = orderItem.VatRate - }; - order.OrderItems.Add(newOrderItem); - _orderService.UpdateOrder(order); - - //gift cards - if (orderItem.Product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - - _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, out giftCardRecipientName, - out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - for (var i = 0; i < orderItem.Quantity; i++) - { - _giftCardService.InsertGiftCard(new GiftCard - { - GiftCardType = orderItem.Product.GiftCardType, - PurchasedWithOrderItem = newOrderItem, - Amount = orderItem.UnitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = giftCardRecipientName, - RecipientEmail = giftCardRecipientEmail, - SenderName = giftCardSenderName, - SenderEmail = giftCardSenderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //inventory - _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); - } - - //notifications - SendNotificationsAndSaveNotes(order); - - //check order status - CheckOrderStatus(order); - - //raise event - _eventPublisher.Publish(new OrderPlacedEvent(order)); - - if (order.PaymentStatus == PaymentStatus.Paid) - ProcessOrderPaid(order); - - //last payment succeeded - recurringPayment.LastPaymentFailed = false; - - //next recurring payment - recurringPayment.RecurringPaymentHistory.Add(new RecurringPaymentHistory - { - RecurringPayment = recurringPayment, - CreatedOnUtc = DateTime.UtcNow, - OrderId = order.Id, - }); - _orderService.UpdateRecurringPayment(recurringPayment); - - return new List(); - } - else - { - //log errors - var logError = processPaymentResult.Errors.Aggregate("Error while processing recurring order. ", - (current, next) => string.Format("{0}Error {1}: {2}. ", current, processPaymentResult.Errors.IndexOf(next) + 1, next)); - _logger.Error(logError, customer: customer); - - if (processPaymentResult.RecurringPaymentFailed) - { - //set flag that last payment failed - recurringPayment.LastPaymentFailed = true; - _orderService.UpdateRecurringPayment(recurringPayment); - - if (_paymentSettings.CancelRecurringPaymentsAfterFailedPayment) - { - //cancel recurring payment - CancelRecurringPayment(recurringPayment).ToList().ForEach(error => _logger.Error(error)); - - //notify a customer about cancelled payment - _workflowMessageService.SendRecurringPaymentCancelledCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); - } - else - //notify a customer about failed payment - _workflowMessageService.SendRecurringPaymentFailedCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); - } - - return processPaymentResult.Errors; - } - } - catch (Exception exc) - { - _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); - throw; - } - } - - /// - /// Cancels a recurring payment - /// - /// Recurring payment - public virtual IList CancelRecurringPayment(RecurringPayment recurringPayment) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - return new List { "Initial order could not be loaded" }; - - - var request = new CancelRecurringPaymentRequest(); - CancelRecurringPaymentResult result = null; - try - { - request.Order = initialOrder; - result = _paymentService.CancelRecurringPayment(request); - if (result.Success) - { - //update recurring payment - recurringPayment.IsActive = false; - _orderService.UpdateRecurringPayment(recurringPayment); - - - //add a note - initialOrder.OrderNotes.Add(new OrderNote - { - Note = "Recurring payment has been cancelled", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); - - //notify a store owner - _workflowMessageService - .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, - _localizationSettings.DefaultAdminLanguageId); - } - } - catch (Exception exc) - { - if (result == null) - result = new CancelRecurringPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - initialOrder.OrderNotes.Add(new OrderNote - { - Note = string.Format("Unable to cancel recurring payment. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); - - //log it - string logError = string.Format("Error cancelling recurring payment. Order #{0}. Error: {1}", initialOrder.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether a customer can cancel recurring payment - /// - /// Customer - /// Recurring Payment - /// value indicating whether a customer can cancel recurring payment - public virtual bool CanCancelRecurringPayment(Customer customerToValidate, RecurringPayment recurringPayment) - { - if (recurringPayment == null) - return false; - - if (customerToValidate == null) - return false; - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - return false; - - var customer = recurringPayment.InitialOrder.Customer; - if (customer == null) - return false; - - if (initialOrder.OrderStatus == OrderStatus.Cancelled) - return false; - - if (!customerToValidate.IsAdmin()) - { - if (customer.Id != customerToValidate.Id) - return false; - } - - if (!recurringPayment.NextPaymentDate.HasValue) - return false; - - return true; - } - - - /// - /// Gets a value indicating whether a customer can retry last failed recurring payment - /// - /// Customer - /// Recurring Payment - /// True if a customer can retry payment; otherwise false - public virtual bool CanRetryLastRecurringPayment(Customer customer, RecurringPayment recurringPayment) - { - if (recurringPayment == null || customer == null) - return false; - - if (recurringPayment.InitialOrder == null || recurringPayment.InitialOrder.OrderStatus == OrderStatus.Cancelled) - return false; - - if (!recurringPayment.LastPaymentFailed || _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName) != RecurringPaymentType.Manual) - return false; - - if (recurringPayment.InitialOrder.Customer == null || (!customer.IsAdmin() && recurringPayment.InitialOrder.Customer.Id != customer.Id)) - return false; - - return true; - } - - - /// - /// Send a shipment - /// - /// Shipment - /// True to notify customer - public virtual void Ship(Shipment shipment, bool notifyCustomer) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = _orderService.GetOrderById(shipment.OrderId); - if (order == null) - throw new Exception("Order cannot be loaded"); - - if (shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is already shipped"); - - shipment.ShippedDateUtc = DateTime.UtcNow; - _shipmentService.UpdateShipment(shipment); - - //process products with "Multiple warehouse" support enabled - foreach (var item in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(item.OrderItemId); - _productService.BookReservedInventory(orderItem.Product, item.WarehouseId, -item.Quantity, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.Ship"), shipment.OrderId)); - } - - //check whether we have more items to ship - if (order.HasItemsToAddToShipment() || order.HasItemsToShip()) - order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped; - else - order.ShippingStatusId = (int)ShippingStatus.Shipped; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Shipment# {0} has been sent", shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - if (notifyCustomer) - { - //notify customer - int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Shipped\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //event - _eventPublisher.PublishShipmentSent(shipment); - - //check order status - CheckOrderStatus(order); - } - - /// - /// Marks a shipment as delivered - /// - /// Shipment - /// True to notify customer - public virtual void Deliver(Shipment shipment, bool notifyCustomer) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = shipment.Order; - if (order == null) - throw new Exception("Order cannot be loaded"); - - if (!shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is not shipped yet"); - - if (shipment.DeliveryDateUtc.HasValue) - throw new Exception("This shipment is already delivered"); - - shipment.DeliveryDateUtc = DateTime.UtcNow; - _shipmentService.UpdateShipment(shipment); - - if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) - order.ShippingStatusId = (int)ShippingStatus.Delivered; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Shipment# {0} has been delivered", shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - if (notifyCustomer) - { - //send email notification - int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Delivered\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //event - _eventPublisher.PublishShipmentDelivered(shipment); - - //check order status - CheckOrderStatus(order); - } - - - - /// - /// Gets a value indicating whether cancel is allowed - /// - /// Order - /// A value indicating whether cancel is allowed - public virtual bool CanCancelOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - return true; - } - - /// - /// Cancels order - /// - /// Order - /// True to notify customer - public virtual void CancelOrder(Order order, bool notifyCustomer) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanCancelOrder(order)) - throw new NopException("Cannot do cancel for order."); - - //Cancel order - SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been cancelled", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //return (add) back redeemded reward points - ReturnBackRedeemedRewardPoints(order); - - //cancel recurring payments - var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); - foreach (var rp in recurringPayments) - { - var errors = CancelRecurringPayment(rp); - //use "errors" variable? - } - - //Adjust inventory for already shipped shipments - //only products with "use multiple warehouses" - foreach (var shipment in order.Shipments) - { - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); - } - } - //Adjust inventory - foreach (var orderItem in order.OrderItems) - { - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); - } - - _eventPublisher.Publish(new OrderCancelledEvent(order)); - - } - - /// - /// Gets a value indicating whether order can be marked as authorized - /// - /// Order - /// A value indicating whether order can be marked as authorized - public virtual bool CanMarkOrderAsAuthorized(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - if (order.PaymentStatus == PaymentStatus.Pending) - return true; - - return false; - } - - /// - /// Marks order as authorized - /// - /// Order - public virtual void MarkAsAuthorized(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - order.PaymentStatusId = (int)PaymentStatus.Authorized; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as authorized", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - } - - - - /// - /// Gets a value indicating whether capture from admin panel is allowed - /// - /// Order - /// A value indicating whether capture from admin panel is allowed - public virtual bool CanCapture(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled || - order.OrderStatus == OrderStatus.Pending) - return false; - - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportCapture(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Capture an order (from admin panel) - /// - /// Order - /// A list of errors; empty list if no errors - public virtual IList Capture(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanCapture(order)) - throw new NopException("Cannot do capture for order."); - - var request = new CapturePaymentRequest(); - CapturePaymentResult result = null; - try - { - //old info from placing order - request.Order = order; - result = _paymentService.Capture(request); - - if (result.Success) - { - var paidDate = order.PaidDateUtc; - if (result.NewPaymentStatus == PaymentStatus.Paid) - paidDate = DateTime.UtcNow; - - order.CaptureTransactionId = result.CaptureTransactionId; - order.CaptureTransactionResult = result.CaptureTransactionResult; - order.PaymentStatus = result.NewPaymentStatus; - order.PaidDateUtc = paidDate; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been captured", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - CheckOrderStatus(order); - - if (order.PaymentStatus == PaymentStatus.Paid) - { - ProcessOrderPaid(order); - } - } - } - catch (Exception exc) - { - if (result == null) - result = new CapturePaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Unable to capture order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error capturing order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as paid - /// - /// Order - /// A value indicating whether order can be marked as paid - public virtual bool CanMarkOrderAsPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - if (order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.Refunded || - order.PaymentStatus == PaymentStatus.Voided) - return false; - - return true; - } - - /// - /// Marks order as paid - /// - /// Order - public virtual void MarkOrderAsPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanMarkOrderAsPaid(order)) - throw new NopException("You can't mark this order as paid"); - - order.PaymentStatusId = (int)PaymentStatus.Paid; - order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as paid", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - CheckOrderStatus(order); - - if (order.PaymentStatus == PaymentStatus.Paid) - { - ProcessOrderPaid(order); - } - } - - - - /// - /// Gets a value indicating whether refund from admin panel is allowed - /// - /// Order - /// A value indicating whether refund from admin panel is allowed - public virtual bool CanRefund(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case - if (order.RefundedAmount > decimal.Zero) - return false; - - //uncomment the lines below in order to disallow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Paid && - _paymentService.SupportRefund(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Refunds an order (from admin panel) - /// - /// Order - /// A list of errors; empty list if no errors - public virtual IList Refund(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanRefund(order)) - throw new NopException("Cannot do refund for order."); - - var request = new RefundPaymentRequest(); - RefundPaymentResult result = null; - try - { - request.Order = order; - request.AmountToRefund = order.OrderTotal; - request.IsPartialRefund = false; - result = _paymentService.Refund(request); - if (result.Success) - { - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + request.AmountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been refunded. Amount = {0}", request.AmountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, request.AmountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, request.AmountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, request.AmountToRefund)); - } - - } - catch (Exception exc) - { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Unable to refund order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as refunded - /// - /// Order - /// A value indicating whether order can be marked as refunded - public virtual bool CanRefundOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case - if (order.RefundedAmount > decimal.Zero) - return false; - - //uncomment the lines below in order to disallow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Paid) - return true; - - return false; - } - - /// - /// Refunds an order (offline) - /// - /// Order - public virtual void RefundOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanRefundOffline(order)) - throw new NopException("You can't refund this order"); - - //amout to refund - decimal amountToRefund = order.OrderTotal; - - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - order.PaymentStatus = PaymentStatus.Refunded; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been marked as refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - - /// - /// Gets a value indicating whether partial refund from admin panel is allowed - /// - /// Order - /// Amount to refund - /// A value indicating whether refund from admin panel is allowed - public virtual bool CanPartiallyRefund(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; - if (canBeRefunded <= decimal.Zero) - return false; - - if (amountToRefund > canBeRefunded) - return false; - - if ((order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.PartiallyRefunded) && - _paymentService.SupportPartiallyRefund(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Partially refunds an order (from admin panel) - /// - /// Order - /// Amount to refund - /// A list of errors; empty list if no errors - public virtual IList PartiallyRefund(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanPartiallyRefund(order, amountToRefund)) - throw new NopException("Cannot do partial refund for order."); - - var request = new RefundPaymentRequest(); - RefundPaymentResult result = null; - try - { - request.Order = order; - request.AmountToRefund = amountToRefund; - request.IsPartialRefund = true; - - result = _paymentService.Refund(request); - - if (result.Success) - { - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - //mark payment status as 'Refunded' if the order total amount is fully refunded - order.PaymentStatus = order.OrderTotal == totalAmountRefunded && result.NewPaymentStatus == PaymentStatus.PartiallyRefunded ? PaymentStatus.Refunded : result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been partially refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - } - catch (Exception exc) - { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Unable to partially refund order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as partially refunded - /// - /// Order - /// Amount to refund - /// A value indicating whether order can be marked as partially refunded - public virtual bool CanPartiallyRefundOffline(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; - if (canBeRefunded <= decimal.Zero) - return false; - - if (amountToRefund > canBeRefunded) - return false; - - if (order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.PartiallyRefunded) - return true; - - return false; - } - - /// - /// Partially refunds an order (offline) - /// - /// Order - /// Amount to refund - public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanPartiallyRefundOffline(order, amountToRefund)) - throw new NopException("You can't partially refund (offline) this order"); - - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - //mark payment status as 'Refunded' if the order total amount is fully refunded - order.PaymentStatus = order.OrderTotal == totalAmountRefunded ? PaymentStatus.Refunded : PaymentStatus.PartiallyRefunded; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been marked as partially refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - - - - /// - /// Gets a value indicating whether void from admin panel is allowed - /// - /// Order - /// A value indicating whether void from admin panel is allowed - public virtual bool CanVoid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportVoid(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Voids order (from admin panel) - /// - /// Order - /// Voided order - public virtual IList Void(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanVoid(order)) - throw new NopException("Cannot do void for order."); - - var request = new VoidPaymentRequest(); - VoidPaymentResult result = null; - try - { - request.Order = order; - result = _paymentService.Void(request); - - if (result.Success) - { - //update order info - order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been voided", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - } - } - catch (Exception exc) - { - if (result == null) - result = new VoidPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Unable to voiding order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error voiding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as voided - /// - /// Order - /// A value indicating whether order can be marked as voided - public virtual bool CanVoidOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Authorized) - return true; - - return false; - } - - /// - /// Voids order (offline) - /// - /// Order - public virtual void VoidOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanVoidOffline(order)) - throw new NopException("You can't void this order"); - - order.PaymentStatusId = (int)PaymentStatus.Voided; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as voided", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check orer status - CheckOrderStatus(order); - } - - - - /// - /// Place order items in current user shopping cart. - /// - /// The order - public virtual void ReOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //move shopping cart items (if possible) - foreach (var orderItem in order.OrderItems) - { - _shoppingCartService.AddToCart(order.Customer, orderItem.Product, - ShoppingCartType.ShoppingCart, order.StoreId, - orderItem.AttributesXml, orderItem.UnitPriceExclTax, - orderItem.RentalStartDateUtc, orderItem.RentalEndDateUtc, - orderItem.Quantity, false); - } - - //set checkout attributes - //comment the code below if you want to disable this functionality - _genericAttributeService.SaveAttribute(order.Customer, SystemCustomerAttributeNames.CheckoutAttributes, order.CheckoutAttributesXml, order.StoreId); - } - - /// - /// Check whether return request is allowed - /// - /// Order - /// Result - public virtual bool IsReturnRequestAllowed(Order order) - { - if (!_orderSettings.ReturnRequestsEnabled) - return false; - - if (order == null || order.Deleted) - return false; - - //status should be compelte - if (order.OrderStatus != OrderStatus.Complete) - return false; - - //validate allowed number of days - if (_orderSettings.NumberOfDaysReturnRequestAvailable > 0) - { - var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; - if (daysPassed >= _orderSettings.NumberOfDaysReturnRequestAvailable) - return false; - } - - //ensure that we have at least one returnable product - return order.OrderItems.Any(oi => !oi.Product.NotReturnable); - } - - - - /// - /// Valdiate minimum order sub-total amount - /// - /// Shopping cart - /// true - OK; false - minimum order sub-total amount is not reached - public virtual bool ValidateMinOrderSubtotalAmount(IList cart) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - //min order amount sub-total validation - if (cart.Any() && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) - { - //subtotal - decimal orderSubTotalDiscountAmountBase; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - _orderTotalCalculationService.GetShoppingCartSubTotal(cart, _orderSettings.MinOrderSubtotalAmountIncludingTax, - out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscounts, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - - if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) - return false; - } - - return true; - } - - /// - /// Valdiate minimum order total amount - /// - /// Shopping cart - /// true - OK; false - minimum order total amount is not reached - public virtual bool ValidateMinOrderTotalAmount(IList cart) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - if (cart.Any() && _orderSettings.MinOrderTotalAmount > decimal.Zero) - { - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); - if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) - return false; - } - - return true; - } - - /// - /// Get invoice ID - /// - /// - public virtual string GetInvoiceId() - { - var actYear = DateTime.UtcNow.Year; - int ident = _orderSettings.InvoiceIdent; - if (_orderSettings.InvoiceYear < actYear) - { - // Reset counter if a new year - ident = 1; - _orderSettings.InvoiceYear = actYear; - _settingService.SetSetting("ordersettings.invoiceyear", _orderSettings.InvoiceYear); - } - else - { - ident += 1; - } - _orderSettings.InvoiceIdent = ident; - // Update settings - _settingService.SetSetting("ordersettings.invoiceident", _orderSettings.InvoiceIdent); - - return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); - } - /// - /// Gets a value indicating whether payment workflow is required - /// - /// Shopping cart - /// A value indicating reward points should be used; null to detect current choice of the customer - /// true - OK; false - minimum order total amount is not reached - public virtual bool IsPaymentWorkflowRequired(IList cart, bool? useRewardPoints = null) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - bool result = true; - - //check whether order total equals zero - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, useRewardPoints: useRewardPoints); - if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value == decimal.Zero) - result = false; - return result; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Localization; +using Nop.Core.Domain.Logging; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Domain.Vendors; +using Nop.Services.Affiliates; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.Events; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Messages; +using Nop.Services.Payments; +using Nop.Services.Security; +using Nop.Services.Shipping; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using Nop.Services.Configuration; + +namespace Nop.Services.Orders +{ + /// + /// Order processing service + /// + public partial class OrderProcessingService : IOrderProcessingService + { + #region Fields + + private readonly IOrderService _orderService; + private readonly IWebHelper _webHelper; + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IProductService _productService; + private readonly IPaymentService _paymentService; + private readonly ILogger _logger; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly IPriceFormatter _priceFormatter; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IGiftCardService _giftCardService; + private readonly IShoppingCartService _shoppingCartService; + private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; + private readonly IShippingService _shippingService; + private readonly IShipmentService _shipmentService; + private readonly ITaxService _taxService; + private readonly ICustomerService _customerService; + private readonly IDiscountService _discountService; + private readonly IEncryptionService _encryptionService; + private readonly IWorkContext _workContext; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly IVendorService _vendorService; + private readonly ICustomerActivityService _customerActivityService; + private readonly ICurrencyService _currencyService; + private readonly IAffiliateService _affiliateService; + private readonly IEventPublisher _eventPublisher; + private readonly IPdfService _pdfService; + private readonly IRewardPointService _rewardPointService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + + private readonly ShippingSettings _shippingSettings; + private readonly PaymentSettings _paymentSettings; + private readonly RewardPointsSettings _rewardPointsSettings; + private readonly OrderSettings _orderSettings; + private readonly TaxSettings _taxSettings; + private readonly LocalizationSettings _localizationSettings; + private readonly CurrencySettings _currencySettings; + + private readonly ISettingService _settingService; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Order service + /// Web helper + /// Localization service + /// Language service + /// Product service + /// Payment service + /// Logger + /// Order total calculationservice + /// Price calculation service + /// Price formatter + /// Product attribute parser + /// Product attribute formatter + /// Gift card service + /// Shopping cart service + /// Checkout attribute service + /// Shipping service + /// Shipment service + /// Tax service + /// Customer service + /// Discount service + /// Encryption service + /// Work context + /// Workflow message service + /// Vendor service + /// Customer activity service + /// Currency service + /// Affiliate service + /// Event published + /// PDF service + /// Reward point service + /// Generic attribute service + /// Country service + /// Payment settings + /// Shipping settings + /// Reward points settings + /// Order settings + /// Tax settings + /// Localization settings + /// Currency settings + /// Setting service + public OrderProcessingService(IOrderService orderService, + IWebHelper webHelper, + ILocalizationService localizationService, + ILanguageService languageService, + IProductService productService, + IPaymentService paymentService, + ILogger logger, + IOrderTotalCalculationService orderTotalCalculationService, + IPriceCalculationService priceCalculationService, + IPriceFormatter priceFormatter, + IProductAttributeParser productAttributeParser, + IProductAttributeFormatter productAttributeFormatter, + IGiftCardService giftCardService, + IShoppingCartService shoppingCartService, + ICheckoutAttributeFormatter checkoutAttributeFormatter, + IShippingService shippingService, + IShipmentService shipmentService, + ITaxService taxService, + ICustomerService customerService, + IDiscountService discountService, + IEncryptionService encryptionService, + IWorkContext workContext, + IWorkflowMessageService workflowMessageService, + IVendorService vendorService, + ICustomerActivityService customerActivityService, + ICurrencyService currencyService, + IAffiliateService affiliateService, + IEventPublisher eventPublisher, + IPdfService pdfService, + IRewardPointService rewardPointService, + IGenericAttributeService genericAttributeService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + ShippingSettings shippingSettings, + PaymentSettings paymentSettings, + RewardPointsSettings rewardPointsSettings, + OrderSettings orderSettings, + TaxSettings taxSettings, + LocalizationSettings localizationSettings, + CurrencySettings currencySettings, + ISettingService settingService) + { + this._orderService = orderService; + this._webHelper = webHelper; + this._localizationService = localizationService; + this._languageService = languageService; + this._productService = productService; + this._paymentService = paymentService; + this._logger = logger; + this._orderTotalCalculationService = orderTotalCalculationService; + this._priceCalculationService = priceCalculationService; + this._priceFormatter = priceFormatter; + this._productAttributeParser = productAttributeParser; + this._productAttributeFormatter = productAttributeFormatter; + this._giftCardService = giftCardService; + this._shoppingCartService = shoppingCartService; + this._checkoutAttributeFormatter = checkoutAttributeFormatter; + this._workContext = workContext; + this._workflowMessageService = workflowMessageService; + this._vendorService = vendorService; + this._shippingService = shippingService; + this._shipmentService = shipmentService; + this._taxService = taxService; + this._customerService = customerService; + this._discountService = discountService; + this._encryptionService = encryptionService; + this._customerActivityService = customerActivityService; + this._currencyService = currencyService; + this._affiliateService = affiliateService; + this._eventPublisher = eventPublisher; + this._pdfService = pdfService; + this._rewardPointService = rewardPointService; + this._genericAttributeService = genericAttributeService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + + this._paymentSettings = paymentSettings; + this._shippingSettings = shippingSettings; + this._rewardPointsSettings = rewardPointsSettings; + this._orderSettings = orderSettings; + this._taxSettings = taxSettings; + this._localizationSettings = localizationSettings; + this._currencySettings = currencySettings; + this._settingService = settingService; + } + + #endregion + + #region Nested classes + + protected class PlaceOrderContainter + { + public PlaceOrderContainter() + { + this.Cart = new List(); + this.AppliedDiscounts = new List(); + this.AppliedGiftCards = new List(); + } + + public Customer Customer { get; set; } + public Language CustomerLanguage { get; set; } + public int AffiliateId { get; set; } + public TaxDisplayType CustomerTaxDisplayType {get; set; } + public string CustomerCurrencyCode { get; set; } + public decimal CustomerCurrencyRate { get; set; } + + public Address BillingAddress { get; set; } + public Address ShippingAddress {get; set; } + public ShippingStatus ShippingStatus { get; set; } + public string ShippingMethodName { get; set; } + public string ShippingRateComputationMethodSystemName { get; set; } + public bool PickUpInStore { get; set; } + public Address PickupAddress { get; set; } + + public bool IsRecurringShoppingCart { get; set; } + //initial order (used with recurring payments) + public Order InitialOrder { get; set; } + + public string CheckoutAttributeDescription { get; set; } + public string CheckoutAttributesXml { get; set; } + + public IList Cart { get; set; } + public List AppliedDiscounts { get; set; } + public List AppliedGiftCards { get; set; } + + public decimal OrderSubTotalInclTax { get; set; } + public decimal OrderSubTotalExclTax { get; set; } + public decimal OrderSubTotalDiscountInclTax { get; set; } + public decimal OrderSubTotalDiscountExclTax { get; set; } + public decimal OrderShippingTotalInclTax { get; set; } + public decimal OrderShippingTotalExclTax { get; set; } + public decimal PaymentAdditionalFeeInclTax {get; set; } + public decimal PaymentAdditionalFeeExclTax { get; set; } + public decimal OrderTaxTotal {get; set; } + public string VatNumber {get; set; } + public string TaxRates {get; set; } + public decimal OrderDiscountAmount { get; set; } + public int RedeemedRewardPoints { get; set; } + public decimal RedeemedRewardPointsAmount { get; set; } + public decimal OrderTotal { get; set; } + public decimal OrderAmount { get; set; } //MF 09.12.16 + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 + } + + #endregion + + #region Utilities + + /// + /// Prepare details to place an order. It also sets some properties to "processPaymentRequest" + /// + /// Process payment request + /// Details + protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRequest processPaymentRequest) + { + var details = new PlaceOrderContainter(); + + //customer + details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + if (details.Customer == null) + throw new ArgumentException("Customer is not set"); + + //affiliate + var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); + if (affiliate != null && affiliate.Active && !affiliate.Deleted) + details.AffiliateId = affiliate.Id; + + //check whether customer is guest + if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + throw new NopException("Anonymous checkout is not allowed"); + + //customer currency + var currencyTmp = _currencyService.GetCurrencyById( + details.Customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); + var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; + var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + details.CustomerCurrencyCode = customerCurrency.CurrencyCode; + details.CustomerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; + + //customer language + details.CustomerLanguage = _languageService.GetLanguageById( + details.Customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); + if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) + details.CustomerLanguage = _workContext.WorkingLanguage; + + //billing address + if (details.Customer.BillingAddress == null) + throw new NopException("Billing address is not provided"); + + if (!CommonHelper.IsValidEmail(details.Customer.BillingAddress.Email)) + throw new NopException("Email is not valid"); + + details.BillingAddress = (Address)details.Customer.BillingAddress.Clone(); + if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) + throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); + + //checkout attributes + details.CheckoutAttributesXml = details.Customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, processPaymentRequest.StoreId); + details.CheckoutAttributeDescription = _checkoutAttributeFormatter.FormatAttributes(details.CheckoutAttributesXml, details.Customer); + + //load shopping cart + details.Cart = details.Customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(processPaymentRequest.StoreId).ToList(); + + if (!details.Cart.Any()) + throw new NopException("Cart is empty"); + + //validate the entire shopping cart + var warnings = _shoppingCartService.GetShoppingCartWarnings(details.Cart, details.CheckoutAttributesXml, true); + if (warnings.Any()) + throw new NopException(warnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); + + //validate individual cart items + foreach (var sci in details.Cart) + { + var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(details.Customer, + sci.ShoppingCartType, sci.Product, processPaymentRequest.StoreId, sci.AttributesXml, + sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, false); + if (sciWarnings.Any()) + throw new NopException(sciWarnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); + } + + //min totals validation + if (!ValidateMinOrderSubtotalAmount(details.Cart)) + { + var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); + throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), + _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); + } + + if (!ValidateMinOrderTotalAmount(details.Cart)) + { + var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); + throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), + _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); + } + + //tax display type + if (_taxSettings.AllowCustomersToSelectTaxDisplayType) + details.CustomerTaxDisplayType = (TaxDisplayType)details.Customer.GetAttribute(SystemCustomerAttributeNames.TaxDisplayTypeId, processPaymentRequest.StoreId); + else + details.CustomerTaxDisplayType = _taxSettings.TaxDisplayType; + + + //order total (and applied discounts, gift cards, reward points) + List appliedGiftCards; + List orderAppliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + decimal orderDiscountAmount; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + TaxSummary taxSummaryIncl; + TaxSummary taxSummaryExcl; + + //calculate two times to get correct incl./excl. tax + var orderTotalIncl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, + out taxSummaryIncl, true); + + var orderTotalExcl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, + out taxSummaryExcl, false); + + //sub total (incl tax) (excl tax) + details.OrderSubTotalInclTax = taxSummaryIncl.TotalSubTotalAmount; + details.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; + + details.OrderSubTotalExclTax = taxSummaryExcl.TotalSubTotalAmount; + details.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; + + + //discount history + foreach (var disc in subTotalAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //shipping info + if (details.Cart.RequiresShipping()) + { + var pickupPoint = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, processPaymentRequest.StoreId); + if (_shippingSettings.AllowPickUpInStore && pickupPoint != null) + { + var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); + var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); + + details.PickUpInStore = true; + details.PickupAddress = new Address + { + Address1 = pickupPoint.Address, + City = pickupPoint.City, + Country = country, + StateProvince = state, + ZipPostalCode = pickupPoint.ZipPostalCode, + CreatedOnUtc = DateTime.UtcNow, + }; + } + else + { + if (details.Customer.ShippingAddress == null) + throw new NopException("Shipping address is not provided"); + + if (!CommonHelper.IsValidEmail(details.Customer.ShippingAddress.Email)) + throw new NopException("Email is not valid"); + + //clone shipping address + details.ShippingAddress = (Address)details.Customer.ShippingAddress.Clone(); + if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) + throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); + } + + var shippingOption = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); + if (shippingOption != null) + { + details.ShippingMethodName = shippingOption.Name; + details.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; + } + + details.ShippingStatus = ShippingStatus.NotYetShipped; + } + else + details.ShippingStatus = ShippingStatus.ShippingNotRequired; + + //shipping total + details.OrderShippingTotalInclTax = taxSummaryIncl.TotalShippingAmount; + details.OrderShippingTotalExclTax = taxSummaryExcl.TotalShippingAmount; + + foreach(var disc in shippingAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //payment total + details.PaymentAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmount; + details.PaymentAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmount; + + //tax amount + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + details.OrderTaxTotal = includingTax? taxSummaryIncl.TotalAmountVAT : taxSummaryExcl.TotalAmountVAT; + + //VAT number + var customerVatStatus = (VatNumberStatus)details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) + details.VatNumber = details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + + //tax rates + //var taxrates = includingTax ? taxSummaryIncl.GenerateOldTaxrateDict() : taxSummaryExcl.GenerateOldTaxrateDict(); + var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; + details.TaxRates = taxrates.GenerateTaxRateString(); + + //order total (and applied discounts, gift cards, reward points) + details.OrderDiscountAmount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; + details.RedeemedRewardPoints = redeemedRewardPoints; + details.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; + details.AppliedGiftCards = appliedGiftCards; + details.OrderTotal = includingTax ? orderTotalIncl ?? 0 : orderTotalExcl ?? 0; + details.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; + details.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; + + //discount history + foreach (var disc in orderAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + processPaymentRequest.OrderTotal = details.OrderTotal; + + //recurring or standard shopping cart? + details.IsRecurringShoppingCart = details.Cart.IsRecurring(); + if (details.IsRecurringShoppingCart) + { + int recurringCycleLength; + RecurringProductCyclePeriod recurringCyclePeriod; + int recurringTotalCycles; + var recurringCyclesError = details.Cart.GetRecurringCycleInfo(_localizationService, + out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); + if (!string.IsNullOrEmpty(recurringCyclesError)) + throw new NopException(recurringCyclesError); + + processPaymentRequest.RecurringCycleLength = recurringCycleLength; + processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; + processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; + } + + return details; + } + + /// + /// Prepare details to place order based on the recurring payment. + /// + /// Process payment request + /// Details + protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPaymentRequest processPaymentRequest) + { + var details = new PlaceOrderContainter(); + details.IsRecurringShoppingCart = true; + + //Load initial order + details.InitialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); + if (details.InitialOrder == null) + throw new ArgumentException("Initial order is not set for recurring payment"); + + processPaymentRequest.PaymentMethodSystemName = details.InitialOrder.PaymentMethodSystemName; + + //customer + details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + if (details.Customer == null) + throw new ArgumentException("Customer is not set"); + + //affiliate + var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); + if (affiliate != null && affiliate.Active && !affiliate.Deleted) + details.AffiliateId = affiliate.Id; + + //check whether customer is guest + if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + throw new NopException("Anonymous checkout is not allowed"); + + //customer currency + details.CustomerCurrencyCode = details.InitialOrder.CustomerCurrencyCode; + details.CustomerCurrencyRate = details.InitialOrder.CurrencyRate; + + //customer language + details.CustomerLanguage = _languageService.GetLanguageById(details.InitialOrder.CustomerLanguageId); + if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) + details.CustomerLanguage = _workContext.WorkingLanguage; + + //billing address + if (details.InitialOrder.BillingAddress == null) + throw new NopException("Billing address is not available"); + + details.BillingAddress = (Address)details.InitialOrder.BillingAddress.Clone(); + if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) + throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); + + //checkout attributes + details.CheckoutAttributesXml = details.InitialOrder.CheckoutAttributesXml; + details.CheckoutAttributeDescription = details.InitialOrder.CheckoutAttributeDescription; + + //tax display type + details.CustomerTaxDisplayType = details.InitialOrder.CustomerTaxDisplayType; + + //sub total + details.OrderSubTotalInclTax = details.InitialOrder.OrderSubtotalInclTax; + details.OrderSubTotalExclTax = details.InitialOrder.OrderSubtotalExclTax; + + //shipping info + if (details.InitialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + details.PickUpInStore = details.InitialOrder.PickUpInStore; + if (!details.PickUpInStore) + { + if (details.InitialOrder.ShippingAddress == null) + throw new NopException("Shipping address is not available"); + + //clone shipping address + details.ShippingAddress = (Address)details.InitialOrder.ShippingAddress.Clone(); + if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) + throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); + } + else + if (details.InitialOrder.PickupAddress != null) + details.PickupAddress = (Address)details.InitialOrder.PickupAddress.Clone(); + details.ShippingMethodName = details.InitialOrder.ShippingMethod; + details.ShippingRateComputationMethodSystemName = details.InitialOrder.ShippingRateComputationMethodSystemName; + details.ShippingStatus = ShippingStatus.NotYetShipped; + } + else + details.ShippingStatus = ShippingStatus.ShippingNotRequired; + + //shipping total + details.OrderShippingTotalInclTax = details.InitialOrder.OrderShippingInclTax; + details.OrderShippingTotalExclTax = details.InitialOrder.OrderShippingExclTax; + + //payment total + details.PaymentAdditionalFeeInclTax = details.InitialOrder.PaymentMethodAdditionalFeeInclTax; + details.PaymentAdditionalFeeExclTax = details.InitialOrder.PaymentMethodAdditionalFeeExclTax; + + //tax total + details.OrderTaxTotal = details.InitialOrder.OrderTax; + + //VAT number + details.VatNumber = details.InitialOrder.VatNumber; + + //order total + details.OrderDiscountAmount = details.InitialOrder.OrderDiscount; + details.OrderTotal = details.InitialOrder.OrderTotal; + details.OrderAmount = details.InitialOrder.OrderAmount; + details.OrderAmountIncl = details.InitialOrder.OrderAmountIncl; + processPaymentRequest.OrderTotal = details.OrderTotal; + + return details; + } + + /// + /// Save order and add order notes + /// + /// Process payment request + /// Process payment result + /// Details + /// Order + protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, + ProcessPaymentResult processPaymentResult, PlaceOrderContainter details) + { + var order = new Order + { + StoreId = processPaymentRequest.StoreId, + OrderGuid = processPaymentRequest.OrderGuid, + CustomerId = details.Customer.Id, + CustomerLanguageId = details.CustomerLanguage.Id, + CustomerTaxDisplayType = details.CustomerTaxDisplayType, + CustomerIp = _webHelper.GetCurrentIpAddress(), + OrderSubtotalInclTax = details.OrderSubTotalInclTax, + OrderSubtotalExclTax = details.OrderSubTotalExclTax, + OrderSubTotalDiscountInclTax = details.OrderSubTotalDiscountInclTax, + OrderSubTotalDiscountExclTax = details.OrderSubTotalDiscountExclTax, + OrderShippingInclTax = details.OrderShippingTotalInclTax, + OrderShippingExclTax = details.OrderShippingTotalExclTax, + PaymentMethodAdditionalFeeInclTax = details.PaymentAdditionalFeeInclTax, + PaymentMethodAdditionalFeeExclTax = details.PaymentAdditionalFeeExclTax, + TaxRates = details.TaxRates, + OrderTax = details.OrderTaxTotal, + OrderTotal = details.OrderTotal, + OrderAmount = details.OrderAmount, + OrderAmountIncl = details.OrderAmountIncl, + RefundedAmount = decimal.Zero, + OrderDiscount = details.OrderDiscountAmount, + CheckoutAttributeDescription = details.CheckoutAttributeDescription, + CheckoutAttributesXml = details.CheckoutAttributesXml, + CustomerCurrencyCode = details.CustomerCurrencyCode, + CurrencyRate = details.CustomerCurrencyRate, + AffiliateId = details.AffiliateId, + OrderStatus = OrderStatus.Pending, + AllowStoringCreditCardNumber = processPaymentResult.AllowStoringCreditCardNumber, + CardType = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardType) : string.Empty, + CardName = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardName) : string.Empty, + CardNumber = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardNumber) : string.Empty, + MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(processPaymentRequest.CreditCardNumber)), + CardCvv2 = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardCvv2) : string.Empty, + CardExpirationMonth = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireMonth.ToString()) : string.Empty, + CardExpirationYear = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireYear.ToString()) : string.Empty, + PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName, + AuthorizationTransactionId = processPaymentResult.AuthorizationTransactionId, + AuthorizationTransactionCode = processPaymentResult.AuthorizationTransactionCode, + AuthorizationTransactionResult = processPaymentResult.AuthorizationTransactionResult, + CaptureTransactionId = processPaymentResult.CaptureTransactionId, + CaptureTransactionResult = processPaymentResult.CaptureTransactionResult, + SubscriptionTransactionId = processPaymentResult.SubscriptionTransactionId, + PaymentStatus = processPaymentResult.NewPaymentStatus, + PaidDateUtc = null, + BillingAddress = details.BillingAddress, + ShippingAddress = details.ShippingAddress, + ShippingStatus = details.ShippingStatus, + ShippingMethod = details.ShippingMethodName, + PickUpInStore = details.PickUpInStore, + PickupAddress = details.PickupAddress, + ShippingRateComputationMethodSystemName = details.ShippingRateComputationMethodSystemName, + CustomValuesXml = processPaymentRequest.SerializeCustomValues(), + VatNumber = details.VatNumber, + CreatedOnUtc = DateTime.UtcNow, + InvoiceId = null, + InvoiceDateUtc = null + }; + _orderService.InsertOrder(order); + + //reward points history + if (details.RedeemedRewardPointsAmount > decimal.Zero) + { + _rewardPointService.AddRewardPointsHistoryEntry(details.Customer, -details.RedeemedRewardPoints, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.Id), + order, details.RedeemedRewardPointsAmount); + _customerService.UpdateCustomer(details.Customer); + } + + return order; + } + + /// + /// Send "order placed" notifications and save order notes + /// + /// Order + protected virtual void SendNotificationsAndSaveNotes(Order order) + { + //notes, messages + if (_workContext.OriginalCustomerIfImpersonated != null) + //this order is placed by a store administrator impersonating a customer + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order placed by a store owner ('{0}'. ID = {1}) impersonating the customer.", + _workContext.OriginalCustomerIfImpersonated.Email, _workContext.OriginalCustomerIfImpersonated.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + else + order.OrderNotes.Add(new OrderNote + { + Note = "Order placed", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //send email notifications + var orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); + if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + var orderPlacedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderPlacedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? + "order.pdf" : null; + var orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService + .SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId, orderPlacedAttachmentFilePath, orderPlacedAttachmentFileName); + if (orderPlacedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + var vendors = GetVendorsInOrder(order); + foreach (var vendor in vendors) + { + var orderPlacedVendorNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); + if (orderPlacedVendorNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to vendor) has been queued. Queued email identifier: {0}.", orderPlacedVendorNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + } + /// + /// Award (earn) reward points (for placing a new order) + /// + /// Order + protected virtual void AwardRewardPoints(Order order) + { + var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); + int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); + if (points == 0) + return; + + //Ensure that reward points were not added (earned) before. We should not add reward points if they were already earned for this order + if (order.RewardPointsHistoryEntryId.HasValue) + return; + + //check whether delay is set + DateTime? activatingDate = null; + if (_rewardPointsSettings.ActivationDelay > 0) + { + var delayPeriod = (RewardPointsActivatingDelayPeriod)_rewardPointsSettings.ActivationDelayPeriodId; + var delayInHours = delayPeriod.ToHours(_rewardPointsSettings.ActivationDelay); + activatingDate = DateTime.UtcNow.AddHours(delayInHours); + } + + //add reward points + order.RewardPointsHistoryEntryId = _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, points, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.Id), activatingDate: activatingDate); + + _orderService.UpdateOrder(order); + } + + /// + /// Reduce (cancel) reward points (previously awarded for placing an order) + /// + /// Order + protected virtual void ReduceRewardPoints(Order order) + { + var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); + int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); + if (points == 0) + return; + + //ensure that reward points were already earned for this order before + if (!order.RewardPointsHistoryEntryId.HasValue) + return; + + //get appropriate history entry + var rewardPointsHistoryEntry = _rewardPointService.GetRewardPointsHistoryEntryById(order.RewardPointsHistoryEntryId.Value); + if (rewardPointsHistoryEntry != null && rewardPointsHistoryEntry.CreatedOnUtc > DateTime.UtcNow) + { + //just delete the upcoming entry (points were not granted yet) + _rewardPointService.DeleteRewardPointsHistoryEntry(rewardPointsHistoryEntry); + } + else + { + //or reduce reward points if the entry already exists + _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -points, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.Id)); + } + + _orderService.UpdateOrder(order); + } + + /// + /// Return back redeemded reward points to a customer (spent when placing an order) + /// + /// Order + protected virtual void ReturnBackRedeemedRewardPoints(Order order) + { + //were some points redeemed when placing an order? + if (order.RedeemedRewardPointsEntry == null) + return; + + //return back + _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -order.RedeemedRewardPointsEntry.Points, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.ReturnedForOrder"), order.Id)); + _orderService.UpdateOrder(order); + } + + + /// + /// Set IsActivated value for purchase gift cards for particular order + /// + /// Order + /// A value indicating whether to activate gift cards; true - activate, false - deactivate + protected virtual void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) + { + var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, + isGiftCardActivated: !activate); + foreach (var gc in giftCards) + { + if (activate) + { + //activate + bool isRecipientNotified = gc.IsRecipientNotified; + if (gc.GiftCardType == GiftCardType.Virtual) + { + //send email for virtual gift card + if (!String.IsNullOrEmpty(gc.RecipientEmail) && + !String.IsNullOrEmpty(gc.SenderEmail)) + { + var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); + if (customerLang == null) + customerLang = _languageService.GetAllLanguages().FirstOrDefault(); + if (customerLang == null) + throw new Exception("No languages could be loaded"); + int queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); + if (queuedEmailId > 0) + isRecipientNotified = true; + } + } + gc.IsGiftCardActivated = true; + gc.IsRecipientNotified = isRecipientNotified; + _giftCardService.UpdateGiftCard(gc); + } + else + { + //deactivate + gc.IsGiftCardActivated = false; + _giftCardService.UpdateGiftCard(gc); + } + } + } + + /// + /// Sets an order status + /// + /// Order + /// New order status + /// True to notify customer + protected virtual void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) + { + if (order == null) + throw new ArgumentNullException("order"); + + OrderStatus prevOrderStatus = order.OrderStatus; + if (prevOrderStatus == os) + return; + + //set and save new order status + order.OrderStatusId = (int)os; + _orderService.UpdateOrder(order); + + //order notes, notifications + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order status has been changed to {0}", os.ToString()), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + + if (prevOrderStatus != OrderStatus.Complete && + os == OrderStatus.Complete + && notifyCustomer) + { + //notification + var orderCompletedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderCompletedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? + "order.pdf" : null; + int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService + .SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId, orderCompletedAttachmentFilePath, + orderCompletedAttachmentFileName); + if (orderCompletedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order completed\" email (to customer) has been queued. Queued email identifier: {0}.", orderCompletedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + if (prevOrderStatus != OrderStatus.Cancelled && + os == OrderStatus.Cancelled + && notifyCustomer) + { + //notification + int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); + if (orderCancelledCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order cancelled\" email (to customer) has been queued. Queued email identifier: {0}.", orderCancelledCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //reward points + if (order.OrderStatus == OrderStatus.Complete) + { + AwardRewardPoints(order); + } + if (order.OrderStatus == OrderStatus.Cancelled) + { + ReduceRewardPoints(order); + } + + //gift cards activation + if (_orderSettings.ActivateGiftCardsAfterCompletingOrder && order.OrderStatus == OrderStatus.Complete) + { + SetActivatedValueForPurchasedGiftCards(order, true); + } + + //gift cards deactivation + if (_orderSettings.DeactivateGiftCardsAfterCancellingOrder && order.OrderStatus == OrderStatus.Cancelled) + { + SetActivatedValueForPurchasedGiftCards(order, false); + } + } + + /// + /// Process order paid status + /// + /// Order + protected virtual void ProcessOrderPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //raise event + _eventPublisher.Publish(new OrderPaidEvent(order)); + + //order paid email notification + if (order.OrderTotal != decimal.Zero) + { + //we should not send it for free ($0 total) orders? + //remove this "if" statement if you want to send it in this case + + var orderPaidAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderPaidAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? + "order.pdf" : null; + _workflowMessageService.SendOrderPaidCustomerNotification(order, order.CustomerLanguageId, + orderPaidAttachmentFilePath, orderPaidAttachmentFileName); + + _workflowMessageService.SendOrderPaidStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); + var vendors = GetVendorsInOrder(order); + foreach (var vendor in vendors) + { + _workflowMessageService.SendOrderPaidVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); + } + //TODO add "order paid email sent" order note + } + + //customer roles with "purchased with product" specified + ProcessCustomerRolesWithPurchasedProductSpecified(order, true); + } + + /// + /// Process customer roles with "Purchased with Product" property configured + /// + /// Order + /// A value indicating whether to add configured customer role; true - add, false - remove + protected virtual void ProcessCustomerRolesWithPurchasedProductSpecified(Order order, bool add) + { + if (order == null) + throw new ArgumentNullException("order"); + + //purchased product identifiers + var purchasedProductIds = new List(); + foreach (var orderItem in order.OrderItems) + { + //standard items + purchasedProductIds.Add(orderItem.ProductId); + + //bundled (associated) products + var attributeValues = _productAttributeParser.ParseProductAttributeValues(orderItem.AttributesXml); + foreach (var attributeValue in attributeValues) + { + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + purchasedProductIds.Add(attributeValue.AssociatedProductId); + } + } + } + + //list of customer roles + var customerRoles = _customerService + .GetAllCustomerRoles(true) + .Where(cr => purchasedProductIds.Contains(cr.PurchasedWithProductId)) + .ToList(); + + if (customerRoles.Any()) + { + var customer = order.Customer; + foreach (var customerRole in customerRoles) + { + if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) + { + //not in the list yet + if (add) + { + //add + customer.CustomerRoles.Add(customerRole); + } + } + else + { + //already in the list + if (!add) + { + //remove + customer.CustomerRoles.Remove(customerRole); + } + } + } + _customerService.UpdateCustomer(customer); + } + } + + /// + /// Get a list of vendors in order (order items) + /// + /// Order + /// Vendors + protected virtual IList GetVendorsInOrder(Order order) + { + var vendors = new List(); + foreach (var orderItem in order.OrderItems) + { + var vendorId = orderItem.Product.VendorId; + //find existing + var vendor = vendors.FirstOrDefault(v => v.Id == vendorId); + if (vendor == null) + { + //not found. load by Id + vendor = _vendorService.GetVendorById(vendorId); + if (vendor != null && !vendor.Deleted && vendor.Active) + { + vendors.Add(vendor); + } + } + } + + return vendors; + } + + #endregion + + #region Methods + + /// + /// Checks order status + /// + /// Order + /// Validated order + public virtual void CheckOrderStatus(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) + { + //ensure that paid date is set + order.PaidDateUtc = DateTime.UtcNow; + _orderService.UpdateOrder(order); + } + + //set invoice id + if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) + { + order.InvoiceDateUtc = DateTime.UtcNow; + order.InvoiceId = GetInvoiceId(); + _orderService.UpdateOrder(order); + } + + if (order.OrderStatus == OrderStatus.Pending) + { + if (order.PaymentStatus == PaymentStatus.Authorized || + order.PaymentStatus == PaymentStatus.Paid) + { + SetOrderStatus(order, OrderStatus.Processing, false); + } + } + + if (order.OrderStatus == OrderStatus.Pending) + { + if (order.ShippingStatus == ShippingStatus.PartiallyShipped || + order.ShippingStatus == ShippingStatus.Shipped || + order.ShippingStatus == ShippingStatus.Delivered) + { + SetOrderStatus(order, OrderStatus.Processing, false); + } + } + + //is order complete? + if (order.OrderStatus != OrderStatus.Cancelled && + order.OrderStatus != OrderStatus.Complete) + { + if (order.PaymentStatus == PaymentStatus.Paid) + { + var completed = false; + if (order.ShippingStatus == ShippingStatus.ShippingNotRequired) + { + //shipping is not required + completed = true; + } + else + { + //shipping is required + if (_orderSettings.CompleteOrderWhenDelivered) + { + completed = order.ShippingStatus == ShippingStatus.Delivered; + } + else + { + completed = order.ShippingStatus == ShippingStatus.Shipped || + order.ShippingStatus == ShippingStatus.Delivered; + } + } + + if (completed) + { + SetOrderStatus(order, OrderStatus.Complete, true); + } + } + } + } + + /// + /// Places an order + /// + /// Process payment request + /// Place order result + public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest) + { + if (processPaymentRequest == null) + throw new ArgumentNullException("processPaymentRequest"); + + var result = new PlaceOrderResult(); + try + { + if (processPaymentRequest.OrderGuid == Guid.Empty) + processPaymentRequest.OrderGuid = Guid.NewGuid(); + + //prepare order details + var details = PreparePlaceOrderDetails(processPaymentRequest); + + #region Payment workflow + + + //process payment + ProcessPaymentResult processPaymentResult = null; + //skip payment workflow if order total equals zero + var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; + if (!skipPaymentWorkflow) + { + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + + //ensure that payment method is active + if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) + throw new NopException("Payment method is not active"); + + if (details.IsRecurringShoppingCart) + { + //recurring cart + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + throw new NopException("Recurring payments are not supported by selected payment method"); + case RecurringPaymentType.Manual: + case RecurringPaymentType.Automatic: + processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); + break; + default: + throw new NopException("Not supported recurring payment type"); + } + } + else + //standard cart + processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); + } + else + //payment is not required + processPaymentResult = new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; + + if (processPaymentResult == null) + throw new NopException("processPaymentResult is not available"); + + #endregion + + if (processPaymentResult.Success) + { + #region Save order details + + var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); + result.PlacedOrder = order; + + //move shopping cart items to order items + foreach (var sc in details.Cart) + { + //prices + decimal taxRate; + List scDiscounts; + decimal discountAmount; + int? maximumDiscountQty; + var scUnitPrice = _priceCalculationService.GetUnitPrice(sc); + var scSubTotal = _priceCalculationService.GetSubTotal(sc, true, out discountAmount, out scDiscounts, out maximumDiscountQty); + var scUnitPriceInclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, true, details.Customer, out taxRate); + var scUnitPriceExclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, false, details.Customer, out taxRate); + var scSubTotalInclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, true, details.Customer, out taxRate); + var scSubTotalExclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, false, details.Customer, out taxRate); + var discountAmountInclTax = _taxService.GetProductPrice(sc.Product, discountAmount, true, details.Customer, out taxRate); + var discountAmountExclTax = _taxService.GetProductPrice(sc.Product, discountAmount, false, details.Customer, out taxRate); + foreach (var disc in scDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //attributes + var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer, subTotal: _taxSettings.PricesIncludeTax ? scSubTotalInclTax : scSubTotalExclTax); + + var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); + + //save order item + var orderItem = new OrderItem + { + OrderItemGuid = Guid.NewGuid(), + Order = order, + ProductId = sc.ProductId, + UnitPriceInclTax = scUnitPriceInclTax, + UnitPriceExclTax = scUnitPriceExclTax, + PriceInclTax = scSubTotalInclTax, + PriceExclTax = scSubTotalExclTax, + OriginalProductCost = _priceCalculationService.GetProductCost(sc.Product, sc.AttributesXml), + AttributeDescription = attributeDescription, + AttributesXml = sc.AttributesXml, + Quantity = sc.Quantity, + DiscountAmountInclTax = discountAmountInclTax, + DiscountAmountExclTax = discountAmountExclTax, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = itemWeight, + RentalStartDateUtc = sc.RentalStartDateUtc, + RentalEndDateUtc = sc.RentalEndDateUtc, + VatRate = taxRate //MF 25.11.16 + }; + order.OrderItems.Add(orderItem); + _orderService.UpdateOrder(order); + + //gift cards + if (sc.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(sc.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < sc.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = sc.Product.GiftCardType, + PurchasedWithOrderItem = orderItem, + Amount = sc.Product.OverriddenGiftCardAmount.HasValue ? sc.Product.OverriddenGiftCardAmount.Value : scUnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //inventory + _productService.AdjustInventory(sc.Product, -sc.Quantity, sc.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); + } + + //clear shopping cart + details.Cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci, false)); + + //discount usage history + foreach (var discount in details.AppliedDiscounts) + { + var d = _discountService.GetDiscountById(discount.Id); + if (d != null) + { + _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory + { + Discount = d, + Order = order, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //gift card usage history + if (details.AppliedGiftCards != null) + foreach (var agc in details.AppliedGiftCards) + { + agc.GiftCard.GiftCardUsageHistory.Add(new GiftCardUsageHistory + { + GiftCard = agc.GiftCard, + UsedWithOrder = order, + UsedValue = agc.AmountCanBeUsed, + CreatedOnUtc = DateTime.UtcNow + }); + _giftCardService.UpdateGiftCard(agc.GiftCard); + } + + //recurring orders + if (details.IsRecurringShoppingCart) + { + //create recurring payment (the first payment) + var rp = new RecurringPayment + { + CycleLength = processPaymentRequest.RecurringCycleLength, + CyclePeriod = processPaymentRequest.RecurringCyclePeriod, + TotalCycles = processPaymentRequest.RecurringTotalCycles, + StartDateUtc = DateTime.UtcNow, + IsActive = true, + CreatedOnUtc = DateTime.UtcNow, + InitialOrder = order, + }; + _orderService.InsertRecurringPayment(rp); + + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + //not supported + break; + case RecurringPaymentType.Manual: + rp.RecurringPaymentHistory.Add(new RecurringPaymentHistory + { + RecurringPayment = rp, + CreatedOnUtc = DateTime.UtcNow, + OrderId = order.Id, + }); + _orderService.UpdateRecurringPayment(rp); + break; + case RecurringPaymentType.Automatic: + //will be created later (process is automated) + break; + default: + break; + } + } + + #endregion + + //notifications + SendNotificationsAndSaveNotes(order); + + //reset checkout data + _customerService.ResetCheckoutData(details.Customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); + _customerActivityService.InsertActivity("PublicStore.PlaceOrder", _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), order.Id); + + //check order status + CheckOrderStatus(order); + + //raise event + _eventPublisher.Publish(new OrderPlacedEvent(order)); + + if (order.PaymentStatus == PaymentStatus.Paid) + ProcessOrderPaid(order); + } + else + foreach (var paymentError in processPaymentResult.Errors) + result.AddError(string.Format(_localizationService.GetResource("Checkout.PaymentError"), paymentError)); + } + catch (Exception exc) + { + _logger.Error(exc.Message, exc); + result.AddError(exc.Message); + } + + #region Process errors + + if (!result.Success) + { + //log errors + var logError = result.Errors.Aggregate("Error while placing order. ", + (current, next) => string.Format("{0}Error {1}: {2}. ", current, result.Errors.IndexOf(next) + 1, next)); + var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + _logger.Error(logError, customer: customer); + } + + #endregion + + 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, + VatRate = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.VatRate : orderItem.VatRate, + SubTotalInclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalInclTax : orderItem.PriceInclTax, + SubTotalExclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalExclTax : orderItem.PriceExclTax + }).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,subTotal: updatedOrder.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? updatedOrderItem.PriceInclTax : updatedOrderItem.PriceExclTax); + + //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.Any(history => history.DiscountId == discount.Id)) + { + var d = _discountService.GetDiscountById(discount.Id); + if (d != null) + { + _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory + { + Discount = d, + Order = updatedOrder, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + } + + CheckOrderStatus(updatedOrder); + } + + /// + /// Deletes an order + /// + /// The order + public virtual void DeleteOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //check whether the order wasn't cancelled before + //if it already was cancelled, then there's no need to make the following adjustments + //(such as reward points, inventory, recurring payments) + //they already was done when cancelling the order + if (order.OrderStatus != OrderStatus.Cancelled) + { + //return (add) back redeemded reward points + ReturnBackRedeemedRewardPoints(order); + //reduce (cancel) back reward points (previously awarded for this order) + ReduceRewardPoints(order); + + //cancel recurring payments + var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); + foreach (var rp in recurringPayments) + { + var errors = CancelRecurringPayment(rp); + //use "errors" variable? + } + + //Adjust inventory for already shipped shipments + //only products with "use multiple warehouses" + foreach (var shipment in order.Shipments) + { + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); + } + } + + //Adjust inventory + foreach (var orderItem in order.OrderItems) + { + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); + } + + } + + //deactivate gift cards + if (_orderSettings.DeactivateGiftCardsAfterDeletingOrder) + SetActivatedValueForPurchasedGiftCards(order, false); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been deleted", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //now delete an order + _orderService.DeleteOrder(order); + } + + /// + /// Process next recurring payment + /// + /// Recurring payment + /// Process payment result (info about last payment for automatic recurring payments) + /// Collection of errors + public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment recurringPayment, ProcessPaymentResult paymentResult = null) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + try + { + if (!recurringPayment.IsActive) + throw new NopException("Recurring payment is not active"); + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + throw new NopException("Initial order could not be loaded"); + + var customer = initialOrder.Customer; + if (customer == null) + throw new NopException("Customer could not be loaded"); + + var nextPaymentDate = recurringPayment.NextPaymentDate; + if (!nextPaymentDate.HasValue) + throw new NopException("Next payment date could not be calculated"); + + //payment info + var processPaymentRequest = new ProcessPaymentRequest + { + StoreId = initialOrder.StoreId, + CustomerId = customer.Id, + OrderGuid = Guid.NewGuid(), + InitialOrderId = initialOrder.Id, + RecurringCycleLength = recurringPayment.CycleLength, + RecurringCyclePeriod = recurringPayment.CyclePeriod, + RecurringTotalCycles = recurringPayment.TotalCycles, + CustomValues = initialOrder.DeserializeCustomValues() + }; + + //prepare order details + var details = PrepareRecurringOrderDetails(processPaymentRequest); + + ProcessPaymentResult processPaymentResult; + //skip payment workflow if order total equals zero + var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; + if (!skipPaymentWorkflow) + { + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + + if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) + throw new NopException("Payment method is not active"); + + //Old credit card info + if (details.InitialOrder.AllowStoringCreditCardNumber) + { + processPaymentRequest.CreditCardType = _encryptionService.DecryptText(details.InitialOrder.CardType); + processPaymentRequest.CreditCardName = _encryptionService.DecryptText(details.InitialOrder.CardName); + processPaymentRequest.CreditCardNumber = _encryptionService.DecryptText(details.InitialOrder.CardNumber); + processPaymentRequest.CreditCardCvv2 = _encryptionService.DecryptText(details.InitialOrder.CardCvv2); + try + { + processPaymentRequest.CreditCardExpireMonth = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationMonth)); + processPaymentRequest.CreditCardExpireYear = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationYear)); + } + catch { } + } + + //payment type + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + throw new NopException("Recurring payments are not supported by selected payment method"); + case RecurringPaymentType.Manual: + processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); + break; + case RecurringPaymentType.Automatic: + //payment is processed on payment gateway site, info about last transaction in paymentResult parameter + processPaymentResult = paymentResult ?? new ProcessPaymentResult(); + break; + default: + throw new NopException("Not supported recurring payment type"); + } + } + else + processPaymentResult = paymentResult ?? new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; + + if (processPaymentResult == null) + throw new NopException("processPaymentResult is not available"); + + if (processPaymentResult.Success) + { + //save order details + var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); + + foreach (var orderItem in details.InitialOrder.OrderItems) + { + //save item + var newOrderItem = new OrderItem + { + OrderItemGuid = Guid.NewGuid(), + Order = order, + ProductId = orderItem.ProductId, + UnitPriceInclTax = orderItem.UnitPriceInclTax, + UnitPriceExclTax = orderItem.UnitPriceExclTax, + PriceInclTax = orderItem.PriceInclTax, + PriceExclTax = orderItem.PriceExclTax, + OriginalProductCost = orderItem.OriginalProductCost, + AttributeDescription = orderItem.AttributeDescription, + AttributesXml = orderItem.AttributesXml, + Quantity = orderItem.Quantity, + DiscountAmountInclTax = orderItem.DiscountAmountInclTax, + DiscountAmountExclTax = orderItem.DiscountAmountExclTax, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = orderItem.ItemWeight, + RentalStartDateUtc = orderItem.RentalStartDateUtc, + RentalEndDateUtc = orderItem.RentalEndDateUtc, + VatRate = orderItem.VatRate + }; + order.OrderItems.Add(newOrderItem); + _orderService.UpdateOrder(order); + + //gift cards + if (orderItem.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + + _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < orderItem.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = orderItem.Product.GiftCardType, + PurchasedWithOrderItem = newOrderItem, + Amount = orderItem.UnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //inventory + _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); + } + + //notifications + SendNotificationsAndSaveNotes(order); + + //check order status + CheckOrderStatus(order); + + //raise event + _eventPublisher.Publish(new OrderPlacedEvent(order)); + + if (order.PaymentStatus == PaymentStatus.Paid) + ProcessOrderPaid(order); + + //last payment succeeded + recurringPayment.LastPaymentFailed = false; + + //next recurring payment + recurringPayment.RecurringPaymentHistory.Add(new RecurringPaymentHistory + { + RecurringPayment = recurringPayment, + CreatedOnUtc = DateTime.UtcNow, + OrderId = order.Id, + }); + _orderService.UpdateRecurringPayment(recurringPayment); + + return new List(); + } + else + { + //log errors + var logError = processPaymentResult.Errors.Aggregate("Error while processing recurring order. ", + (current, next) => string.Format("{0}Error {1}: {2}. ", current, processPaymentResult.Errors.IndexOf(next) + 1, next)); + _logger.Error(logError, customer: customer); + + if (processPaymentResult.RecurringPaymentFailed) + { + //set flag that last payment failed + recurringPayment.LastPaymentFailed = true; + _orderService.UpdateRecurringPayment(recurringPayment); + + if (_paymentSettings.CancelRecurringPaymentsAfterFailedPayment) + { + //cancel recurring payment + CancelRecurringPayment(recurringPayment).ToList().ForEach(error => _logger.Error(error)); + + //notify a customer about cancelled payment + _workflowMessageService.SendRecurringPaymentCancelledCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); + } + else + //notify a customer about failed payment + _workflowMessageService.SendRecurringPaymentFailedCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); + } + + return processPaymentResult.Errors; + } + } + catch (Exception exc) + { + _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); + throw; + } + } + + /// + /// Cancels a recurring payment + /// + /// Recurring payment + public virtual IList CancelRecurringPayment(RecurringPayment recurringPayment) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + return new List { "Initial order could not be loaded" }; + + + var request = new CancelRecurringPaymentRequest(); + CancelRecurringPaymentResult result = null; + try + { + request.Order = initialOrder; + result = _paymentService.CancelRecurringPayment(request); + if (result.Success) + { + //update recurring payment + recurringPayment.IsActive = false; + _orderService.UpdateRecurringPayment(recurringPayment); + + + //add a note + initialOrder.OrderNotes.Add(new OrderNote + { + Note = "Recurring payment has been cancelled", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(initialOrder); + + //notify a store owner + _workflowMessageService + .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, + _localizationSettings.DefaultAdminLanguageId); + } + } + catch (Exception exc) + { + if (result == null) + result = new CancelRecurringPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + + //process errors + string error = ""; + for (int i = 0; i < result.Errors.Count; i++) + { + error += string.Format("Error {0}: {1}", i, result.Errors[i]); + if (i != result.Errors.Count - 1) + error += ". "; + } + if (!String.IsNullOrEmpty(error)) + { + //add a note + initialOrder.OrderNotes.Add(new OrderNote + { + Note = string.Format("Unable to cancel recurring payment. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(initialOrder); + + //log it + string logError = string.Format("Error cancelling recurring payment. Order #{0}. Error: {1}", initialOrder.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether a customer can cancel recurring payment + /// + /// Customer + /// Recurring Payment + /// value indicating whether a customer can cancel recurring payment + public virtual bool CanCancelRecurringPayment(Customer customerToValidate, RecurringPayment recurringPayment) + { + if (recurringPayment == null) + return false; + + if (customerToValidate == null) + return false; + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + return false; + + var customer = recurringPayment.InitialOrder.Customer; + if (customer == null) + return false; + + if (initialOrder.OrderStatus == OrderStatus.Cancelled) + return false; + + if (!customerToValidate.IsAdmin()) + { + if (customer.Id != customerToValidate.Id) + return false; + } + + if (!recurringPayment.NextPaymentDate.HasValue) + return false; + + return true; + } + + + /// + /// Gets a value indicating whether a customer can retry last failed recurring payment + /// + /// Customer + /// Recurring Payment + /// True if a customer can retry payment; otherwise false + public virtual bool CanRetryLastRecurringPayment(Customer customer, RecurringPayment recurringPayment) + { + if (recurringPayment == null || customer == null) + return false; + + if (recurringPayment.InitialOrder == null || recurringPayment.InitialOrder.OrderStatus == OrderStatus.Cancelled) + return false; + + if (!recurringPayment.LastPaymentFailed || _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName) != RecurringPaymentType.Manual) + return false; + + if (recurringPayment.InitialOrder.Customer == null || (!customer.IsAdmin() && recurringPayment.InitialOrder.Customer.Id != customer.Id)) + return false; + + return true; + } + + + /// + /// Send a shipment + /// + /// Shipment + /// True to notify customer + public virtual void Ship(Shipment shipment, bool notifyCustomer) + { + if (shipment == null) + throw new ArgumentNullException("shipment"); + + var order = _orderService.GetOrderById(shipment.OrderId); + if (order == null) + throw new Exception("Order cannot be loaded"); + + if (shipment.ShippedDateUtc.HasValue) + throw new Exception("This shipment is already shipped"); + + shipment.ShippedDateUtc = DateTime.UtcNow; + _shipmentService.UpdateShipment(shipment); + + //process products with "Multiple warehouse" support enabled + foreach (var item in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(item.OrderItemId); + _productService.BookReservedInventory(orderItem.Product, item.WarehouseId, -item.Quantity, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.Ship"), shipment.OrderId)); + } + + //check whether we have more items to ship + if (order.HasItemsToAddToShipment() || order.HasItemsToShip()) + order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped; + else + order.ShippingStatusId = (int)ShippingStatus.Shipped; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Shipment# {0} has been sent", shipment.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + if (notifyCustomer) + { + //notify customer + int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); + if (queuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Shipped\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //event + _eventPublisher.PublishShipmentSent(shipment); + + //check order status + CheckOrderStatus(order); + } + + /// + /// Marks a shipment as delivered + /// + /// Shipment + /// True to notify customer + public virtual void Deliver(Shipment shipment, bool notifyCustomer) + { + if (shipment == null) + throw new ArgumentNullException("shipment"); + + var order = shipment.Order; + if (order == null) + throw new Exception("Order cannot be loaded"); + + if (!shipment.ShippedDateUtc.HasValue) + throw new Exception("This shipment is not shipped yet"); + + if (shipment.DeliveryDateUtc.HasValue) + throw new Exception("This shipment is already delivered"); + + shipment.DeliveryDateUtc = DateTime.UtcNow; + _shipmentService.UpdateShipment(shipment); + + if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) + order.ShippingStatusId = (int)ShippingStatus.Delivered; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Shipment# {0} has been delivered", shipment.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + if (notifyCustomer) + { + //send email notification + int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); + if (queuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Delivered\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //event + _eventPublisher.PublishShipmentDelivered(shipment); + + //check order status + CheckOrderStatus(order); + } + + + + /// + /// Gets a value indicating whether cancel is allowed + /// + /// Order + /// A value indicating whether cancel is allowed + public virtual bool CanCancelOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + return true; + } + + /// + /// Cancels order + /// + /// Order + /// True to notify customer + public virtual void CancelOrder(Order order, bool notifyCustomer) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanCancelOrder(order)) + throw new NopException("Cannot do cancel for order."); + + //Cancel order + SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been cancelled", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //return (add) back redeemded reward points + ReturnBackRedeemedRewardPoints(order); + + //cancel recurring payments + var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); + foreach (var rp in recurringPayments) + { + var errors = CancelRecurringPayment(rp); + //use "errors" variable? + } + + //Adjust inventory for already shipped shipments + //only products with "use multiple warehouses" + foreach (var shipment in order.Shipments) + { + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); + } + } + //Adjust inventory + foreach (var orderItem in order.OrderItems) + { + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); + } + + _eventPublisher.Publish(new OrderCancelledEvent(order)); + + } + + /// + /// Gets a value indicating whether order can be marked as authorized + /// + /// Order + /// A value indicating whether order can be marked as authorized + public virtual bool CanMarkOrderAsAuthorized(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + if (order.PaymentStatus == PaymentStatus.Pending) + return true; + + return false; + } + + /// + /// Marks order as authorized + /// + /// Order + public virtual void MarkAsAuthorized(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + order.PaymentStatusId = (int)PaymentStatus.Authorized; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as authorized", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + } + + + + /// + /// Gets a value indicating whether capture from admin panel is allowed + /// + /// Order + /// A value indicating whether capture from admin panel is allowed + public virtual bool CanCapture(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled || + order.OrderStatus == OrderStatus.Pending) + return false; + + if (order.PaymentStatus == PaymentStatus.Authorized && + _paymentService.SupportCapture(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Capture an order (from admin panel) + /// + /// Order + /// A list of errors; empty list if no errors + public virtual IList Capture(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanCapture(order)) + throw new NopException("Cannot do capture for order."); + + var request = new CapturePaymentRequest(); + CapturePaymentResult result = null; + try + { + //old info from placing order + request.Order = order; + result = _paymentService.Capture(request); + + if (result.Success) + { + var paidDate = order.PaidDateUtc; + if (result.NewPaymentStatus == PaymentStatus.Paid) + paidDate = DateTime.UtcNow; + + order.CaptureTransactionId = result.CaptureTransactionId; + order.CaptureTransactionResult = result.CaptureTransactionResult; + order.PaymentStatus = result.NewPaymentStatus; + order.PaidDateUtc = paidDate; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been captured", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + CheckOrderStatus(order); + + if (order.PaymentStatus == PaymentStatus.Paid) + { + ProcessOrderPaid(order); + } + } + } + catch (Exception exc) + { + if (result == null) + result = new CapturePaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + + //process errors + string error = ""; + for (int i = 0; i < result.Errors.Count; i++) + { + error += string.Format("Error {0}: {1}", i, result.Errors[i]); + if (i != result.Errors.Count - 1) + error += ". "; + } + if (!String.IsNullOrEmpty(error)) + { + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Unable to capture order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error capturing order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as paid + /// + /// Order + /// A value indicating whether order can be marked as paid + public virtual bool CanMarkOrderAsPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + if (order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.Refunded || + order.PaymentStatus == PaymentStatus.Voided) + return false; + + return true; + } + + /// + /// Marks order as paid + /// + /// Order + public virtual void MarkOrderAsPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanMarkOrderAsPaid(order)) + throw new NopException("You can't mark this order as paid"); + + order.PaymentStatusId = (int)PaymentStatus.Paid; + order.PaidDateUtc = DateTime.UtcNow; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as paid", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + CheckOrderStatus(order); + + if (order.PaymentStatus == PaymentStatus.Paid) + { + ProcessOrderPaid(order); + } + } + + + + /// + /// Gets a value indicating whether refund from admin panel is allowed + /// + /// Order + /// A value indicating whether refund from admin panel is allowed + public virtual bool CanRefund(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case + if (order.RefundedAmount > decimal.Zero) + return false; + + //uncomment the lines below in order to disallow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid && + _paymentService.SupportRefund(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Refunds an order (from admin panel) + /// + /// Order + /// A list of errors; empty list if no errors + public virtual IList Refund(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanRefund(order)) + throw new NopException("Cannot do refund for order."); + + var request = new RefundPaymentRequest(); + RefundPaymentResult result = null; + try + { + request.Order = order; + request.AmountToRefund = order.OrderTotal; + request.IsPartialRefund = false; + result = _paymentService.Refund(request); + if (result.Success) + { + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + request.AmountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + order.PaymentStatus = result.NewPaymentStatus; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been refunded. Amount = {0}", request.AmountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, request.AmountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, request.AmountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, request.AmountToRefund)); + } + + } + catch (Exception exc) + { + if (result == null) + result = new RefundPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //process errors + string error = ""; + for (int i = 0; i < result.Errors.Count; i++) + { + error += string.Format("Error {0}: {1}", i, result.Errors[i]); + if (i != result.Errors.Count - 1) + error += ". "; + } + if (!String.IsNullOrEmpty(error)) + { + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Unable to refund order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as refunded + /// + /// Order + /// A value indicating whether order can be marked as refunded + public virtual bool CanRefundOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case + if (order.RefundedAmount > decimal.Zero) + return false; + + //uncomment the lines below in order to disallow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid) + return true; + + return false; + } + + /// + /// Refunds an order (offline) + /// + /// Order + public virtual void RefundOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanRefundOffline(order)) + throw new NopException("You can't refund this order"); + + //amout to refund + decimal amountToRefund = order.OrderTotal; + + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + order.PaymentStatus = PaymentStatus.Refunded; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been marked as refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + + /// + /// Gets a value indicating whether partial refund from admin panel is allowed + /// + /// Order + /// Amount to refund + /// A value indicating whether refund from admin panel is allowed + public virtual bool CanPartiallyRefund(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; + if (canBeRefunded <= decimal.Zero) + return false; + + if (amountToRefund > canBeRefunded) + return false; + + if ((order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.PartiallyRefunded) && + _paymentService.SupportPartiallyRefund(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Partially refunds an order (from admin panel) + /// + /// Order + /// Amount to refund + /// A list of errors; empty list if no errors + public virtual IList PartiallyRefund(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanPartiallyRefund(order, amountToRefund)) + throw new NopException("Cannot do partial refund for order."); + + var request = new RefundPaymentRequest(); + RefundPaymentResult result = null; + try + { + request.Order = order; + request.AmountToRefund = amountToRefund; + request.IsPartialRefund = true; + + result = _paymentService.Refund(request); + + if (result.Success) + { + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + //mark payment status as 'Refunded' if the order total amount is fully refunded + order.PaymentStatus = order.OrderTotal == totalAmountRefunded && result.NewPaymentStatus == PaymentStatus.PartiallyRefunded ? PaymentStatus.Refunded : result.NewPaymentStatus; + _orderService.UpdateOrder(order); + + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been partially refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + } + catch (Exception exc) + { + if (result == null) + result = new RefundPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //process errors + string error = ""; + for (int i = 0; i < result.Errors.Count; i++) + { + error += string.Format("Error {0}: {1}", i, result.Errors[i]); + if (i != result.Errors.Count - 1) + error += ". "; + } + if (!String.IsNullOrEmpty(error)) + { + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Unable to partially refund order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as partially refunded + /// + /// Order + /// Amount to refund + /// A value indicating whether order can be marked as partially refunded + public virtual bool CanPartiallyRefundOffline(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; + if (canBeRefunded <= decimal.Zero) + return false; + + if (amountToRefund > canBeRefunded) + return false; + + if (order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.PartiallyRefunded) + return true; + + return false; + } + + /// + /// Partially refunds an order (offline) + /// + /// Order + /// Amount to refund + public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanPartiallyRefundOffline(order, amountToRefund)) + throw new NopException("You can't partially refund (offline) this order"); + + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + //mark payment status as 'Refunded' if the order total amount is fully refunded + order.PaymentStatus = order.OrderTotal == totalAmountRefunded ? PaymentStatus.Refunded : PaymentStatus.PartiallyRefunded; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been marked as partially refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + + + + /// + /// Gets a value indicating whether void from admin panel is allowed + /// + /// Order + /// A value indicating whether void from admin panel is allowed + public virtual bool CanVoid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Authorized && + _paymentService.SupportVoid(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Voids order (from admin panel) + /// + /// Order + /// Voided order + public virtual IList Void(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanVoid(order)) + throw new NopException("Cannot do void for order."); + + var request = new VoidPaymentRequest(); + VoidPaymentResult result = null; + try + { + request.Order = order; + result = _paymentService.Void(request); + + if (result.Success) + { + //update order info + order.PaymentStatus = result.NewPaymentStatus; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been voided", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + } + } + catch (Exception exc) + { + if (result == null) + result = new VoidPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //process errors + string error = ""; + for (int i = 0; i < result.Errors.Count; i++) + { + error += string.Format("Error {0}: {1}", i, result.Errors[i]); + if (i != result.Errors.Count - 1) + error += ". "; + } + if (!String.IsNullOrEmpty(error)) + { + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Unable to voiding order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error voiding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as voided + /// + /// Order + /// A value indicating whether order can be marked as voided + public virtual bool CanVoidOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Authorized) + return true; + + return false; + } + + /// + /// Voids order (offline) + /// + /// Order + public virtual void VoidOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanVoidOffline(order)) + throw new NopException("You can't void this order"); + + order.PaymentStatusId = (int)PaymentStatus.Voided; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as voided", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check orer status + CheckOrderStatus(order); + } + + + + /// + /// Place order items in current user shopping cart. + /// + /// The order + public virtual void ReOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //move shopping cart items (if possible) + foreach (var orderItem in order.OrderItems) + { + _shoppingCartService.AddToCart(order.Customer, orderItem.Product, + ShoppingCartType.ShoppingCart, order.StoreId, + orderItem.AttributesXml, orderItem.UnitPriceExclTax, + orderItem.RentalStartDateUtc, orderItem.RentalEndDateUtc, + orderItem.Quantity, false); + } + + //set checkout attributes + //comment the code below if you want to disable this functionality + _genericAttributeService.SaveAttribute(order.Customer, SystemCustomerAttributeNames.CheckoutAttributes, order.CheckoutAttributesXml, order.StoreId); + } + + /// + /// Check whether return request is allowed + /// + /// Order + /// Result + public virtual bool IsReturnRequestAllowed(Order order) + { + if (!_orderSettings.ReturnRequestsEnabled) + return false; + + if (order == null || order.Deleted) + return false; + + //status should be compelte + if (order.OrderStatus != OrderStatus.Complete) + return false; + + //validate allowed number of days + if (_orderSettings.NumberOfDaysReturnRequestAvailable > 0) + { + var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; + if (daysPassed >= _orderSettings.NumberOfDaysReturnRequestAvailable) + return false; + } + + //ensure that we have at least one returnable product + return order.OrderItems.Any(oi => !oi.Product.NotReturnable); + } + + + + /// + /// Valdiate minimum order sub-total amount + /// + /// Shopping cart + /// true - OK; false - minimum order sub-total amount is not reached + public virtual bool ValidateMinOrderSubtotalAmount(IList cart) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + //min order amount sub-total validation + if (cart.Any() && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) + { + //subtotal + decimal orderSubTotalDiscountAmountBase; + List orderSubTotalAppliedDiscounts; + decimal subTotalWithoutDiscountBase; + decimal subTotalWithDiscountBase; + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, _orderSettings.MinOrderSubtotalAmountIncludingTax, + out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscounts, + out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); + + if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) + return false; + } + + return true; + } + + /// + /// Valdiate minimum order total amount + /// + /// Shopping cart + /// true - OK; false - minimum order total amount is not reached + public virtual bool ValidateMinOrderTotalAmount(IList cart) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + if (cart.Any() && _orderSettings.MinOrderTotalAmount > decimal.Zero) + { + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) + return false; + } + + return true; + } + + /// + /// Get invoice ID + /// + /// + public virtual string GetInvoiceId() + { + var actYear = DateTime.UtcNow.Year; + int ident = _orderSettings.InvoiceIdent; + if (_orderSettings.InvoiceYear < actYear) + { + // Reset counter if a new year + ident = 1; + _orderSettings.InvoiceYear = actYear; + _settingService.SetSetting("ordersettings.invoiceyear", _orderSettings.InvoiceYear); + } + else + { + ident += 1; + } + _orderSettings.InvoiceIdent = ident; + // Update settings + _settingService.SetSetting("ordersettings.invoiceident", _orderSettings.InvoiceIdent); + + return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); + } + /// + /// Gets a value indicating whether payment workflow is required + /// + /// Shopping cart + /// A value indicating reward points should be used; null to detect current choice of the customer + /// true - OK; false - minimum order total amount is not reached + public virtual bool IsPaymentWorkflowRequired(IList cart, bool? useRewardPoints = null) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + bool result = true; + + //check whether order total equals zero + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, useRewardPoints: useRewardPoints); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value == decimal.Zero) + result = false; + return result; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs index 94ded4673ca..9c170915837 100644 --- a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs @@ -43,6 +43,7 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic private readonly ShippingSettings _shippingSettings; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; + private readonly IProductAttributeParser _productAttributeParser; #endregion #region Ctor @@ -66,6 +67,7 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic /// Shipping settings /// Shopping cart settings /// Catalog settings + /// Product Attribute Parser public OrderTotalCalculationService(IWorkContext workContext, IStoreContext storeContext, IPriceCalculationService priceCalculationService, @@ -81,7 +83,8 @@ public OrderTotalCalculationService(IWorkContext workContext, RewardPointsSettings rewardPointsSettings, ShippingSettings shippingSettings, ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IProductAttributeParser productAttributeParser) { this._workContext = workContext; this._storeContext = storeContext; @@ -99,6 +102,7 @@ public OrderTotalCalculationService(IWorkContext workContext, this._shippingSettings = shippingSettings; this._shoppingCartSettings = shoppingCartSettings; this._catalogSettings = catalogSettings; + this._productAttributeParser = productAttributeParser; } #endregion @@ -269,7 +273,7 @@ public virtual void GetShoppingCartSubTotal(IList cart, decimal taxRate; //restored cartitem - if (shoppingCartItem.VatRate.HasValue) + if (shoppingCartItem.VatRate.HasValue && shoppingCartItem.SubTotalExclTax != null && shoppingCartItem.SubTotalInclTax != null) { itemAmount = includingTax ? shoppingCartItem.SubTotalInclTax ?? 0 : shoppingCartItem.SubTotalExclTax ?? 0; taxRate = shoppingCartItem.VatRate ?? 0; @@ -282,9 +286,19 @@ public virtual void GetShoppingCartSubTotal(IList cart, itemAmount = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, includingTax, customer, out taxRate); subTotalAmount += itemAmount; } + //tax rates - if (itemAmount > decimal.Zero) //MF 22.11.16 taxRate > decimal.Zero &&: VatBase is need also when tax is 0 to show up in summary - taxSummary.AddRate(taxRate, itemAmount); + var attributesXml = shoppingCartItem.AttributesXml; + SortedDictionary attributeTaxWeight = !String.IsNullOrEmpty(attributesXml) ? _productAttributeParser.ParseTaxAttribute(attributesXml) : null; + if (attributeTaxWeight == null || !attributeTaxWeight.Any()) + { + if (itemAmount > decimal.Zero) //MF 22.11.16 taxRate > decimal.Zero &&: VatBase is need also when tax is 0 to show up in summary + taxSummary.AddRate(taxRate, itemAmount); + } + else //tax in attributes + { + taxSummary.AddAttributeRate(itemAmount, attributeTaxWeight); + } } //checkout attributes @@ -495,16 +509,7 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter #region Tax rates //tax rates var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; - updatedOrder.TaxRates = taxrates.TaxRates.Aggregate(string.Empty, (current, next) => - string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, - next.Key.ToString(CultureInfo.InvariantCulture), - (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), - (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), - next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), - next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), - next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) - ) - ); + updatedOrder.TaxRates = taxrates.GenerateTaxRateString(); #endregion @@ -862,7 +867,7 @@ public virtual decimal GetTaxTotal(IList cart, if (paymentMethodAdditionalFee != decimal.Zero ) //tfc case of taxable fee > 0 - if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) + if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) taxSummary.AddRate(taxRate, paymentfeeAmount: paymentMethodAdditionalFee); //tfc case fee < 0 is considered as discount or as surcharge when not taxable else diff --git a/src/Libraries/Nop.Services/Tax/TaxService.cs b/src/Libraries/Nop.Services/Tax/TaxService.cs index 9a461ea8797..5bfcc67eb36 100644 --- a/src/Libraries/Nop.Services/Tax/TaxService.cs +++ b/src/Libraries/Nop.Services/Tax/TaxService.cs @@ -1,838 +1,835 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Tax; -using Nop.Core.Plugins; -using Nop.Services.Common; -using Nop.Services.Directory; -using Nop.Services.Logging; - -namespace Nop.Services.Tax -{ - /// - /// Tax service - /// - public partial class TaxService : ITaxService - { - #region Fields - - private readonly IAddressService _addressService; - private readonly IWorkContext _workContext; - private readonly TaxSettings _taxSettings; - private readonly IPluginFinder _pluginFinder; - private readonly IGeoLookupService _geoLookupService; - private readonly ICountryService _countryService; - private readonly ILogger _logger; - private readonly CustomerSettings _customerSettings; - private readonly AddressSettings _addressSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Address service - /// Work context - /// Tax settings - /// Plugin finder - /// GEO lookup service - /// Country service - /// Logger service - /// Customer settings - /// Address settings - public TaxService(IAddressService addressService, - IWorkContext workContext, - TaxSettings taxSettings, - IPluginFinder pluginFinder, - IGeoLookupService geoLookupService, - ICountryService countryService, - ILogger logger, - CustomerSettings customerSettings, - AddressSettings addressSettings) - { - this._addressService = addressService; - this._workContext = workContext; - this._taxSettings = taxSettings; - this._pluginFinder = pluginFinder; - this._geoLookupService = geoLookupService; - this._countryService = countryService; - this._logger = logger; - this._customerSettings = customerSettings; - this._addressSettings = addressSettings; - } - - #endregion - - #region Utilities - - /// - /// Get a value indicating whether a customer is consumer (a person, not a company) located in Europe Union - /// - /// Customer - /// Result - protected virtual bool IsEuConsumer(Customer customer) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - Country country = null; - - //get country from billing address - if (_addressSettings.CountryEnabled && customer.BillingAddress != null) - country = customer.BillingAddress.Country; - - //get country specified during registration? - if (country == null && _customerSettings.CountryEnabled) - { - var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - country = _countryService.GetCountryById(countryId); - } - - //get country by IP address - if (country == null) - { - var ipAddress = customer.LastIpAddress; - //ipAddress = _webHelper.GetCurrentIpAddress(); - var countryIsoCode = _geoLookupService.LookupCountryIsoCode(ipAddress); - country = _countryService.GetCountryByTwoLetterIsoCode(countryIsoCode); - } - - //we cannot detect country - if (country == null) - return false; - - //outside EU - if (!country.SubjectToVat) - return false; - - //company (business) or consumer? - var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - if (customerVatStatus == VatNumberStatus.Valid) - return false; - - //TODO: use specified company name? (both address and registration one) - - //consumer - return true; - } - - /// - /// Create request for tax calculation - /// - /// Product - /// Tax category identifier - /// Customer - /// Price - /// Package for tax calculation - protected virtual CalculateTaxRequest CreateCalculateTaxRequest(Product product, - int taxCategoryId, Customer customer, decimal price) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var calculateTaxRequest = new CalculateTaxRequest - { - Customer = customer, - Product = product, - Price = price - }; - if (taxCategoryId > 0) - { - calculateTaxRequest.TaxCategoryId = taxCategoryId; - } - else - { - if (product != null) - calculateTaxRequest.TaxCategoryId = product.TaxCategoryId; - } - - var basedOn = _taxSettings.TaxBasedOn; - //new EU VAT rules starting January 1st 2015 - //find more info at http://ec.europa.eu/taxation_customs/taxation/vat/how_vat_works/telecom/index_en.htm#new_rules - //EU VAT enabled? - if (_taxSettings.EuVatEnabled) - { - //telecommunications, broadcasting and electronic services? - if (product != null && product.IsTelecommunicationsOrBroadcastingOrElectronicServices) - { - //January 1st 2015 passed? - if (DateTime.UtcNow > new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)) - { - //Europe Union consumer? - if (IsEuConsumer(customer)) - { - //We must charge VAT in the EU country where the customer belongs (not where the business is based) - basedOn = TaxBasedOn.BillingAddress; - } - } - } - } - if (basedOn == TaxBasedOn.BillingAddress && customer.BillingAddress == null) - basedOn = TaxBasedOn.DefaultAddress; - if (basedOn == TaxBasedOn.ShippingAddress && customer.ShippingAddress == null) - basedOn = TaxBasedOn.DefaultAddress; - - Address address; - - switch (basedOn) - { - case TaxBasedOn.BillingAddress: - { - address = customer.BillingAddress; - } - break; - case TaxBasedOn.ShippingAddress: - { - address = customer.ShippingAddress; - } - break; - case TaxBasedOn.DefaultAddress: - default: - { - address = _addressService.GetAddressById(_taxSettings.DefaultTaxAddressId); - } - break; - } - - calculateTaxRequest.Address = address; - return calculateTaxRequest; - } - - /// - /// Calculated price - /// - /// Price - /// Percent - /// Increase - /// New price - protected virtual decimal CalculatePrice(decimal price, decimal percent, bool increase) - { - if (percent == decimal.Zero) - return price; - - decimal result; - if (increase) - { - result = price * (1 + percent / 100); - } - else - { - result = price / (1 + percent / 100); //MF 13.11.16: is the same but written in a complicated way: price - (price) / (100 + percent) * percent; - } - return result; - } - - /// - /// Gets tax rate - /// - /// Product - /// Tax category identifier - /// Customer - /// Price (taxable value) - /// Calculated tax rate - /// A value indicating whether a request is taxable - protected virtual void GetTaxRate(Product product, int taxCategoryId, - Customer customer, decimal price, out decimal taxRate, out bool isTaxable) - { - taxRate = decimal.Zero; - isTaxable = true; - - //active tax provider - var activeTaxProvider = LoadActiveTaxProvider(customer); - if (activeTaxProvider == null) - return; - - //tax request - var calculateTaxRequest = CreateCalculateTaxRequest(product, taxCategoryId, customer, price); - - //tax exempt - if (IsTaxExempt(product, calculateTaxRequest.Customer)) - { - isTaxable = false; - } - //make EU VAT exempt validation (the European Union Value Added Tax) - if (isTaxable && - _taxSettings.EuVatEnabled && - IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) - { - //VAT is not chargeable - isTaxable = false; - } - - //get tax rate - var calculateTaxResult = activeTaxProvider.GetTaxRate(calculateTaxRequest); - if (calculateTaxResult.Success) - { - //ensure that tax is equal or greater than zero - if (calculateTaxResult.TaxRate < decimal.Zero) - calculateTaxResult.TaxRate = decimal.Zero; - - taxRate = calculateTaxResult.TaxRate; - } - else - if (_taxSettings.LogErrors) - { - foreach (var error in calculateTaxResult.Errors) - { - _logger.Error(string.Format("{0} - {1}", activeTaxProvider.PluginDescriptor.FriendlyName, error), null, customer); - } - } - } - - #endregion - - #region Methods - - #region Tax providers - - /// - /// Load active tax provider - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Active tax provider - public virtual ITaxProvider LoadActiveTaxProvider(Customer customer = null) - { - var taxProvider = LoadTaxProviderBySystemName(_taxSettings.ActiveTaxProviderSystemName); - if (taxProvider == null) - taxProvider = LoadAllTaxProviders(customer).FirstOrDefault(); - return taxProvider; - } - - /// - /// Load tax provider by system name - /// - /// System name - /// Found tax provider - public virtual ITaxProvider LoadTaxProviderBySystemName(string systemName) - { - var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); - if (descriptor != null) - return descriptor.Instance(); - - return null; - } - - /// - /// Load all tax providers - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Tax providers - public virtual IList LoadAllTaxProviders(Customer customer = null) - { - return _pluginFinder.GetPlugins(customer: customer).ToList(); - } - - #endregion - - #region Product price - - /// - /// Gets price - /// - /// Product - /// Price - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - out decimal taxRate) - { - var customer = _workContext.CurrentCustomer; - return GetProductPrice(product, price, customer, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Price - /// Customer - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - Customer customer, out decimal taxRate) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetProductPrice(product, price, includingTax, customer, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - bool includingTax, Customer customer, out decimal taxRate) - { - bool priceIncludesTax = _taxSettings.PricesIncludeTax; - int taxCategoryId = 0; - return GetProductPrice(product, taxCategoryId, price, includingTax, - customer, priceIncludesTax, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Tax category identifier - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// A value indicating whether price already includes tax - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, int taxCategoryId, - decimal price, bool includingTax, Customer customer, - bool priceIncludesTax, out decimal taxRate) - { - bool isTaxable; - //taxrate should be always passed - GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); - - //no need to calculate tax rate if passed "price" is 0 - if (price == decimal.Zero) - { - return price; - } - - - - - if (priceIncludesTax) - { - //"price" already includes tax - if (includingTax) - { - //we should calculate price WITH tax - if (!isTaxable) - { - //but our request is not taxable - //hence we should calculate price WITHOUT tax - price = CalculatePrice(price, taxRate, false); - } - } - else - { - //we should calculate price WITHOUT tax - price = CalculatePrice(price, taxRate, false); - } - } - else - { - //"price" doesn't include tax - if (includingTax) - { - //we should calculate price WITH tax - //do it only when price is taxable - if (isTaxable) - { - price = CalculatePrice(price, taxRate, true); - } - } - } - - - if (!isTaxable) - { - //we return 0% tax rate in case a request is not taxable - taxRate = decimal.Zero; - } - - //allowed to support negative price adjustments - //if (price < decimal.Zero) - // price = decimal.Zero; - - return price; - } - - #endregion - - #region Shipping price - - /// - /// Gets shipping price - /// - /// Price - /// Customer - /// Price - public virtual decimal GetShippingPrice(decimal price, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetShippingPrice(price, includingTax, customer); - } - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer) - { - decimal taxRate; - return GetShippingPrice(price, includingTax, customer, out taxRate); - } - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate) - { - taxRate = decimal.Zero; - - if (!_taxSettings.ShippingIsTaxable) - { - return price; - } - int taxClassId = _taxSettings.ShippingTaxClassId; - bool priceIncludesTax = _taxSettings.ShippingPriceIncludesTax; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region Payment additional fee - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// Customer - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetPaymentMethodAdditionalFee(price, includingTax, customer); - } - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer) - { - decimal taxRate; - return GetPaymentMethodAdditionalFee(price, includingTax, - customer, out taxRate); - } - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) - { - taxRate = decimal.Zero; - - if (!_taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { - return price; - } - int taxClassId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; - bool priceIncludesTax = _taxSettings.PaymentMethodAdditionalFeeIncludesTax; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region Checkout attribute price - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav) - { - var customer = _workContext.CurrentCustomer; - return GetCheckoutAttributePrice(cav, customer); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Customer - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetCheckoutAttributePrice(cav, includingTax, customer); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer) - { - decimal taxRate; - return GetCheckoutAttributePrice(cav, includingTax, customer, out taxRate); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer, out decimal taxRate) - { - if (cav == null) - throw new ArgumentNullException("cav"); - - taxRate = decimal.Zero; - - decimal price = cav.PriceAdjustment; - if (cav.CheckoutAttribute.IsTaxExempt) - { - return price; - } - - bool priceIncludesTax = _taxSettings.PricesIncludeTax; - int taxClassId = cav.CheckoutAttribute.TaxCategoryId; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region VAT - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber) - { - string name, address; - return GetVatNumberStatus(fullVatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// Name (if received) - /// Address (if received) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, - out string name, out string address) - { - name = string.Empty; - address = string.Empty; - - if (String.IsNullOrWhiteSpace(fullVatNumber)) - return VatNumberStatus.Empty; - fullVatNumber = fullVatNumber.Trim(); - - //GB 111 1111 111 or GB 1111111111 - //more advanced regex - http://codeigniter.com/wiki/European_Vat_Checker - var r = new Regex(@"^(\w{2})(.*)"); - var match = r.Match(fullVatNumber); - if (!match.Success) - return VatNumberStatus.Invalid; - var twoLetterIsoCode = match.Groups[1].Value; - var vatNumber = match.Groups[2].Value; - - return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber) - { - string name, address; - return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// Name (if received) - /// Address (if received) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, - out string name, out string address) - { - name = string.Empty; - address = string.Empty; - - if (String.IsNullOrEmpty(twoLetterIsoCode)) - return VatNumberStatus.Empty; - - if (String.IsNullOrEmpty(vatNumber)) - return VatNumberStatus.Empty; - - if (_taxSettings.EuVatAssumeValid) - return VatNumberStatus.Valid; - - if (!_taxSettings.EuVatUseWebService) - return VatNumberStatus.Unknown; - - Exception exception; - return DoVatCheck(twoLetterIsoCode, vatNumber, out name, out address, out exception); - } - - /// - /// Performs a basic check of a VAT number for validity - /// - /// Two letter ISO code of a country - /// VAT number - /// Company name - /// Address - /// Exception - /// VAT number status - public virtual VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, - out string name, out string address, out Exception exception) - { - name = string.Empty; - address = string.Empty; - - if (vatNumber == null) - vatNumber = string.Empty; - vatNumber = vatNumber.Trim().Replace(" ", ""); - - if (twoLetterIsoCode == null) - twoLetterIsoCode = string.Empty; - if (!String.IsNullOrEmpty(twoLetterIsoCode)) - //The service returns INVALID_INPUT for country codes that are not uppercase. - twoLetterIsoCode = twoLetterIsoCode.ToUpper(); - - EuropaCheckVatService.checkVatService s = null; - - try - { - bool valid; - - s = new EuropaCheckVatService.checkVatService(); - s.checkVat(ref twoLetterIsoCode, ref vatNumber, out valid, out name, out address); - exception = null; - return valid ? VatNumberStatus.Valid : VatNumberStatus.Invalid; - } - catch (Exception ex) - { - name = address = string.Empty; - exception = ex; - return VatNumberStatus.Unknown; - } - finally - { - if (name == null) - name = string.Empty; - - if (address == null) - address = string.Empty; - - if (s != null) - s.Dispose(); - } - } - - #endregion - - #region Exempts - - /// - /// Gets a value indicating whether a product is tax exempt - /// - /// Product - /// Customer - /// A value indicating whether a product is tax exempt - public virtual bool IsTaxExempt(Product product, Customer customer) - { - if (customer != null) - { - if (customer.IsTaxExempt) - return true; - - if (customer.CustomerRoles.Where(cr => cr.Active).Any(cr => cr.TaxExempt)) - return true; - } - - if (product == null) - { - return false; - } - - if (product.IsTaxExempt) - { - return true; - } - - return false; - } - - /// - /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) - /// - /// Address - /// Customer - /// Result - public virtual bool IsVatExempt(Address address, Customer customer) - { - if (!_taxSettings.EuVatEnabled) - return false; - - if (address == null || address.Country == null || customer == null) - return false; - - - if (!address.Country.SubjectToVat) - // VAT not chargeable if shipping outside VAT zone - return true; - - // VAT not chargeable if address, customer and config meet our VAT exemption requirements: - // returns true if this customer is VAT exempt because they are shipping within the EU but outside our shop country, they have supplied a validated VAT number, and the shop is configured to allow VAT exemption - var customerVatStatus = (VatNumberStatus) customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - return address.CountryId != _taxSettings.EuVatShopCountryId && - customerVatStatus == VatNumberStatus.Valid && - _taxSettings.EuVatAllowVatExemption; - } - - #endregion - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Tax; +using Nop.Core.Plugins; +using Nop.Services.Common; +using Nop.Services.Directory; +using Nop.Services.Logging; + +namespace Nop.Services.Tax +{ + /// + /// Tax service + /// + public partial class TaxService : ITaxService + { + #region Fields + + private readonly IAddressService _addressService; + private readonly IWorkContext _workContext; + private readonly TaxSettings _taxSettings; + private readonly IPluginFinder _pluginFinder; + private readonly IGeoLookupService _geoLookupService; + private readonly ICountryService _countryService; + private readonly ILogger _logger; + private readonly CustomerSettings _customerSettings; + private readonly AddressSettings _addressSettings; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Address service + /// Work context + /// Tax settings + /// Plugin finder + /// GEO lookup service + /// Country service + /// Logger service + /// Customer settings + /// Address settings + public TaxService(IAddressService addressService, + IWorkContext workContext, + TaxSettings taxSettings, + IPluginFinder pluginFinder, + IGeoLookupService geoLookupService, + ICountryService countryService, + ILogger logger, + CustomerSettings customerSettings, + AddressSettings addressSettings) + { + this._addressService = addressService; + this._workContext = workContext; + this._taxSettings = taxSettings; + this._pluginFinder = pluginFinder; + this._geoLookupService = geoLookupService; + this._countryService = countryService; + this._logger = logger; + this._customerSettings = customerSettings; + this._addressSettings = addressSettings; + } + + #endregion + + #region Utilities + + /// + /// Get a value indicating whether a customer is consumer (a person, not a company) located in Europe Union + /// + /// Customer + /// Result + protected virtual bool IsEuConsumer(Customer customer) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + Country country = null; + + //get country from billing address + if (_addressSettings.CountryEnabled && customer.BillingAddress != null) + country = customer.BillingAddress.Country; + + //get country specified during registration? + if (country == null && _customerSettings.CountryEnabled) + { + var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); + country = _countryService.GetCountryById(countryId); + } + + //get country by IP address + if (country == null) + { + var ipAddress = customer.LastIpAddress; + //ipAddress = _webHelper.GetCurrentIpAddress(); + var countryIsoCode = _geoLookupService.LookupCountryIsoCode(ipAddress); + country = _countryService.GetCountryByTwoLetterIsoCode(countryIsoCode); + } + + //we cannot detect country + if (country == null) + return false; + + //outside EU + if (!country.SubjectToVat) + return false; + + //company (business) or consumer? + var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + if (customerVatStatus == VatNumberStatus.Valid) + return false; + + //TODO: use specified company name? (both address and registration one) + + //consumer + return true; + } + + /// + /// Create request for tax calculation + /// + /// Product + /// Tax category identifier + /// Customer + /// Price + /// Package for tax calculation + protected virtual CalculateTaxRequest CreateCalculateTaxRequest(Product product, + int taxCategoryId, Customer customer, decimal price) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + var calculateTaxRequest = new CalculateTaxRequest + { + Customer = customer, + Product = product, + Price = price + }; + if (taxCategoryId > 0) + { + calculateTaxRequest.TaxCategoryId = taxCategoryId; + } + else + { + if (product != null) + calculateTaxRequest.TaxCategoryId = product.TaxCategoryId; + } + + var basedOn = _taxSettings.TaxBasedOn; + //new EU VAT rules starting January 1st 2015 + //find more info at http://ec.europa.eu/taxation_customs/taxation/vat/how_vat_works/telecom/index_en.htm#new_rules + //EU VAT enabled? + if (_taxSettings.EuVatEnabled) + { + //telecommunications, broadcasting and electronic services? + if (product != null && product.IsTelecommunicationsOrBroadcastingOrElectronicServices) + { + //January 1st 2015 passed? + if (DateTime.UtcNow > new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + { + //Europe Union consumer? + if (IsEuConsumer(customer)) + { + //We must charge VAT in the EU country where the customer belongs (not where the business is based) + basedOn = TaxBasedOn.BillingAddress; + } + } + } + } + if (basedOn == TaxBasedOn.BillingAddress && customer.BillingAddress == null) + basedOn = TaxBasedOn.DefaultAddress; + if (basedOn == TaxBasedOn.ShippingAddress && customer.ShippingAddress == null) + basedOn = TaxBasedOn.DefaultAddress; + + Address address; + + switch (basedOn) + { + case TaxBasedOn.BillingAddress: + { + address = customer.BillingAddress; + } + break; + case TaxBasedOn.ShippingAddress: + { + address = customer.ShippingAddress; + } + break; + case TaxBasedOn.DefaultAddress: + default: + { + address = _addressService.GetAddressById(_taxSettings.DefaultTaxAddressId); + } + break; + } + + calculateTaxRequest.Address = address; + return calculateTaxRequest; + } + + /// + /// Calculated price + /// + /// Price + /// Percent + /// Increase + /// New price + protected virtual decimal CalculatePrice(decimal price, decimal percent, bool increase) + { + if (percent == decimal.Zero) + return price; + + decimal result; + if (increase) + { + result = price * (1 + percent / 100); + } + else + { + result = price / (1 + percent / 100); //MF 13.11.16: is the same but written in a complicated way: price - (price) / (100 + percent) * percent; + } + return result; + } + + /// + /// Gets tax rate + /// + /// Product + /// Tax category identifier + /// Customer + /// Price (taxable value) + /// Calculated tax rate + /// A value indicating whether a request is taxable + protected virtual void GetTaxRate(Product product, int taxCategoryId, + Customer customer, decimal price, out decimal taxRate, out bool isTaxable) + { + taxRate = decimal.Zero; + isTaxable = true; + + //active tax provider + var activeTaxProvider = LoadActiveTaxProvider(customer); + if (activeTaxProvider == null) + return; + + //tax request + var calculateTaxRequest = CreateCalculateTaxRequest(product, taxCategoryId, customer, price); + + //tax exempt + if (IsTaxExempt(product, calculateTaxRequest.Customer)) + { + isTaxable = false; + } + //make EU VAT exempt validation (the European Union Value Added Tax) + if (isTaxable && + _taxSettings.EuVatEnabled && + IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) + { + //VAT is not chargeable + isTaxable = false; + } + + //get tax rate + var calculateTaxResult = activeTaxProvider.GetTaxRate(calculateTaxRequest); + if (calculateTaxResult.Success) + { + //ensure that tax is equal or greater than zero + if (calculateTaxResult.TaxRate < decimal.Zero) + calculateTaxResult.TaxRate = decimal.Zero; + + taxRate = calculateTaxResult.TaxRate; + } + else + if (_taxSettings.LogErrors) + { + foreach (var error in calculateTaxResult.Errors) + { + _logger.Error(string.Format("{0} - {1}", activeTaxProvider.PluginDescriptor.FriendlyName, error), null, customer); + } + } + } + + #endregion + + #region Methods + + #region Tax providers + + /// + /// Load active tax provider + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Active tax provider + public virtual ITaxProvider LoadActiveTaxProvider(Customer customer = null) + { + var taxProvider = LoadTaxProviderBySystemName(_taxSettings.ActiveTaxProviderSystemName); + if (taxProvider == null) + taxProvider = LoadAllTaxProviders(customer).FirstOrDefault(); + return taxProvider; + } + + /// + /// Load tax provider by system name + /// + /// System name + /// Found tax provider + public virtual ITaxProvider LoadTaxProviderBySystemName(string systemName) + { + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + if (descriptor != null) + return descriptor.Instance(); + + return null; + } + + /// + /// Load all tax providers + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Tax providers + public virtual IList LoadAllTaxProviders(Customer customer = null) + { + return _pluginFinder.GetPlugins(customer: customer).ToList(); + } + + #endregion + + #region Product price + + /// + /// Gets price + /// + /// Product + /// Price + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + out decimal taxRate) + { + var customer = _workContext.CurrentCustomer; + return GetProductPrice(product, price, customer, out taxRate); + } + + /// + /// Gets price + /// + /// Product + /// Price + /// Customer + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + Customer customer, out decimal taxRate) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetProductPrice(product, price, includingTax, customer, out taxRate); + } + + /// + /// Gets price + /// + /// Product + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + bool includingTax, Customer customer, out decimal taxRate) + { + bool priceIncludesTax = _taxSettings.PricesIncludeTax; + int taxCategoryId = 0; + return GetProductPrice(product, taxCategoryId, price, includingTax, + customer, priceIncludesTax, out taxRate); + } + + /// + /// Gets price + /// + /// Product + /// Tax category identifier + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// A value indicating whether price already includes tax + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, int taxCategoryId, + decimal price, bool includingTax, Customer customer, + bool priceIncludesTax, out decimal taxRate) + { + bool isTaxable; + //taxrate should be always passed + GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); + + //no need to calculate tax rate if passed "price" is 0 + if (price == decimal.Zero) + { + return price; + } + + if (priceIncludesTax) + { + //"price" already includes tax + if (includingTax) + { + //we should calculate price WITH tax + if (!isTaxable) + { + //but our request is not taxable + //hence we should calculate price WITHOUT tax + price = CalculatePrice(price, taxRate, false); + } + } + else + { + //we should calculate price WITHOUT tax + price = CalculatePrice(price, taxRate, false); + } + } + else + { + //"price" doesn't include tax + if (includingTax) + { + //we should calculate price WITH tax + //do it only when price is taxable + if (isTaxable) + { + price = CalculatePrice(price, taxRate, true); + } + } + } + + + if (!isTaxable) + { + //we return 0% tax rate in case a request is not taxable + taxRate = decimal.Zero; + } + + //allowed to support negative price adjustments + //if (price < decimal.Zero) + // price = decimal.Zero; + + return price; + } + + #endregion + + #region Shipping price + + /// + /// Gets shipping price + /// + /// Price + /// Customer + /// Price + public virtual decimal GetShippingPrice(decimal price, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetShippingPrice(price, includingTax, customer); + } + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer) + { + decimal taxRate; + return GetShippingPrice(price, includingTax, customer, out taxRate); + } + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate) + { + taxRate = decimal.Zero; + + if (!_taxSettings.ShippingIsTaxable) + { + return price; + } + int taxClassId = _taxSettings.ShippingTaxClassId; + bool priceIncludesTax = _taxSettings.ShippingPriceIncludesTax; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate); + } + + #endregion + + #region Payment additional fee + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// Customer + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetPaymentMethodAdditionalFee(price, includingTax, customer); + } + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer) + { + decimal taxRate; + return GetPaymentMethodAdditionalFee(price, includingTax, + customer, out taxRate); + } + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) + { + taxRate = decimal.Zero; + + if (!_taxSettings.PaymentMethodAdditionalFeeIsTaxable) + { + return price; + } + int taxClassId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; + bool priceIncludesTax = _taxSettings.PaymentMethodAdditionalFeeIncludesTax; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate); + } + + #endregion + + #region Checkout attribute price + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav) + { + var customer = _workContext.CurrentCustomer; + return GetCheckoutAttributePrice(cav, customer); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Customer + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetCheckoutAttributePrice(cav, includingTax, customer); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer) + { + decimal taxRate; + return GetCheckoutAttributePrice(cav, includingTax, customer, out taxRate); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer, out decimal taxRate) + { + if (cav == null) + throw new ArgumentNullException("cav"); + + taxRate = decimal.Zero; + + decimal price = cav.PriceAdjustment; + if (cav.CheckoutAttribute.IsTaxExempt) + { + return price; + } + + bool priceIncludesTax = _taxSettings.PricesIncludeTax; + int taxClassId = cav.CheckoutAttribute.TaxCategoryId; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate); + } + + #endregion + + #region VAT + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber) + { + string name, address; + return GetVatNumberStatus(fullVatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// Name (if received) + /// Address (if received) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, + out string name, out string address) + { + name = string.Empty; + address = string.Empty; + + if (String.IsNullOrWhiteSpace(fullVatNumber)) + return VatNumberStatus.Empty; + fullVatNumber = fullVatNumber.Trim(); + + //GB 111 1111 111 or GB 1111111111 + //more advanced regex - http://codeigniter.com/wiki/European_Vat_Checker + var r = new Regex(@"^(\w{2})(.*)"); + var match = r.Match(fullVatNumber); + if (!match.Success) + return VatNumberStatus.Invalid; + var twoLetterIsoCode = match.Groups[1].Value; + var vatNumber = match.Groups[2].Value; + + return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber) + { + string name, address; + return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// Name (if received) + /// Address (if received) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, + out string name, out string address) + { + name = string.Empty; + address = string.Empty; + + if (String.IsNullOrEmpty(twoLetterIsoCode)) + return VatNumberStatus.Empty; + + if (String.IsNullOrEmpty(vatNumber)) + return VatNumberStatus.Empty; + + if (_taxSettings.EuVatAssumeValid) + return VatNumberStatus.Valid; + + if (!_taxSettings.EuVatUseWebService) + return VatNumberStatus.Unknown; + + Exception exception; + return DoVatCheck(twoLetterIsoCode, vatNumber, out name, out address, out exception); + } + + /// + /// Performs a basic check of a VAT number for validity + /// + /// Two letter ISO code of a country + /// VAT number + /// Company name + /// Address + /// Exception + /// VAT number status + public virtual VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, + out string name, out string address, out Exception exception) + { + name = string.Empty; + address = string.Empty; + + if (vatNumber == null) + vatNumber = string.Empty; + vatNumber = vatNumber.Trim().Replace(" ", ""); + + if (twoLetterIsoCode == null) + twoLetterIsoCode = string.Empty; + if (!String.IsNullOrEmpty(twoLetterIsoCode)) + //The service returns INVALID_INPUT for country codes that are not uppercase. + twoLetterIsoCode = twoLetterIsoCode.ToUpper(); + + EuropaCheckVatService.checkVatService s = null; + + try + { + bool valid; + + s = new EuropaCheckVatService.checkVatService(); + s.checkVat(ref twoLetterIsoCode, ref vatNumber, out valid, out name, out address); + exception = null; + return valid ? VatNumberStatus.Valid : VatNumberStatus.Invalid; + } + catch (Exception ex) + { + name = address = string.Empty; + exception = ex; + return VatNumberStatus.Unknown; + } + finally + { + if (name == null) + name = string.Empty; + + if (address == null) + address = string.Empty; + + if (s != null) + s.Dispose(); + } + } + + #endregion + + #region Exempts + + /// + /// Gets a value indicating whether a product is tax exempt + /// + /// Product + /// Customer + /// A value indicating whether a product is tax exempt + public virtual bool IsTaxExempt(Product product, Customer customer) + { + if (customer != null) + { + if (customer.IsTaxExempt) + return true; + + if (customer.CustomerRoles.Where(cr => cr.Active).Any(cr => cr.TaxExempt)) + return true; + } + + if (product == null) + { + return false; + } + + if (product.IsTaxExempt) + { + return true; + } + + return false; + } + + /// + /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) + /// + /// Address + /// Customer + /// Result + public virtual bool IsVatExempt(Address address, Customer customer) + { + if (!_taxSettings.EuVatEnabled) + return false; + + if (address == null || address.Country == null || customer == null) + return false; + + + if (!address.Country.SubjectToVat) + // VAT not chargeable if shipping outside VAT zone + return true; + + // VAT not chargeable if address, customer and config meet our VAT exemption requirements: + // returns true if this customer is VAT exempt because they are shipping within the EU but outside our shop country, they have supplied a validated VAT number, and the shop is configured to allow VAT exemption + var customerVatStatus = (VatNumberStatus) customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + return address.CountryId != _taxSettings.EuVatShopCountryId && + customerVatStatus == VatNumberStatus.Valid && + _taxSettings.EuVatAllowVatExemption; + } + + #endregion + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Tax/TaxSummary.cs b/src/Libraries/Nop.Services/Tax/TaxSummary.cs index 87213341666..440e9077875 100644 --- a/src/Libraries/Nop.Services/Tax/TaxSummary.cs +++ b/src/Libraries/Nop.Services/Tax/TaxSummary.cs @@ -1,23 +1,28 @@  using Nop.Services.Catalog; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text; namespace Nop.Services.Tax { /// /// TaxRate entry to store amounts per vatrate - /// all value are per rate and most of them calculated + /// all values are per rate and most of them calculated /// only VatRate, ItemAmount, ShippingAmount and PaymentFeeAmount can be set /// public partial class TaxRateEntry { public decimal VatRate { get; set; } + public decimal VatRateWeight { get; internal set; } //weight of VatRate used for product sets [it's (sum of associated products price per VatRate) / (total price of assoc. prod.) ] public decimal SubtotalAmount { get; set; } public decimal ShippingAmount { get; set; } public decimal PaymentFeeAmount { get; set; } //can be negative when discounted + public decimal Amount { get; internal set; } //sum of above amounts public decimal SubTotalDiscAmount { get; internal set; } public decimal InvoiceDiscountAmount { get; internal set; } + public decimal DiscountAmount { get; internal set; } //sum of above discounts public decimal BaseAmount { get; internal set; } public decimal VatAmount { get; internal set; } public decimal AmountIncludingVAT { get; internal set; } @@ -68,6 +73,19 @@ public TaxSummary(bool includingTax) private bool HasChanged = false; #region utilities + public string GenerateTaxRateString() + { + return this.TaxRates.Aggregate(string.Empty, (current, next) => + string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, + next.Key.ToString(CultureInfo.InvariantCulture), + next.Value.Amount.ToString(CultureInfo.InvariantCulture), + next.Value.DiscountAmount.ToString(CultureInfo.InvariantCulture), + next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), + next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), + next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) + ) + ); + } public SortedDictionary GenerateOldTaxrateDict() { return new SortedDictionary(TaxRates.ToDictionary(x => x.Key, x => x.Value.VatAmount)); @@ -101,7 +119,51 @@ public void AddRate(decimal vatRate, decimal itemAmount = 0, decimal shippingAmo } HasChanged = true; } + /// + /// + /// + /// Total Amount of Attributes + /// dictionary of taxRate weights on that amount + public void AddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + { + var dummyResult = ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: true); + } + + public SortedDictionary ParseAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + { + return ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: false); + } + public SortedDictionary ParseOrAddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight, bool doAdd = true) + { + var result = new SortedDictionary(); + int i = 0; + int c = attributeTaxWeight.Count(); + decimal reminder = attribAmount; + foreach (KeyValuePair kvp in attributeTaxWeight) + { + i += 1; + decimal vatpercentage = kvp.Key; + decimal rateWeight = kvp.Value; + var attribAmountWeighted = RoundingHelper.RoundAmount(attribAmount * rateWeight); + if (i < c) + { + if (doAdd) + AddRate(vatpercentage, attribAmountWeighted); + else + result.Add(kvp.Key, attribAmountWeighted); + reminder -= attribAmountWeighted; + } + else + { + if (doAdd) + AddRate(vatpercentage, reminder); + else + result.Add(kvp.Key, reminder); + } + } + return result; + } public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal totalAmount = 0) { if (totalAmount == 0) @@ -120,7 +182,7 @@ public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal total /// Base amount to which apply fee public void SetPaymentFeeDiscAmount(decimal totalPaymentFeeDiscAmount, decimal totalAmount) { - PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; + PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; HasChanged = true; } @@ -192,8 +254,11 @@ public void CalculateAmounts() //last remainder get's lost as it can't be considered anywhere else. This has no implication and only lowers or highers discount. + taxrate.Amount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount; + taxrate.DiscountAmount = taxrate.SubTotalDiscAmount + taxrate.InvoiceDiscountAmount; + //VAT: always round VAT first - decimal rateamount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount - taxrate.SubTotalDiscAmount - taxrate.InvoiceDiscountAmount; + decimal rateamount = taxrate.Amount - taxrate.DiscountAmount; if (PricesIncludeTax) { taxrate.AmountIncludingVAT = rateamount; @@ -221,7 +286,24 @@ public void CalculateAmounts() TotalAmount += taxrate.BaseAmount; TotalAmountVAT += taxrate.VatAmount; TotalAmountIncludingVAT += taxrate.AmountIncludingVAT; - + } + int i = 0; + int c = TaxRates.Count(); + decimal totWeight = decimal.Zero; + foreach (KeyValuePair kvp in this.TaxRates) + { + i++; + decimal vatpercentage = kvp.Key; + TaxRateEntry taxrate = kvp.Value; + if (i < c) + { + taxrate.VatRateWeight = (this.PricesIncludeTax ? (taxrate.AmountIncludingVAT / this.TotalAmountIncludingVAT) : (taxrate.BaseAmount / this.TotalAmount)); + totWeight += taxrate.VatRateWeight; + } + else + { + taxrate.VatRateWeight = decimal.One - totWeight; //assure sum of VatRateWeight = 1 + } } } #endregion diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index cf96f8feb5f..5282967b23a 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -1,4420 +1,4486 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Web.Mvc; -using Nop.Admin.Extensions; -using Nop.Admin.Helpers; -using Nop.Admin.Models.Orders; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; -using Nop.Services; -using Nop.Services.Affiliates; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Directory; -using Nop.Services.Discounts; -using Nop.Services.ExportImport; -using Nop.Services.Helpers; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Media; -using Nop.Services.Messages; -using Nop.Services.Orders; -using Nop.Services.Payments; -using Nop.Services.Security; -using Nop.Services.Shipping; -using Nop.Services.Shipping.Tracking; -using Nop.Services.Stores; -using Nop.Services.Tax; -using Nop.Services.Vendors; -using Nop.Web.Framework.Controllers; -using Nop.Web.Framework.Kendoui; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Controllers -{ - public partial class OrderController : BaseAdminController - { - #region Fields - - private readonly IOrderService _orderService; - private readonly IOrderReportService _orderReportService; - private readonly IOrderProcessingService _orderProcessingService; - private readonly IReturnRequestService _returnRequestService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly ITaxService _taxService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly IPriceFormatter _priceFormatter; - private readonly IDiscountService _discountService; - private readonly ILocalizationService _localizationService; - private readonly IWorkContext _workContext; - private readonly ICurrencyService _currencyService; - private readonly IEncryptionService _encryptionService; - private readonly IPaymentService _paymentService; - private readonly IMeasureService _measureService; - private readonly IPdfService _pdfService; - private readonly IAddressService _addressService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly IProductService _productService; - private readonly IExportManager _exportManager; - private readonly IPermissionService _permissionService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly IProductAttributeService _productAttributeService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductAttributeFormatter _productAttributeFormatter; - private readonly IShoppingCartService _shoppingCartService; - private readonly IGiftCardService _giftCardService; - private readonly IDownloadService _downloadService; - private readonly IShipmentService _shipmentService; - private readonly IShippingService _shippingService; - private readonly IStoreService _storeService; - private readonly IVendorService _vendorService; - private readonly IAddressAttributeParser _addressAttributeParser; - private readonly IAddressAttributeService _addressAttributeService; - private readonly IAddressAttributeFormatter _addressAttributeFormatter; - private readonly IAffiliateService _affiliateService; - private readonly IPictureService _pictureService; - private readonly ICustomerActivityService _customerActivityService; - private readonly ICacheManager _cacheManager; - - private readonly OrderSettings _orderSettings; - private readonly CurrencySettings _currencySettings; - private readonly TaxSettings _taxSettings; - private readonly MeasureSettings _measureSettings; - private readonly AddressSettings _addressSettings; - private readonly ShippingSettings _shippingSettings; - - #endregion - - #region Ctor - - public OrderController(IOrderService orderService, - IOrderReportService orderReportService, - IOrderProcessingService orderProcessingService, - IReturnRequestService returnRequestService, - IPriceCalculationService priceCalculationService, - ITaxService taxService, - IDateTimeHelper dateTimeHelper, - IPriceFormatter priceFormatter, - IDiscountService discountService, - ILocalizationService localizationService, - IWorkContext workContext, - ICurrencyService currencyService, - IEncryptionService encryptionService, - IPaymentService paymentService, - IMeasureService measureService, - IPdfService pdfService, - IAddressService addressService, - ICountryService countryService, - IStateProvinceService stateProvinceService, - IProductService productService, - IExportManager exportManager, - IPermissionService permissionService, - IWorkflowMessageService workflowMessageService, - ICategoryService categoryService, - IManufacturerService manufacturerService, - IProductAttributeService productAttributeService, - IProductAttributeParser productAttributeParser, - IProductAttributeFormatter productAttributeFormatter, - IShoppingCartService shoppingCartService, - IGiftCardService giftCardService, - IDownloadService downloadService, - IShipmentService shipmentService, - IShippingService shippingService, - IStoreService storeService, - IVendorService vendorService, - IAddressAttributeParser addressAttributeParser, - IAddressAttributeService addressAttributeService, - IAddressAttributeFormatter addressAttributeFormatter, - IAffiliateService affiliateService, - IPictureService pictureService, - ICustomerActivityService customerActivityService, - ICacheManager cacheManager, - OrderSettings orderSettings, - CurrencySettings currencySettings, - TaxSettings taxSettings, - MeasureSettings measureSettings, - AddressSettings addressSettings, - ShippingSettings shippingSettings) - { - this._orderService = orderService; - this._orderReportService = orderReportService; - this._orderProcessingService = orderProcessingService; - this._returnRequestService = returnRequestService; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; - this._dateTimeHelper = dateTimeHelper; - this._priceFormatter = priceFormatter; - this._discountService = discountService; - this._localizationService = localizationService; - this._workContext = workContext; - this._currencyService = currencyService; - this._encryptionService = encryptionService; - this._paymentService = paymentService; - this._measureService = measureService; - this._pdfService = pdfService; - this._addressService = addressService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._productService = productService; - this._exportManager = exportManager; - this._permissionService = permissionService; - this._workflowMessageService = workflowMessageService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._productAttributeFormatter = productAttributeFormatter; - this._shoppingCartService = shoppingCartService; - this._giftCardService = giftCardService; - this._downloadService = downloadService; - this._shipmentService = shipmentService; - this._shippingService = shippingService; - this._storeService = storeService; - this._vendorService = vendorService; - this._addressAttributeParser = addressAttributeParser; - this._addressAttributeService = addressAttributeService; - this._addressAttributeFormatter = addressAttributeFormatter; - this._affiliateService = affiliateService; - this._pictureService = pictureService; - this._customerActivityService = customerActivityService; - this._cacheManager = cacheManager; - this._orderSettings = orderSettings; - this._currencySettings = currencySettings; - this._taxSettings = taxSettings; - this._measureSettings = measureSettings; - this._addressSettings = addressSettings; - this._shippingSettings = shippingSettings; - } - - #endregion - - #region Utilities - - [NonAction] - protected virtual bool HasAccessToOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (_workContext.CurrentVendor == null) - //not a vendor; has access - return true; - - var vendorId = _workContext.CurrentVendor.Id; - var hasVendorProducts = order.OrderItems.Any(orderItem => orderItem.Product.VendorId == vendorId); - return hasVendorProducts; - } - - [NonAction] - protected virtual bool HasAccessToOrderItem(OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); - - if (_workContext.CurrentVendor == null) - //not a vendor; has access - return true; - - var vendorId = _workContext.CurrentVendor.Id; - return orderItem.Product.VendorId == vendorId; - } - - [NonAction] - protected virtual bool HasAccessToProduct(Product product) - { - if (product == null) - throw new ArgumentNullException("product"); - - if (_workContext.CurrentVendor == null) - //not a vendor; has access - return true; - - var vendorId = _workContext.CurrentVendor.Id; - return product.VendorId == vendorId; - } - - [NonAction] - protected virtual bool HasAccessToShipment(Shipment shipment) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - if (_workContext.CurrentVendor == null) - //not a vendor; has access - return true; - - var hasVendorProducts = false; - var vendorId = _workContext.CurrentVendor.Id; - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem != null) - { - if (orderItem.Product.VendorId == vendorId) - { - hasVendorProducts = true; - break; - } - } - } - return hasVendorProducts; - } - - /// - /// Parse product attributes on the add product to order details page - /// - /// Product - /// Form - /// Parsed attributes - [NonAction] - protected virtual string ParseProductAttributes(Product product, FormCollection form) - { - var attributesXml = string.Empty; - - #region Product attributes - - var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - foreach (var attribute in productAttributes) - { - var controlId = string.Format("product_attribute_{0}", attribute.Id); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - int selectedAttributeId = int.Parse(ctrlAttributes); - if (selectedAttributeId > 0) - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.Checkboxes: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - foreach (var item in ctrlAttributes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - } - break; - case AttributeControlType.ReadonlyCheckboxes: - { - //load read-only (already server-side selected) values - var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); - foreach (var selectedAttributeId in attributeValues - .Where(v => v.IsPreSelected) - .Select(v => v.Id) - .ToList()) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - string enteredText = ctrlAttributes.Trim(); - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, enteredText); - } - } - break; - case AttributeControlType.Datepicker: - { - var day = form[controlId + "_day"]; - var month = form[controlId + "_month"]; - var year = form[controlId + "_year"]; - DateTime? selectedDate = null; - try - { - selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day)); - } - catch { } - if (selectedDate.HasValue) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedDate.Value.ToString("D")); - } - } - break; - case AttributeControlType.FileUpload: - { - Guid downloadGuid; - Guid.TryParse(form[controlId], out downloadGuid); - var download = _downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, download.DownloadGuid.ToString()); - } - } - break; - default: - break; - } - } - //validate conditional attributes (if specified) - foreach (var attribute in productAttributes) - { - var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributesXml); - if (conditionMet.HasValue && !conditionMet.Value) - { - attributesXml = _productAttributeParser.RemoveProductAttribute(attributesXml, attribute); - } - } - - #endregion - - return attributesXml; - } - - /// - /// Parse rental dates on the add product to order details page - /// - /// Form - /// Start date - /// End date - [NonAction] - protected virtual void ParseRentalDates(FormCollection form, - out DateTime? startDate, out DateTime? endDate) - { - startDate = null; - endDate = null; - - var ctrlStartDate = form["rental_start_date"]; - var ctrlEndDate = form["rental_end_date"]; - try - { - const string datePickerFormat = "MM/dd/yyyy"; - startDate = DateTime.ParseExact(ctrlStartDate, datePickerFormat, CultureInfo.InvariantCulture); - endDate = DateTime.ParseExact(ctrlEndDate, datePickerFormat, CultureInfo.InvariantCulture); - } - catch - { - } - } - - [NonAction] - protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (model == null) - throw new ArgumentNullException("model"); - - model.Id = order.Id; - model.InvoiceId = order.InvoiceId; - model.InvoiceDateUtc = order.InvoiceDateUtc; - model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); - model.OrderStatusId = order.OrderStatusId; - model.OrderGuid = order.OrderGuid; - var store = _storeService.GetStoreById(order.StoreId); - model.StoreName = store != null ? store.Name : "Unknown"; - model.CustomerId = order.CustomerId; - var customer = order.Customer; - model.CustomerInfo = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); - model.CustomerIp = order.CustomerIp; - model.VatNumber = order.VatNumber; - model.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); - model.AllowCustomersToSelectTaxDisplayType = _taxSettings.AllowCustomersToSelectTaxDisplayType; - model.TaxDisplayType = _taxSettings.TaxDisplayType; - - var affiliate = _affiliateService.GetAffiliateById(order.AffiliateId); - if (affiliate != null) - { - model.AffiliateId = affiliate.Id; - model.AffiliateName = affiliate.GetFullName(); - } - - //a vendor should have access only to his products - model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; - //custom values - model.CustomValues = order.DeserializeCustomValues(); - - #region Order totals - - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); - if (primaryStoreCurrency == null) - throw new Exception("Cannot load primary store currency"); - - //subtotal - model.OrderSubtotalInclTax = _priceFormatter.FormatPrice(order.OrderSubtotalInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); - model.OrderSubtotalExclTax = _priceFormatter.FormatPrice(order.OrderSubtotalExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); - model.OrderSubtotalInclTaxValue = order.OrderSubtotalInclTax; - model.OrderSubtotalExclTaxValue = order.OrderSubtotalExclTax; - //discount (applied to order subtotal) - string orderSubtotalDiscountInclTaxStr = _priceFormatter.FormatPrice(order.OrderSubTotalDiscountInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); - string orderSubtotalDiscountExclTaxStr = _priceFormatter.FormatPrice(order.OrderSubTotalDiscountExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); - if (order.OrderSubTotalDiscountInclTax > decimal.Zero) - model.OrderSubTotalDiscountInclTax = orderSubtotalDiscountInclTaxStr; - if (order.OrderSubTotalDiscountExclTax > decimal.Zero) - model.OrderSubTotalDiscountExclTax = orderSubtotalDiscountExclTaxStr; - model.OrderSubTotalDiscountInclTaxValue = order.OrderSubTotalDiscountInclTax; - model.OrderSubTotalDiscountExclTaxValue = order.OrderSubTotalDiscountExclTax; - - //shipping - model.OrderShippingInclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); - model.OrderShippingExclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); - model.OrderShippingInclTaxValue = order.OrderShippingInclTax; - model.OrderShippingExclTaxValue = order.OrderShippingExclTax; - - //payment method additional fee - if (order.PaymentMethodAdditionalFeeInclTax > decimal.Zero) - { - model.PaymentMethodAdditionalFeeInclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); - model.PaymentMethodAdditionalFeeExclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); - } - model.PaymentMethodAdditionalFeeInclTaxValue = order.PaymentMethodAdditionalFeeInclTax; - model.PaymentMethodAdditionalFeeExclTaxValue = order.PaymentMethodAdditionalFeeExclTax; - - - //tax - model.Tax = _priceFormatter.FormatPrice(order.OrderTax, true, false); - SortedDictionary taxRates = order.TaxRatesDictionary; - bool displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); - bool displayTax = !displayTaxRates; - foreach (var tr in order.TaxRatesDictionary) - { - model.TaxRates.Add(new OrderModel.TaxRate - { - Rate = _priceFormatter.FormatTaxRate(tr.Key), - Amount = _priceFormatter.FormatPrice(tr.Value.Amount, true, false), - DiscountAmount = _priceFormatter.FormatPrice(tr.Value.DiscountAmount, true, false), - BaseAmount = _priceFormatter.FormatPrice(tr.Value.BaseAmount, true, false), - VatAmount = _priceFormatter.FormatPrice(tr.Value.VatAmount, true, false), - }); - } - model.DisplayTaxRates = displayTaxRates; - model.DisplayTax = displayTax; - model.TaxValue = order.OrderTax; - model.TaxRatesValue = order.TaxRates; - - //discount - if (order.OrderDiscount > 0) - model.OrderTotalDiscount = _priceFormatter.FormatPrice(-order.OrderDiscount, true, false); - model.OrderTotalDiscountValue = order.OrderDiscount; - - //gift cards - foreach (var gcuh in order.GiftCardUsageHistory) - { - model.GiftCards.Add(new OrderModel.GiftCard - { - CouponCode = gcuh.GiftCard.GiftCardCouponCode, - Amount = _priceFormatter.FormatPrice(-gcuh.UsedValue, true, false), - }); - } - - //reward points - if (order.RedeemedRewardPointsEntry != null) - { - model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; - model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-order.RedeemedRewardPointsEntry.UsedAmount, true, false); - } - - //total - model.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); - model.OrderTotalValue = order.OrderTotal; - model.OrderAmount = _priceFormatter.FormatPrice(order.OrderAmount, true, false); - model.OrderAmountValue = order.OrderAmount; - model.OrderAmountIncl = _priceFormatter.FormatPrice(order.OrderAmountIncl, true, false); - model.OrderAmountInclValue = order.OrderAmountIncl; - - //refunded amount - if (order.RefundedAmount > decimal.Zero) - model.RefundedAmount = _priceFormatter.FormatPrice(order.RefundedAmount, true, false); - - //used discounts - var duh = _discountService.GetAllDiscountUsageHistory(orderId: order.Id); - foreach (var d in duh) - { - model.UsedDiscounts.Add(new OrderModel.UsedDiscountModel - { - DiscountId = d.DiscountId, - DiscountName = d.Discount.Name - }); - } - - //profit (hide for vendors) - if (_workContext.CurrentVendor == null) - { - var profit = _orderReportService.ProfitReport(orderId: order.Id); - model.Profit = _priceFormatter.FormatPrice(profit, true, false); - } - - #endregion - - #region Payment info - - if (order.AllowStoringCreditCardNumber) - { - //card type - model.CardType = _encryptionService.DecryptText(order.CardType); - //cardholder name - model.CardName = _encryptionService.DecryptText(order.CardName); - //card number - model.CardNumber = _encryptionService.DecryptText(order.CardNumber); - //cvv - model.CardCvv2 = _encryptionService.DecryptText(order.CardCvv2); - //expiry date - string cardExpirationMonthDecrypted = _encryptionService.DecryptText(order.CardExpirationMonth); - if (!String.IsNullOrEmpty(cardExpirationMonthDecrypted) && cardExpirationMonthDecrypted != "0") - model.CardExpirationMonth = cardExpirationMonthDecrypted; - string cardExpirationYearDecrypted = _encryptionService.DecryptText(order.CardExpirationYear); - if (!String.IsNullOrEmpty(cardExpirationYearDecrypted) && cardExpirationYearDecrypted != "0") - model.CardExpirationYear = cardExpirationYearDecrypted; - - model.AllowStoringCreditCardNumber = true; - } - else - { - string maskedCreditCardNumberDecrypted = _encryptionService.DecryptText(order.MaskedCreditCardNumber); - if (!String.IsNullOrEmpty(maskedCreditCardNumberDecrypted)) - model.CardNumber = maskedCreditCardNumberDecrypted; - } - - - //payment transaction info - model.AuthorizationTransactionId = order.AuthorizationTransactionId; - model.CaptureTransactionId = order.CaptureTransactionId; - model.SubscriptionTransactionId = order.SubscriptionTransactionId; - - //payment method info - var pm = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - model.PaymentMethod = pm != null ? pm.PluginDescriptor.FriendlyName : order.PaymentMethodSystemName; - model.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); - - //payment method buttons - model.CanCancelOrder = _orderProcessingService.CanCancelOrder(order); - model.CanCapture = _orderProcessingService.CanCapture(order); - model.CanMarkOrderAsPaid = _orderProcessingService.CanMarkOrderAsPaid(order); - model.CanRefund = _orderProcessingService.CanRefund(order); - model.CanRefundOffline = _orderProcessingService.CanRefundOffline(order); - model.CanPartiallyRefund = _orderProcessingService.CanPartiallyRefund(order, decimal.Zero); - model.CanPartiallyRefundOffline = _orderProcessingService.CanPartiallyRefundOffline(order, decimal.Zero); - model.CanVoid = _orderProcessingService.CanVoid(order); - model.CanVoidOffline = _orderProcessingService.CanVoidOffline(order); - - model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; - model.MaxAmountToRefund = order.OrderTotal - order.RefundedAmount; - - //recurring payment record - var recurringPayment = _orderService.SearchRecurringPayments(initialOrderId: order.Id, showHidden: true).FirstOrDefault(); - if (recurringPayment != null) - { - model.RecurringPaymentId = recurringPayment.Id; - } - #endregion - - #region Billing & shipping info - - model.BillingAddress = order.BillingAddress.ToModel(); - model.BillingAddress.FormattedCustomAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.BillingAddress.CustomAttributes); - model.BillingAddress.FirstNameEnabled = true; - model.BillingAddress.FirstNameRequired = true; - model.BillingAddress.LastNameEnabled = true; - model.BillingAddress.LastNameRequired = true; - model.BillingAddress.EmailEnabled = true; - model.BillingAddress.EmailRequired = true; - model.BillingAddress.CompanyEnabled = _addressSettings.CompanyEnabled; - model.BillingAddress.CompanyRequired = _addressSettings.CompanyRequired; - model.BillingAddress.CountryEnabled = _addressSettings.CountryEnabled; - model.BillingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled - model.BillingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; - model.BillingAddress.CityEnabled = _addressSettings.CityEnabled; - model.BillingAddress.CityRequired = _addressSettings.CityRequired; - model.BillingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; - model.BillingAddress.StreetAddressRequired = _addressSettings.StreetAddressRequired; - model.BillingAddress.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; - model.BillingAddress.StreetAddress2Required = _addressSettings.StreetAddress2Required; - model.BillingAddress.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; - model.BillingAddress.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; - model.BillingAddress.PhoneEnabled = _addressSettings.PhoneEnabled; - model.BillingAddress.PhoneRequired = _addressSettings.PhoneRequired; - model.BillingAddress.FaxEnabled = _addressSettings.FaxEnabled; - model.BillingAddress.FaxRequired = _addressSettings.FaxRequired; - - model.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); ; - if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) - { - model.IsShippable = true; - - model.PickUpInStore = order.PickUpInStore; - if (!order.PickUpInStore) - { - model.ShippingAddress = order.ShippingAddress.ToModel(); - model.ShippingAddress.FormattedCustomAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); - model.ShippingAddress.FirstNameEnabled = true; - model.ShippingAddress.FirstNameRequired = true; - model.ShippingAddress.LastNameEnabled = true; - model.ShippingAddress.LastNameRequired = true; - model.ShippingAddress.EmailEnabled = true; - model.ShippingAddress.EmailRequired = true; - model.ShippingAddress.CompanyEnabled = _addressSettings.CompanyEnabled; - model.ShippingAddress.CompanyRequired = _addressSettings.CompanyRequired; - model.ShippingAddress.CountryEnabled = _addressSettings.CountryEnabled; - model.ShippingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled - model.ShippingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; - model.ShippingAddress.CityEnabled = _addressSettings.CityEnabled; - model.ShippingAddress.CityRequired = _addressSettings.CityRequired; - model.ShippingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; - model.ShippingAddress.StreetAddressRequired = _addressSettings.StreetAddressRequired; - model.ShippingAddress.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; - model.ShippingAddress.StreetAddress2Required = _addressSettings.StreetAddress2Required; - model.ShippingAddress.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; - model.ShippingAddress.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; - model.ShippingAddress.PhoneEnabled = _addressSettings.PhoneEnabled; - model.ShippingAddress.PhoneRequired = _addressSettings.PhoneRequired; - model.ShippingAddress.FaxEnabled = _addressSettings.FaxEnabled; - model.ShippingAddress.FaxRequired = _addressSettings.FaxRequired; - - model.ShippingAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", Server.UrlEncode(order.ShippingAddress.Address1 + " " + order.ShippingAddress.ZipPostalCode + " " + order.ShippingAddress.City + " " + (order.ShippingAddress.Country != null ? order.ShippingAddress.Country.Name : ""))); - } - else - { - if (order.PickupAddress != null) - { - model.PickupAddress = order.PickupAddress.ToModel(); - model.PickupAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", - Server.UrlEncode(string.Format("{0} {1} {2} {3}", order.PickupAddress.Address1, order.PickupAddress.ZipPostalCode, order.PickupAddress.City, - order.PickupAddress.Country != null ? order.PickupAddress.Country.Name : string.Empty))); - } - } - model.ShippingMethod = order.ShippingMethod; - - model.CanAddNewShipments = order.HasItemsToAddToShipment(); - } - - #endregion - - #region Products - - model.CheckoutAttributeInfo = order.CheckoutAttributeDescription; - bool hasDownloadableItems = false; - var products = order.OrderItems; - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - products = products - .Where(orderItem => orderItem.Product.VendorId == _workContext.CurrentVendor.Id) - .ToList(); - } - foreach (var orderItem in products) - { - if (orderItem.Product.IsDownload) - hasDownloadableItems = true; - - var orderItemModel = new OrderModel.OrderItemModel - { - Id = orderItem.Id, - ProductId = orderItem.ProductId, - ProductName = orderItem.Product.Name, - Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), - Quantity = orderItem.Quantity, - IsDownload = orderItem.Product.IsDownload, - DownloadCount = orderItem.DownloadCount, - DownloadActivationType = orderItem.Product.DownloadActivationType, - IsDownloadActivated = orderItem.IsDownloadActivated, - VatRate = orderItem.VatRate - }; - //picture - var orderItemPicture = orderItem.Product.GetProductPicture(orderItem.AttributesXml, _pictureService, _productAttributeParser); - orderItemModel.PictureThumbnailUrl = _pictureService.GetPictureUrl(orderItemPicture, 75, true); - - //license file - if (orderItem.LicenseDownloadId.HasValue) - { - var licenseDownload = _downloadService.GetDownloadById(orderItem.LicenseDownloadId.Value); - if (licenseDownload != null) - { - orderItemModel.LicenseDownloadGuid = licenseDownload.DownloadGuid; - } - } - //vendor - var vendor = _vendorService.GetVendorById(orderItem.Product.VendorId); - orderItemModel.VendorName = vendor != null ? vendor.Name : ""; - - //unit price - orderItemModel.UnitPriceInclTaxValue = orderItem.UnitPriceInclTax; - orderItemModel.UnitPriceExclTaxValue = orderItem.UnitPriceExclTax; - orderItemModel.UnitPriceInclTax = _priceFormatter.FormatPrice(orderItem.UnitPriceInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); - orderItemModel.UnitPriceExclTax = _priceFormatter.FormatPrice(orderItem.UnitPriceExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); - //discounts - orderItemModel.DiscountInclTaxValue = orderItem.DiscountAmountInclTax; - orderItemModel.DiscountExclTaxValue = orderItem.DiscountAmountExclTax; - orderItemModel.DiscountInclTax = _priceFormatter.FormatPrice(orderItem.DiscountAmountInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); - orderItemModel.DiscountExclTax = _priceFormatter.FormatPrice(orderItem.DiscountAmountExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); - //subtotal - orderItemModel.SubTotalInclTaxValue = orderItem.PriceInclTax; - orderItemModel.SubTotalExclTaxValue = orderItem.PriceExclTax; - orderItemModel.SubTotalInclTax = _priceFormatter.FormatPrice(orderItem.PriceInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); - orderItemModel.SubTotalExclTax = _priceFormatter.FormatPrice(orderItem.PriceExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); - - orderItemModel.AttributeInfo = orderItem.AttributeDescription; - if (orderItem.Product.IsRecurring) - orderItemModel.RecurringInfo = string.Format(_localizationService.GetResource("Admin.Orders.Products.RecurringPeriod"), orderItem.Product.RecurringCycleLength, orderItem.Product.RecurringCyclePeriod.GetLocalizedEnum(_localizationService, _workContext)); - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; - orderItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - } - - //return requests - orderItemModel.ReturnRequests = _returnRequestService - .SearchReturnRequests(orderItemId: orderItem.Id) - .Select(item => new OrderModel.OrderItemModel.ReturnRequestBriefModel - { - CustomNumber = item.CustomNumber, - Id = item.Id - }).ToList(); - - //gift cards - orderItemModel.PurchasedGiftCardIds = _giftCardService.GetGiftCardsByPurchasedWithOrderItemId(orderItem.Id) - .Select(gc => gc.Id).ToList(); - - model.Items.Add(orderItemModel); - } - model.HasDownloadableProducts = hasDownloadableItems; - #endregion - } - - [NonAction] - protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAddProductToOrderModel(int orderId, int productId) - { - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); - - var presetQty = 1; - var presetPrice = _priceCalculationService.GetFinalPrice(product, order.Customer, decimal.Zero, true, presetQty); - decimal taxRate; - decimal presetPriceInclTax = _taxService.GetProductPrice(product, presetPrice, true, order.Customer, out taxRate); - decimal presetPriceExclTax = _taxService.GetProductPrice(product, presetPrice, false, order.Customer, out taxRate); - - var model = new OrderModel.AddOrderProductModel.ProductDetailsModel - { - ProductId = productId, - OrderId = orderId, - Name = product.Name, - ProductType = product.ProductType, - UnitPriceExclTax = presetPriceExclTax, - UnitPriceInclTax = presetPriceInclTax, - Quantity = presetQty, - SubTotalExclTax = presetPriceExclTax, - SubTotalInclTax = presetPriceInclTax, - AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder, - VatRate = taxRate - }; - - //attributes - var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - foreach (var attribute in attributes) - { - var attributeModel = new OrderModel.AddOrderProductModel.ProductAttributeModel - { - Id = attribute.Id, - ProductAttributeId = attribute.ProductAttributeId, - Name = attribute.ProductAttribute.Name, - TextPrompt = attribute.TextPrompt, - IsRequired = attribute.IsRequired, - AttributeControlType = attribute.AttributeControlType, - HasCondition = !String.IsNullOrEmpty(attribute.ConditionAttributeXml) - }; - if (!String.IsNullOrEmpty(attribute.ValidationFileAllowedExtensions)) - { - attributeModel.AllowedFileExtensions = attribute.ValidationFileAllowedExtensions - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .ToList(); - } - - if (attribute.ShouldHaveValues()) - { - //values - var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); - foreach (var attributeValue in attributeValues) - { - var attributeValueModel = new OrderModel.AddOrderProductModel.ProductAttributeValueModel - { - Id = attributeValue.Id, - Name = attributeValue.Name, - IsPreSelected = attributeValue.IsPreSelected - }; - attributeModel.Values.Add(attributeValueModel); - } - } - - model.ProductAttributes.Add(attributeModel); - } - model.HasCondition = model.ProductAttributes.Any(a => a.HasCondition); - //gift card - model.GiftCard.IsGiftCard = product.IsGiftCard; - if (model.GiftCard.IsGiftCard) - { - model.GiftCard.GiftCardType = product.GiftCardType; - } - //rental - model.IsRental = product.IsRental; - return model; - } - - [NonAction] - protected virtual ShipmentModel PrepareShipmentModel(Shipment shipment, bool prepareProducts, bool prepareShipmentEvent = false) - { - //measures - var baseWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); - var baseWeightIn = baseWeight != null ? baseWeight.Name : ""; - var baseDimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId); - var baseDimensionIn = baseDimension != null ? baseDimension.Name : ""; - - var model = new ShipmentModel - { - Id = shipment.Id, - OrderId = shipment.OrderId, - TrackingNumber = shipment.TrackingNumber, - TotalWeight = shipment.TotalWeight.HasValue ? string.Format("{0:F2} [{1}]", shipment.TotalWeight, baseWeightIn) : "", - ShippedDate = shipment.ShippedDateUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc).ToString() : _localizationService.GetResource("Admin.Orders.Shipments.ShippedDate.NotYet"), - ShippedDateUtc = shipment.ShippedDateUtc, - CanShip = !shipment.ShippedDateUtc.HasValue, - DeliveryDate = shipment.DeliveryDateUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc).ToString() : _localizationService.GetResource("Admin.Orders.Shipments.DeliveryDate.NotYet"), - DeliveryDateUtc = shipment.DeliveryDateUtc, - CanDeliver = shipment.ShippedDateUtc.HasValue && !shipment.DeliveryDateUtc.HasValue, - AdminComment = shipment.AdminComment, - }; - - if (prepareProducts) - { - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - //quantities - var qtyInThisShipment = shipmentItem.Quantity; - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - var qtyOrdered = orderItem.Quantity; - var qtyInAllShipments = orderItem.GetTotalNumberOfItemsInAllShipment(); - - var warehouse = _shippingService.GetWarehouseById(shipmentItem.WarehouseId); - var shipmentItemModel = new ShipmentModel.ShipmentItemModel - { - Id = shipmentItem.Id, - OrderItemId = orderItem.Id, - ProductId = orderItem.ProductId, - ProductName = orderItem.Product.Name, - Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), - AttributeInfo = orderItem.AttributeDescription, - ShippedFromWarehouse = warehouse != null ? warehouse.Name : null, - ShipSeparately = orderItem.Product.ShipSeparately, - ItemWeight = orderItem.ItemWeight.HasValue ? string.Format("{0:F2} [{1}]", orderItem.ItemWeight, baseWeightIn) : "", - ItemDimensions = string.Format("{0:F2} x {1:F2} x {2:F2} [{3}]", orderItem.Product.Length, orderItem.Product.Width, orderItem.Product.Height, baseDimensionIn), - QuantityOrdered = qtyOrdered, - QuantityInThisShipment = qtyInThisShipment, - QuantityInAllShipments = qtyInAllShipments, - QuantityToAdd = maxQtyToAdd, - }; - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; - shipmentItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - } - - model.Items.Add(shipmentItemModel); - } - } - - if (prepareShipmentEvent && !String.IsNullOrEmpty(shipment.TrackingNumber)) - { - var shipmentTracker = shipment.GetShipmentTracker(_shippingService, _shippingSettings); - if (shipmentTracker != null) - { - model.TrackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber); - if (_shippingSettings.DisplayShipmentEventsToStoreOwner) - { - var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber); - if (shipmentEvents != null) - foreach (var shipmentEvent in shipmentEvents) - { - var shipmentStatusEventModel = new ShipmentModel.ShipmentStatusEventModel(); - var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); - shipmentStatusEventModel.Country = shipmentEventCountry != null - ? shipmentEventCountry.GetLocalized(x => x.Name) - : shipmentEvent.CountryCode; - shipmentStatusEventModel.Date = shipmentEvent.Date; - shipmentStatusEventModel.EventName = shipmentEvent.EventName; - shipmentStatusEventModel.Location = shipmentEvent.Location; - model.ShipmentStatusEvents.Add(shipmentStatusEventModel); - } - } - } - } - - return model; - } - - #endregion - - #region Order list - - public ActionResult Index() - { - return RedirectToAction("List"); - } - - public ActionResult List( - [ModelBinder(typeof(CommaSeparatedModelBinder))] List orderStatusIds = null, - [ModelBinder(typeof(CommaSeparatedModelBinder))] List paymentStatusIds = null, - [ModelBinder(typeof(CommaSeparatedModelBinder))] List shippingStatusIds = null) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //order statuses - var model = new OrderListModel(); - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.AvailableOrderStatuses.Insert(0, new SelectListItem - { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); - if (orderStatusIds != null && orderStatusIds.Any()) - { - foreach (var item in model.AvailableOrderStatuses.Where(os => orderStatusIds.Contains(os.Value))) - item.Selected = true; - model.AvailableOrderStatuses.First().Selected = false; - } - //payment statuses - model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.AvailablePaymentStatuses.Insert(0, new SelectListItem - { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); - if (paymentStatusIds != null && paymentStatusIds.Any()) - { - foreach (var item in model.AvailablePaymentStatuses.Where(ps => paymentStatusIds.Contains(ps.Value))) - item.Selected = true; - model.AvailablePaymentStatuses.First().Selected = false; - } - - //shipping statuses - model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.AvailableShippingStatuses.Insert(0, new SelectListItem - { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); - if (shippingStatusIds != null && shippingStatusIds.Any()) - { - foreach (var item in model.AvailableShippingStatuses.Where(ss => shippingStatusIds.Contains(ss.Value))) - item.Selected = true; - model.AvailableShippingStatuses.First().Selected = false; - } - - //stores - model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); - - //vendors - model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); - foreach (var v in vendors) - model.AvailableVendors.Add(v); - - //warehouses - model.AvailableWarehouses.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var w in _shippingService.GetAllWarehouses()) - model.AvailableWarehouses.Add(new SelectListItem { Text = w.Name, Value = w.Id.ToString() }); - - //payment methods - model.AvailablePaymentMethods.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "" }); - foreach (var pm in _paymentService.LoadAllPaymentMethods()) - model.AvailablePaymentMethods.Add(new SelectListItem { Text = pm.PluginDescriptor.FriendlyName, Value = pm.PluginDescriptor.SystemName }); - - //billing countries - foreach (var c in _countryService.GetAllCountriesForBilling(showHidden: true)) - { - model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); - } - model.AvailableCountries.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //a vendor should have access only to orders with his products - model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; - - return View(model); - } - - [HttpPost] - public ActionResult OrderList(DataSourceRequest command, OrderListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - model.VendorId = _workContext.CurrentVendor.Id; - } - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; - var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; - var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; - - var filterByProductId = 0; - var product = _productService.GetProductById(model.ProductId); - if (product != null && HasAccessToProduct(product)) - filterByProductId = model.ProductId; - - //load orders - var orders = _orderService.SearchOrders(storeId: model.StoreId, - vendorId: model.VendorId, - productId: filterByProductId, - warehouseId: model.WarehouseId, - paymentMethodSystemName: model.PaymentMethodSystemName, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes, - pageIndex: command.Page - 1, - pageSize: command.PageSize); - var gridModel = new DataSourceResult - { - Data = orders.Select(x => - { - var store = _storeService.GetStoreById(x.StoreId); - return new OrderModel - { - Id = x.Id, - StoreName = store != null ? store.Name : "Unknown", - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), - OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - OrderStatusId = x.OrderStatusId, - PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), - PaymentStatusId = x.PaymentStatusId, - ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), - ShippingStatusId = x.ShippingStatusId, - CustomerEmail = x.BillingAddress.Email, - CustomerFullName = string.Format("{0} {1}", x.BillingAddress.FirstName, x.BillingAddress.LastName), - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }), - Total = orders.TotalCount - }; - - //summary report - //currently we do not support productId and warehouseId parameters for this report - var reportSummary = _orderReportService.GetOrderAverageReportLine( - storeId: model.StoreId, - vendorId: model.VendorId, - orderId: 0, - paymentMethodSystemName: model.PaymentMethodSystemName, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - startTimeUtc: startDateValue, - endTimeUtc: endDateValue, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes); - - var profit = _orderReportService.ProfitReport( - storeId: model.StoreId, - vendorId: model.VendorId, - paymentMethodSystemName: model.PaymentMethodSystemName, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - startTimeUtc: startDateValue, - endTimeUtc: endDateValue, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes); - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); - if (primaryStoreCurrency == null) - throw new Exception("Cannot load primary store currency"); - - gridModel.ExtraData = new OrderAggreratorModel - { - aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), - aggregatorshipping = _priceFormatter.FormatShippingPrice(reportSummary.SumShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false), - aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), - aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) - }; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost, ActionName("List")] - [FormValueRequired("go-to-order-by-number")] - public ActionResult GoToOrderId(OrderListModel model) - { - var order = _orderService.GetOrderById(model.GoDirectlyToNumber); - if (order == null) - return List(); - - return RedirectToAction("Edit", "Order", new { id = order.Id }); - } - - public ActionResult ProductSearchAutoComplete(string term) - { - const int searchTermMinimumLength = 3; - if (String.IsNullOrWhiteSpace(term) || term.Length < searchTermMinimumLength) - return Content(""); - - //a vendor should have access only to his products - var vendorId = 0; - if (_workContext.CurrentVendor != null) - { - vendorId = _workContext.CurrentVendor.Id; - } - - //products - const int productNumber = 15; - var products = _productService.SearchProducts( - vendorId: vendorId, - keywords: term, - pageSize: productNumber, - showHidden: true); - - var result = (from p in products - select new - { - label = p.Name, - productid = p.Id - }) - .ToList(); - return Json(result, JsonRequestBehavior.AllowGet); - } - - #endregion - - #region Export / Import - - [HttpPost, ActionName("List")] - [FormValueRequired("exportxml-all")] - public ActionResult ExportXmlAll(OrderListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor cannot export orders - if (_workContext.CurrentVendor != null) - return AccessDeniedView(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; - var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; - var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; - - var filterByProductId = 0; - var product = _productService.GetProductById(model.ProductId); - if (product != null && HasAccessToProduct(product)) - filterByProductId = model.ProductId; - - //load orders - var orders = _orderService.SearchOrders(storeId: model.StoreId, - vendorId: model.VendorId, - productId: filterByProductId, - warehouseId: model.WarehouseId, - paymentMethodSystemName: model.PaymentMethodSystemName, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes); - - try - { - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportXmlSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor cannot export orders - if (_workContext.CurrentVendor != null) - return AccessDeniedView(); - - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } - - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); - } - - [HttpPost, ActionName("List")] - [FormValueRequired("exportexcel-all")] - public ActionResult ExportExcelAll(OrderListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor cannot export orders - if (_workContext.CurrentVendor != null) - return AccessDeniedView(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; - var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; - var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; - - var filterByProductId = 0; - var product = _productService.GetProductById(model.ProductId); - if (product != null && HasAccessToProduct(product)) - filterByProductId = model.ProductId; - - //load orders - var orders = _orderService.SearchOrders(storeId: model.StoreId, - vendorId: model.VendorId, - productId: filterByProductId, - warehouseId: model.WarehouseId, - paymentMethodSystemName: model.PaymentMethodSystemName, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes); - - try - { - byte[] bytes = _exportManager.ExportOrdersToXlsx(orders); - return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public ActionResult ExportExcelSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor cannot export orders - if (_workContext.CurrentVendor != null) - return AccessDeniedView(); - - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } - - try - { - byte[] bytes = _exportManager.ExportOrdersToXlsx(orders); - return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - #endregion - - #region Order details - - #region Payments and other order workflow - - [HttpPost, ActionName("Edit")] - [FormValueRequired("cancelorder")] - public ActionResult CancelOrder(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - _orderProcessingService.CancelOrder(order, true); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("captureorder")] - public ActionResult CaptureOrder(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - var errors = _orderProcessingService.Capture(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - foreach (var error in errors) - ErrorNotification(error, false); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("markorderaspaid")] - public ActionResult MarkOrderAsPaid(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - _orderProcessingService.MarkOrderAsPaid(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("refundorder")] - public ActionResult RefundOrder(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - var errors = _orderProcessingService.Refund(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - foreach (var error in errors) - ErrorNotification(error, false); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("refundorderoffline")] - public ActionResult RefundOrderOffline(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - _orderProcessingService.RefundOffline(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("voidorder")] - public ActionResult VoidOrder(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - var errors = _orderProcessingService.Void(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - foreach (var error in errors) - ErrorNotification(error, false); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("voidorderoffline")] - public ActionResult VoidOrderOffline(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - _orderProcessingService.VoidOffline(order); - LogEditOrder(order.Id); - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - return View(model); - } - catch (Exception exc) - { - //error - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - public ActionResult PartiallyRefundOrderPopup(int id, bool online) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - - return View(model); - } - - [HttpPost] - [FormValueRequired("partialrefundorder")] - public ActionResult PartiallyRefundOrderPopup(string btnId, string formId, int id, bool online, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - decimal amountToRefund = model.AmountToRefund; - if (amountToRefund <= decimal.Zero) - throw new NopException("Enter amount to refund"); - - decimal maxAmountToRefund = order.OrderTotal - order.RefundedAmount; - if (amountToRefund > maxAmountToRefund) - amountToRefund = maxAmountToRefund; - - var errors = new List(); - if (online) - errors = _orderProcessingService.PartiallyRefund(order, amountToRefund).ToList(); - else - _orderProcessingService.PartiallyRefundOffline(order, amountToRefund); - - LogEditOrder(order.Id); - - if (!errors.Any()) - { - //success - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - - PrepareOrderDetailsModel(model, order); - return View(model); - } - - //error - PrepareOrderDetailsModel(model, order); - foreach (var error in errors) - ErrorNotification(error, false); - return View(model); - } - catch (Exception exc) - { - //error - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("btnSaveOrderStatus")] - public ActionResult ChangeOrderStatus(int id, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - try - { - order.OrderStatusId = model.OrderStatusId; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order status has been edited. New status: {0}", order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - return View(model); - } - catch (Exception exc) - { - //error - model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - ErrorNotification(exc, false); - return View(model); - } - } - - #endregion - - #region Edit, delete - - public ActionResult Edit(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null || order.Deleted) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) - return RedirectToAction("List"); - - 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); - } - - [HttpPost] - public ActionResult Delete(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - _orderProcessingService.DeleteOrder(order); - - //activity log - _customerActivityService.InsertActivity("DeleteOrder", _localizationService.GetResource("ActivityLog.DeleteOrder"), order.Id); - - return RedirectToAction("List"); - } - - public ActionResult PdfInvoice(int orderId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor should have access only to his products - var vendorId = 0; - if (_workContext.CurrentVendor != null) - { - vendorId = _workContext.CurrentVendor.Id; - } - - var order = _orderService.GetOrderById(orderId); - var orders = new List(); - orders.Add(order); - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, vendorId); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, string.Format("order_{0}.pdf", order.Id)); - } - - [HttpPost, ActionName("List")] - [FormValueRequired("pdf-invoice-all")] - public ActionResult PdfInvoiceAll(OrderListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - model.VendorId = _workContext.CurrentVendor.Id; - } - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; - var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; - var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; - - var filterByProductId = 0; - var product = _productService.GetProductById(model.ProductId); - if (product != null && HasAccessToProduct(product)) - filterByProductId = model.ProductId; - - //load orders - var orders = _orderService.SearchOrders(storeId: model.StoreId, - vendorId: model.VendorId, - productId: filterByProductId, - warehouseId: model.WarehouseId, - paymentMethodSystemName: model.PaymentMethodSystemName, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - billingEmail: model.BillingEmail, - billingLastName: model.BillingLastName, - billingCountryId: model.BillingCountryId, - orderNotes: model.OrderNotes); - - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, model.VendorId); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, "orders.pdf"); - } - - [HttpPost] - public ActionResult PdfInvoiceSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } - - //a vendor should have access only to his products - var vendorId = 0; - if (_workContext.CurrentVendor != null) - { - orders = orders.Where(HasAccessToOrder).ToList(); - vendorId = _workContext.CurrentVendor.Id; - } - - //ensure that we at least one order selected - if (!orders.Any()) - { - ErrorNotification(_localizationService.GetResource("Admin.Orders.PdfInvoice.NoOrders")); - return RedirectToAction("List"); - } - - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, vendorId); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, "orders.pdf"); - } - - //currently we use this method on the add product to order details pages - [HttpPost] - [ValidateInput(false)] - public ActionResult ProductDetails_AttributeChange(int productId, bool validateAttributeConditions, - FormCollection form) - { - var product = _productService.GetProductById(productId); - if (product == null) - return new NullJsonResult(); - - var attributeXml = ParseProductAttributes(product, form); - - //conditional attributes - var enabledAttributeMappingIds = new List(); - var disabledAttributeMappingIds = new List(); - if (validateAttributeConditions) - { - var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - foreach (var attribute in attributes) - { - var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributeXml); - if (conditionMet.HasValue) - { - if (conditionMet.Value) - enabledAttributeMappingIds.Add(attribute.Id); - else - disabledAttributeMappingIds.Add(attribute.Id); - } - } - } - - return Json(new - { - enabledattributemappingids = enabledAttributeMappingIds.ToArray(), - disabledattributemappingids = disabledAttributeMappingIds.ToArray() - }); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("btnSaveCC")] - public ActionResult EditCreditCardInfo(int id, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - if (order.AllowStoringCreditCardNumber) - { - string cardType = model.CardType; - string cardName = model.CardName; - string cardNumber = model.CardNumber; - string cardCvv2 = model.CardCvv2; - string cardExpirationMonth = model.CardExpirationMonth; - string cardExpirationYear = model.CardExpirationYear; - - order.CardType = _encryptionService.EncryptText(cardType); - order.CardName = _encryptionService.EncryptText(cardName); - order.CardNumber = _encryptionService.EncryptText(cardNumber); - order.MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(cardNumber)); - order.CardCvv2 = _encryptionService.EncryptText(cardCvv2); - order.CardExpirationMonth = _encryptionService.EncryptText(cardExpirationMonth); - order.CardExpirationYear = _encryptionService.EncryptText(cardExpirationYear); - _orderService.UpdateOrder(order); - } - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Credit card info has been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - PrepareOrderDetailsModel(model, order); - return View(model); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("btnSaveOrderTotals")] - public ActionResult EditOrderTotals(int id, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - order.OrderSubtotalInclTax = model.OrderSubtotalInclTaxValue; - order.OrderSubtotalExclTax = model.OrderSubtotalExclTaxValue; - order.OrderSubTotalDiscountInclTax = model.OrderSubTotalDiscountInclTaxValue; - order.OrderSubTotalDiscountExclTax = model.OrderSubTotalDiscountExclTaxValue; - order.OrderShippingInclTax = model.OrderShippingInclTaxValue; - order.OrderShippingExclTax = model.OrderShippingExclTaxValue; - order.PaymentMethodAdditionalFeeInclTax = model.PaymentMethodAdditionalFeeInclTaxValue; - order.PaymentMethodAdditionalFeeExclTax = model.PaymentMethodAdditionalFeeExclTaxValue; - order.TaxRates = model.TaxRatesValue; - order.OrderTax = model.TaxValue; - order.OrderDiscount = model.OrderTotalDiscountValue; - order.OrderTotal = model.OrderTotalValue; - order.OrderAmount = model.OrderAmountValue; - order.OrderAmountIncl = model.OrderAmountInclValue; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order totals have been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - PrepareOrderDetailsModel(model, order); - return View(model); - } - [HttpPost, ActionName("Edit")] - [FormValueRequired("btnSaveIV")] - public ActionResult EditInvoiceInfo(int id, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - //assign invoice id when null - if (model.InvoiceId == null) - { - order.InvoiceId = _orderProcessingService.GetInvoiceId(); - order.InvoiceDateUtc = DateTime.UtcNow; - //not needed, we use redirection now - //ModelState.SetModelValue("InvoiceId", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceId"].Value.Culture)); - //ModelState.SetModelValue("InvoiceDateUtc", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceDateUtc"].Value.Culture)); - } - else - { - order.InvoiceId = model.InvoiceId; - order.InvoiceDateUtc = model.InvoiceDateUtc; - } - - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Invoice data has been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - //use redirect instead of passing the new model to have correct browser refresh - //PrepareOrderDetailsModel(model, order); - //return View(model); - return RedirectToAction("Edit", "Order", new { id = id }); - } - - //something failed, redisplay form - return View(model); - } - [HttpPost, ActionName("Edit")] - [FormValueRequired("save-shipping-method")] - public ActionResult EditShippingMethod(int id, OrderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - order.ShippingMethod = model.ShippingMethod; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Shipping method has been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - PrepareOrderDetailsModel(model, order); - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired(FormValueRequirement.StartsWith, "btnSaveOrderItem")] - [ValidateInput(false)] - public ActionResult EditOrderItem(int id, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - //get order item identifier - int orderItemId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("btnSaveOrderItem", StringComparison.InvariantCultureIgnoreCase)) - orderItemId = Convert.ToInt32(formValue.Substring("btnSaveOrderItem".Length)); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - - decimal unitPriceInclTax, unitPriceExclTax, discountInclTax, discountExclTax,priceInclTax,priceExclTax; - int quantity; decimal vatrate; - if (!decimal.TryParse(form["pvUnitPriceInclTax" + orderItemId], out unitPriceInclTax)) - unitPriceInclTax = orderItem.UnitPriceInclTax; - if (!decimal.TryParse(form["pvUnitPriceExclTax" + orderItemId], out unitPriceExclTax)) - unitPriceExclTax = orderItem.UnitPriceExclTax; - if (!int.TryParse(form["pvQuantity" + orderItemId], out quantity)) - quantity = orderItem.Quantity; - if (!decimal.TryParse(form["pvDiscountInclTax" + orderItemId], out discountInclTax)) - discountInclTax = orderItem.DiscountAmountInclTax; - if (!decimal.TryParse(form["pvDiscountExclTax" + orderItemId], out discountExclTax)) - discountExclTax = orderItem.DiscountAmountExclTax; - if (!decimal.TryParse(form["pvPriceInclTax" + orderItemId], out priceInclTax)) - priceInclTax = orderItem.PriceInclTax; - if (!decimal.TryParse(form["pvPriceExclTax" + orderItemId], out priceExclTax)) - priceExclTax = orderItem.PriceExclTax; - if (!decimal.TryParse(form["pvVatRate" + orderItemId], out vatrate)) - vatrate = orderItem.VatRate; - - if (quantity > 0) - { - int qtyDifference = orderItem.Quantity - quantity; - - if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) - { - orderItem.UnitPriceInclTax = unitPriceInclTax; - orderItem.UnitPriceExclTax = unitPriceExclTax; - orderItem.Quantity = quantity; - orderItem.DiscountAmountInclTax = discountInclTax; - orderItem.DiscountAmountExclTax = discountExclTax; - orderItem.PriceInclTax = priceInclTax; - orderItem.PriceExclTax = priceExclTax; - orderItem.VatRate = vatrate; - _orderService.UpdateOrder(order); - } - - //adjust inventory - _productService.AdjustInventory(orderItem.Product, qtyDifference, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditOrder"), order.Id)); - - } - else - { - //adjust inventory - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrderItem"), order.Id)); - - //delete item - _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, - VatRate = vatrate - }; - _orderProcessingService.UpdateOrderTotals(updateOrderParameters); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order item has been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - model.Warnings = updateOrderParameters.Warnings; - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired(FormValueRequirement.StartsWith, "btnDeleteOrderItem")] - [ValidateInput(false)] - public ActionResult DeleteOrderItem(int id, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = id }); - - //get order item identifier - int orderItemId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("btnDeleteOrderItem", StringComparison.InvariantCultureIgnoreCase)) - orderItemId = Convert.ToInt32(formValue.Substring("btnDeleteOrderItem".Length)); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - if (_giftCardService.GetGiftCardsByPurchasedWithOrderItemId(orderItem.Id).Any()) - { - //we cannot delete an order item with associated gift cards - //a store owner should delete them first - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - - ErrorNotification(_localizationService.GetResource("Admin.Orders.OrderItem.DeleteAssociatedGiftCardRecordError"), false); - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - - } - else - { - //adjust inventory - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrderItem"), order.Id)); - - //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 - { - Note = "Order item has been deleted", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - model.Warnings = updateOrderParameters.Warnings; - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired(FormValueRequirement.StartsWith, "btnResetDownloadCount")] - [ValidateInput(false)] - public ActionResult ResetDownloadCount(int id, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //get order item identifier - int orderItemId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("btnResetDownloadCount", StringComparison.InvariantCultureIgnoreCase)) - orderItemId = Convert.ToInt32(formValue.Substring("btnResetDownloadCount".Length)); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - //ensure a vendor has access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) - return RedirectToAction("List"); - - orderItem.DownloadCount = 0; - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired(FormValueRequirement.StartsWith, "btnPvActivateDownload")] - [ValidateInput(false)] - public ActionResult ActivateDownloadItem(int id, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //get order item identifier - int orderItemId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("btnPvActivateDownload", StringComparison.InvariantCultureIgnoreCase)) - orderItemId = Convert.ToInt32(formValue.Substring("btnPvActivateDownload".Length)); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - //ensure a vendor has access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) - return RedirectToAction("List"); - - orderItem.IsDownloadActivated = !orderItem.IsDownloadActivated; - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - var model = new OrderModel(); - PrepareOrderDetailsModel(model, order); - - //selected tab - SaveSelectedTabName(persistForTheNextRequest: false); - - return View(model); - } - - public ActionResult UploadLicenseFilePopup(int id, int orderItemId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(id); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - if (!orderItem.Product.IsDownload) - throw new ArgumentException("Product is not downloadable"); - - //ensure a vendor has access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) - return RedirectToAction("List"); - - var model = new OrderModel.UploadLicenseModel - { - LicenseDownloadId = orderItem.LicenseDownloadId.HasValue ? orderItem.LicenseDownloadId.Value : 0, - OrderId = order.Id, - OrderItemId = orderItem.Id - }; - - return View(model); - } - - [HttpPost] - [FormValueRequired("uploadlicense")] - public ActionResult UploadLicenseFilePopup(string btnId, string formId, OrderModel.UploadLicenseModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(model.OrderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - //ensure a vendor has access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) - return RedirectToAction("List"); - - //attach license - if (model.LicenseDownloadId > 0) - orderItem.LicenseDownloadId = model.LicenseDownloadId; - else - orderItem.LicenseDownloadId = null; - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - //success - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - - return View(model); - } - - [HttpPost, ActionName("UploadLicenseFilePopup")] - [FormValueRequired("deletelicense")] - public ActionResult DeleteLicenseFilePopup(string btnId, string formId, OrderModel.UploadLicenseModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(model.OrderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId); - if (orderItem == null) - throw new ArgumentException("No order item found with the specified id"); - - //ensure a vendor has access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) - return RedirectToAction("List"); - - //attach license - orderItem.LicenseDownloadId = null; - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - //success - ViewBag.RefreshPage = true; - ViewBag.btnId = btnId; - ViewBag.formId = formId; - - return View(model); - } - - public ActionResult AddProductToOrder(int orderId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = orderId }); - - var model = new OrderModel.AddOrderProductModel(); - model.OrderId = orderId; - //categories - model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); - foreach (var c in categories) - model.AvailableCategories.Add(c); - - //manufacturers - model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); - foreach (var m in manufacturers) - model.AvailableManufacturers.Add(m); - - //product types - model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost] - public ActionResult AddProductToOrder(DataSourceRequest command, OrderModel.AddOrderProductModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return Content(""); - - var gridModel = new DataSourceResult(); - var products = _productService.SearchProducts(categoryIds: new List {model.SearchCategoryId}, - manufacturerId: model.SearchManufacturerId, - productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, - keywords: model.SearchProductName, - pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true); - gridModel.Data = products.Select(x => - { - var productModel = new OrderModel.AddOrderProductModel.ProductModel - { - Id = x.Id, - Name = x.Name, - Sku = x.Sku, - }; - - return productModel; - }); - gridModel.Total = products.TotalCount; - - return Json(gridModel); - } - - public ActionResult AddProductToOrderDetails(int orderId, int productId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = orderId }); - - var model = PrepareAddProductToOrderModel(orderId, productId); - return View(model); - } - - [HttpPost] - public ActionResult AddProductToOrderDetails(int orderId, int productId, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = orderId }); - - var order = _orderService.GetOrderById(orderId); - var product = _productService.GetProductById(productId); - //save order item - - //basic properties - decimal unitPriceInclTax; - decimal.TryParse(form["UnitPriceInclTax"], out unitPriceInclTax); - decimal unitPriceExclTax; - decimal.TryParse(form["UnitPriceExclTax"], out unitPriceExclTax); - int quantity; - int.TryParse(form["Quantity"], out quantity); - decimal priceInclTax; - decimal.TryParse(form["SubTotalInclTax"], out priceInclTax); - decimal priceExclTax; - decimal.TryParse(form["SubTotalExclTax"], out priceExclTax); - decimal vatRate; - decimal.TryParse(form["VatRate"], out vatRate); - - //warnings - var warnings = new List(); - - //attributes - var attributesXml = ParseProductAttributes(product, form); - - #region Gift cards - - string recipientName = ""; - string recipientEmail = ""; - string senderName = ""; - string senderEmail = ""; - string giftCardMessage = ""; - if (product.IsGiftCard) - { - foreach (string formKey in form.AllKeys) - { - if (formKey.Equals("giftcard.RecipientName", StringComparison.InvariantCultureIgnoreCase)) - { - recipientName = form[formKey]; - continue; - } - if (formKey.Equals("giftcard.RecipientEmail", StringComparison.InvariantCultureIgnoreCase)) - { - recipientEmail = form[formKey]; - continue; - } - if (formKey.Equals("giftcard.SenderName", StringComparison.InvariantCultureIgnoreCase)) - { - senderName = form[formKey]; - continue; - } - if (formKey.Equals("giftcard.SenderEmail", StringComparison.InvariantCultureIgnoreCase)) - { - senderEmail = form[formKey]; - continue; - } - if (formKey.Equals("giftcard.Message", StringComparison.InvariantCultureIgnoreCase)) - { - giftCardMessage = form[formKey]; - continue; - } - } - - attributesXml = _productAttributeParser.AddGiftCardAttribute(attributesXml, - recipientName, recipientEmail, senderName, senderEmail, giftCardMessage); - } - - #endregion - - #region Rental product - - DateTime? rentalStartDate = null; - DateTime? rentalEndDate = null; - if (product.IsRental) - { - ParseRentalDates(form, out rentalStartDate, out rentalEndDate); - } - - #endregion - - //warnings - warnings.AddRange(_shoppingCartService.GetShoppingCartItemAttributeWarnings(order.Customer, ShoppingCartType.ShoppingCart, product, quantity, attributesXml)); - warnings.AddRange(_shoppingCartService.GetShoppingCartItemGiftCardWarnings(ShoppingCartType.ShoppingCart, product, attributesXml)); - warnings.AddRange(_shoppingCartService.GetRentalProductWarnings(product, rentalStartDate, rentalEndDate)); - if (!warnings.Any()) - { - //no errors - - //attributes - var attributeDescription = _productAttributeFormatter.FormatAttributes(product, attributesXml, order.Customer); - - //save item - var orderItem = new OrderItem - { - OrderItemGuid = Guid.NewGuid(), - Order = order, - ProductId = product.Id, - UnitPriceInclTax = unitPriceInclTax, - UnitPriceExclTax = unitPriceExclTax, - PriceInclTax = priceInclTax, - PriceExclTax = priceExclTax, - OriginalProductCost = _priceCalculationService.GetProductCost(product, attributesXml), - AttributeDescription = attributeDescription, - AttributesXml = attributesXml, - Quantity = quantity, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - RentalStartDateUtc = rentalStartDate, - RentalEndDateUtc = rentalEndDate, - VatRate = vatRate - }; - order.OrderItems.Add(orderItem); - _orderService.UpdateOrder(order); - - //adjust inventory - _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditOrder"), order.Id)); - - //update order totals - var updateOrderParameters = new UpdateOrderParameters - { - UpdatedOrder = order, - UpdatedOrderItem = orderItem, - PriceInclTax = unitPriceInclTax, - PriceExclTax = unitPriceExclTax, - SubTotalInclTax = priceInclTax, - SubTotalExclTax = priceExclTax, - Quantity = quantity, - VatRate = vatRate - }; - _orderProcessingService.UpdateOrderTotals(updateOrderParameters); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "A new order item has been added", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - //gift cards - if (product.IsGiftCard) - { - for (int i = 0; i < orderItem.Quantity; i++) - { - var gc = new GiftCard - { - GiftCardType = product.GiftCardType, - PurchasedWithOrderItem = orderItem, - Amount = unitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = recipientName, - RecipientEmail = recipientEmail, - SenderName = senderName, - SenderEmail = senderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }; - _giftCardService.InsertGiftCard(gc); - } - } - - //redirect to order details page - TempData["nop.admin.order.warnings"] = updateOrderParameters.Warnings; - return RedirectToAction("Edit", "Order", new { id = order.Id }); - } - - //errors - var model = PrepareAddProductToOrderModel(order.Id, product.Id); - model.Warnings.AddRange(warnings); - return View(model); - } - - #endregion - - #endregion - - #region Addresses - - public ActionResult AddressEdit(int addressId, int orderId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = orderId }); - - var address = _addressService.GetAddressById(addressId); - if (address == null) - throw new ArgumentException("No address found with the specified id", "addressId"); - - var model = new OrderAddressModel(); - model.OrderId = orderId; - model.Address = address.ToModel(); - model.Address.FirstNameEnabled = true; - model.Address.FirstNameRequired = true; - model.Address.LastNameEnabled = true; - model.Address.LastNameRequired = true; - model.Address.EmailEnabled = true; - model.Address.EmailRequired = true; - model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; - model.Address.CompanyRequired = _addressSettings.CompanyRequired; - model.Address.CountryEnabled = _addressSettings.CountryEnabled; - model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled - model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; - model.Address.CityEnabled = _addressSettings.CityEnabled; - model.Address.CityRequired = _addressSettings.CityRequired; - model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; - model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; - model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; - model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; - model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; - model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; - model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; - model.Address.PhoneRequired = _addressSettings.PhoneRequired; - model.Address.FaxEnabled = _addressSettings.FaxEnabled; - model.Address.FaxRequired = _addressSettings.FaxRequired; - - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) }); - //states - var states = address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - //customer attribute services - model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); - - return View(model); - } - - [HttpPost] - [ValidateInput(false)] - public ActionResult AddressEdit(OrderAddressModel model, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(model.OrderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = order.Id }); - - var address = _addressService.GetAddressById(model.Address.Id); - if (address == null) - throw new ArgumentException("No address found with the specified id"); - - //custom address attributes - var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); - var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); - foreach (var error in customAttributeWarnings) - { - ModelState.AddModelError("", error); - } - - if (ModelState.IsValid) - { - address = model.Address.ToEntity(address); - address.CustomAttributes = customAttributes; - _addressService.UpdateAddress(address); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Address has been edited", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, orderId = model.OrderId }); - } - - //If we got this far, something failed, redisplay form - model.OrderId = order.Id; - model.Address = address.ToModel(); - model.Address.FirstNameEnabled = true; - model.Address.FirstNameRequired = true; - model.Address.LastNameEnabled = true; - model.Address.LastNameRequired = true; - model.Address.EmailEnabled = true; - model.Address.EmailRequired = true; - model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; - model.Address.CompanyRequired = _addressSettings.CompanyRequired; - model.Address.CountryEnabled = _addressSettings.CountryEnabled; - model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled - model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; - model.Address.CityEnabled = _addressSettings.CityEnabled; - model.Address.CityRequired = _addressSettings.CityRequired; - model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; - model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; - model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; - model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; - model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; - model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; - model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; - model.Address.PhoneRequired = _addressSettings.PhoneRequired; - model.Address.FaxEnabled = _addressSettings.FaxEnabled; - model.Address.FaxRequired = _addressSettings.FaxRequired; - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) }); - //states - var states = address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - //customer attribute services - model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); - - return View(model); - } - - #endregion - - #region Shipments - - public ActionResult ShipmentList() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var model = new ShipmentListModel(); - //countries - model.AvailableCountries.Add(new SelectListItem { Text = "*", Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); - //states - model.AvailableStates.Add(new SelectListItem { Text = "*", Value = "0" }); - - //warehouses - model.AvailableWarehouses.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var w in _shippingService.GetAllWarehouses()) - model.AvailableWarehouses.Add(new SelectListItem { Text = w.Name, Value = w.Id.ToString() }); - - return View(model); - } - - [HttpPost] - public ActionResult ShipmentListSelect(DataSourceRequest command, ShipmentListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - //a vendor should have access only to his products - int vendorId = 0; - if (_workContext.CurrentVendor != null) - vendorId = _workContext.CurrentVendor.Id; - - //load shipments - var shipments = _shipmentService.GetAllShipments(vendorId: vendorId, - warehouseId: model.WarehouseId, - shippingCountryId: model.CountryId, - shippingStateId: model.StateProvinceId, - shippingCity: model.City, - trackingNumber: model.TrackingNumber, - loadNotShipped: model.LoadNotShipped, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - pageIndex: command.Page - 1, - pageSize: command.PageSize); - var gridModel = new DataSourceResult - { - Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false)), - Total = shipments.TotalCount - }; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - public ActionResult ShipmentsByOrder(int orderId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) - return Content(""); - - //shipments - var shipmentModels = new List(); - var shipments = order.Shipments - //a vendor should have access only to his products - .Where(s => _workContext.CurrentVendor == null || HasAccessToShipment(s)) - .OrderBy(s => s.CreatedOnUtc) - .ToList(); - foreach (var shipment in shipments) - shipmentModels.Add(PrepareShipmentModel(shipment, false)); - - var gridModel = new DataSourceResult - { - Data = shipmentModels, - Total = shipmentModels.Count - }; - - - return Json(gridModel); - } - - [HttpPost] - public ActionResult ShipmentsItemsByShipmentId(int shipmentId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(shipmentId); - if (shipment == null) - throw new ArgumentException("No shipment found with the specified id"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return Content(""); - - var order = _orderService.GetOrderById(shipment.OrderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) - return Content(""); - - //shipments - var shipmentModel = PrepareShipmentModel(shipment, true); - var gridModel = new DataSourceResult - { - Data = shipmentModel.Items, - Total = shipmentModel.Items.Count - }; - - return Json(gridModel); - } - - public ActionResult AddShipment(int orderId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) - return RedirectToAction("List"); - - var model = new ShipmentModel - { - OrderId = order.Id, - }; - - //measures - var baseWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); - var baseWeightIn = baseWeight != null ? baseWeight.Name : ""; - var baseDimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId); - var baseDimensionIn = baseDimension != null ? baseDimension.Name : ""; - - var orderItems = order.OrderItems; - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - orderItems = orderItems.Where(HasAccessToOrderItem).ToList(); - } - - foreach (var orderItem in orderItems) - { - //we can ship only shippable products - if (!orderItem.Product.IsShipEnabled) - continue; - - //quantities - var qtyInThisShipment = 0; - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - var qtyOrdered = orderItem.Quantity; - var qtyInAllShipments = orderItem.GetTotalNumberOfItemsInAllShipment(); - - //ensure that this product can be added to a shipment - if (maxQtyToAdd <= 0) - continue; - - var shipmentItemModel = new ShipmentModel.ShipmentItemModel - { - OrderItemId = orderItem.Id, - ProductId = orderItem.ProductId, - ProductName = orderItem.Product.Name, - Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), - AttributeInfo = orderItem.AttributeDescription, - ShipSeparately = orderItem.Product.ShipSeparately, - ItemWeight = orderItem.ItemWeight.HasValue ? string.Format("{0:F2} [{1}]", orderItem.ItemWeight, baseWeightIn) : "", - ItemDimensions = string.Format("{0:F2} x {1:F2} x {2:F2} [{3}]", orderItem.Product.Length, orderItem.Product.Width, orderItem.Product.Height, baseDimensionIn), - QuantityOrdered = qtyOrdered, - QuantityInThisShipment = qtyInThisShipment, - QuantityInAllShipments = qtyInAllShipments, - QuantityToAdd = maxQtyToAdd, - }; - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; - shipmentItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - } - - if (orderItem.Product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && - orderItem.Product.UseMultipleWarehouses) - { - //multiple warehouses supported - shipmentItemModel.AllowToChooseWarehouse = true; - foreach (var pwi in orderItem.Product.ProductWarehouseInventory - .OrderBy(w => w.WarehouseId).ToList()) - { - var warehouse = pwi.Warehouse; - if (warehouse != null) - { - shipmentItemModel.AvailableWarehouses.Add(new ShipmentModel.ShipmentItemModel.WarehouseInfo - { - WarehouseId = warehouse.Id, - WarehouseName = warehouse.Name, - StockQuantity = pwi.StockQuantity, - ReservedQuantity = pwi.ReservedQuantity, - PlannedQuantity = _shipmentService.GetQuantityInShipments(orderItem.Product, warehouse.Id, true, true) - }); - } - } - } - else - { - //multiple warehouses are not supported - var warehouse = _shippingService.GetWarehouseById(orderItem.Product.WarehouseId); - if (warehouse != null) - { - shipmentItemModel.AvailableWarehouses.Add(new ShipmentModel.ShipmentItemModel.WarehouseInfo - { - WarehouseId = warehouse.Id, - WarehouseName = warehouse.Name, - StockQuantity = orderItem.Product.StockQuantity - }); - } - } - - model.Items.Add(shipmentItemModel); - } - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - [FormValueRequired("save", "save-continue")] - public ActionResult AddShipment(int orderId, FormCollection form, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - //No order found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) - return RedirectToAction("List"); - - var orderItems = order.OrderItems; - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - orderItems = orderItems.Where(HasAccessToOrderItem).ToList(); - } - - Shipment shipment = null; - decimal? totalWeight = null; - foreach (var orderItem in orderItems) - { - //is shippable - if (!orderItem.Product.IsShipEnabled) - continue; - - //ensure that this product can be shipped (have at least one item to ship) - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - if (maxQtyToAdd <= 0) - continue; - - int qtyToAdd = 0; //parse quantity - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out qtyToAdd); - break; - } - - int warehouseId = 0; - if (orderItem.Product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && - orderItem.Product.UseMultipleWarehouses) - { - //multiple warehouses supported - //warehouse is chosen by a store owner - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("warehouse_{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out warehouseId); - break; - } - } - else - { - //multiple warehouses are not supported - warehouseId = orderItem.Product.WarehouseId; - } - - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out qtyToAdd); - break; - } - - //validate quantity - if (qtyToAdd <= 0) - continue; - if (qtyToAdd > maxQtyToAdd) - qtyToAdd = maxQtyToAdd; - - //ok. we have at least one item. let's create a shipment (if it does not exist) - - var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; - if (orderItemTotalWeight.HasValue) - { - if (!totalWeight.HasValue) - totalWeight = 0; - totalWeight += orderItemTotalWeight.Value; - } - if (shipment == null) - { - var trackingNumber = form["TrackingNumber"]; - var adminComment = form["AdminComment"]; - shipment = new Shipment - { - OrderId = order.Id, - TrackingNumber = trackingNumber, - TotalWeight = null, - ShippedDateUtc = null, - DeliveryDateUtc = null, - AdminComment = adminComment, - CreatedOnUtc = DateTime.UtcNow, - }; - } - //create a shipment item - var shipmentItem = new ShipmentItem - { - OrderItemId = orderItem.Id, - Quantity = qtyToAdd, - WarehouseId = warehouseId - }; - shipment.ShipmentItems.Add(shipmentItem); - } - - //if we have at least one item in the shipment, then save it - if (shipment != null && shipment.ShipmentItems.Any()) - { - shipment.TotalWeight = totalWeight; - _shipmentService.InsertShipment(shipment); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "A shipment has been added", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Orders.Shipments.Added")); - return continueEditing - ? RedirectToAction("ShipmentDetails", new {id = shipment.Id}) - : RedirectToAction("Edit", new { id = orderId }); - } - - ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoProductsSelected")); - return RedirectToAction("AddShipment", new { orderId = orderId }); - } - - public ActionResult ShipmentDetails(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - var model = PrepareShipmentModel(shipment, true, true); - return View(model); - } - - [HttpPost] - public ActionResult DeleteShipment(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteShipment"), shipment.OrderId)); - } - - var orderId = shipment.OrderId; - _shipmentService.DeleteShipment(shipment); - - var order =_orderService.GetOrderById(orderId); - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "A shipment has been deleted", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - LogEditOrder(order.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Orders.Shipments.Deleted")); - return RedirectToAction("Edit", new { id = orderId }); - } - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("settrackingnumber")] - public ActionResult SetTrackingNumber(ShipmentModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(model.Id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - shipment.TrackingNumber = model.TrackingNumber; - _shipmentService.UpdateShipment(shipment); - - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("setadmincomment")] - public ActionResult SetShipmentAdminComment(ShipmentModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(model.Id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - shipment.AdminComment = model.AdminComment; - _shipmentService.UpdateShipment(shipment); - - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("setasshipped")] - public ActionResult SetAsShipped(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - try - { - _orderProcessingService.Ship(shipment, true); - LogEditOrder(shipment.OrderId); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - catch (Exception exc) - { - //error - ErrorNotification(exc, true); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - } - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("saveshippeddate")] - public ActionResult EditShippedDate(ShipmentModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(model.Id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - try - { - if (!model.ShippedDateUtc.HasValue) - { - throw new Exception("Enter shipped date"); - } - shipment.ShippedDateUtc = model.ShippedDateUtc; - _shipmentService.UpdateShipment(shipment); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - catch (Exception exc) - { - //error - ErrorNotification(exc, true); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - } - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("setasdelivered")] - public ActionResult SetAsDelivered(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - try - { - _orderProcessingService.Deliver(shipment, true); - LogEditOrder(shipment.OrderId); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - catch (Exception exc) - { - //error - ErrorNotification(exc, true); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - } - - - [HttpPost, ActionName("ShipmentDetails")] - [FormValueRequired("savedeliverydate")] - public ActionResult EditDeliveryDate(ShipmentModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(model.Id); - if (shipment == null) - //No shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - try - { - if (!model.DeliveryDateUtc.HasValue) - { - throw new Exception("Enter delivery date"); - } - shipment.DeliveryDateUtc = model.DeliveryDateUtc; - _shipmentService.UpdateShipment(shipment); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - catch (Exception exc) - { - //error - ErrorNotification(exc, true); - return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); - } - } - - public ActionResult PdfPackagingSlip(int shipmentId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipment = _shipmentService.GetShipmentById(shipmentId); - if (shipment == null) - //no shipment found with the specified id - return RedirectToAction("List"); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) - return RedirectToAction("List"); - - var shipments = new List(); - shipments.Add(shipment); - - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, string.Format("packagingslip_{0}.pdf", shipment.Id)); - } - - [HttpPost, ActionName("ShipmentList")] - [FormValueRequired("exportpackagingslips-all")] - public ActionResult PdfPackagingSlipAll(ShipmentListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - //a vendor should have access only to his products - int vendorId = 0; - if (_workContext.CurrentVendor != null) - vendorId = _workContext.CurrentVendor.Id; - - //load shipments - var shipments = _shipmentService.GetAllShipments(vendorId: vendorId, - warehouseId: model.WarehouseId, - shippingCountryId: model.CountryId, - shippingStateId: model.StateProvinceId, - shippingCity: model.City, - trackingNumber: model.TrackingNumber, - loadNotShipped: model.LoadNotShipped, - createdFromUtc: startDateValue, - createdToUtc: endDateValue); - - //ensure that we at least one shipment selected - if (!shipments.Any()) - { - ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoShipmentsSelected")); - return RedirectToAction("ShipmentList"); - } - - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, "packagingslips.pdf"); - } - - [HttpPost] - public ActionResult PdfPackagingSlipSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipments = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - shipments.AddRange(_shipmentService.GetShipmentsByIds(ids)); - } - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - shipments = shipments.Where(HasAccessToShipment).ToList(); - } - - //ensure that we at least one shipment selected - if (!shipments.Any()) - { - ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoShipmentsSelected")); - return RedirectToAction("ShipmentList"); - } - - byte[] bytes; - using (var stream = new MemoryStream()) - { - _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); - bytes = stream.ToArray(); - } - return File(bytes, MimeTypes.ApplicationPdf, "packagingslips.pdf"); - } - - [HttpPost] - public ActionResult SetAsShippedSelected(ICollection selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipments = new List(); - if (selectedIds != null) - { - shipments.AddRange(_shipmentService.GetShipmentsByIds(selectedIds.ToArray())); - } - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - shipments = shipments.Where(HasAccessToShipment).ToList(); - } - - foreach (var shipment in shipments) - { - try - { - _orderProcessingService.Ship(shipment, true); - } - catch - { - //ignore any exception - } - } - - return Json(new { Result = true }); - } - - [HttpPost] - public ActionResult SetAsDeliveredSelected(ICollection selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var shipments = new List(); - if (selectedIds != null) - { - shipments.AddRange(_shipmentService.GetShipmentsByIds(selectedIds.ToArray())); - } - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - shipments = shipments.Where(HasAccessToShipment).ToList(); - } - - foreach (var shipment in shipments) - { - try - { - _orderProcessingService.Deliver(shipment, true); - } - catch - { - //ignore any exception - } - } - - return Json(new { Result = true }); - } - - #endregion - - #region Order notes - - [HttpPost] - public ActionResult OrderNotesSelect(int orderId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return Content(""); - - //order notes - var orderNoteModels = new List(); - foreach (var orderNote in order.OrderNotes - .OrderByDescending(on => on.CreatedOnUtc)) - { - var download = _downloadService.GetDownloadById(orderNote.DownloadId); - orderNoteModels.Add(new OrderModel.OrderNote - { - Id = orderNote.Id, - OrderId = orderNote.OrderId, - DownloadId = orderNote.DownloadId, - DownloadGuid = download != null ? download.DownloadGuid : Guid.Empty, - DisplayToCustomer = orderNote.DisplayToCustomer, - Note = orderNote.FormatOrderNoteText(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) - }); - } - - var gridModel = new DataSourceResult - { - Data = orderNoteModels, - Total = orderNoteModels.Count - }; - - return Json(gridModel); - } - - [ValidateInput(false)] - public ActionResult OrderNoteAdd(int orderId, int downloadId, bool displayToCustomer, string message) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - return Json(new { Result = false }, JsonRequestBehavior.AllowGet); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return Json(new { Result = false }, JsonRequestBehavior.AllowGet); - - var orderNote = new OrderNote - { - DisplayToCustomer = displayToCustomer, - Note = message, - DownloadId = downloadId, - CreatedOnUtc = DateTime.UtcNow, - }; - order.OrderNotes.Add(orderNote); - _orderService.UpdateOrder(order); - - //new order notification - if (displayToCustomer) - { - //email - _workflowMessageService.SendNewOrderNoteAddedCustomerNotification( - orderNote, _workContext.WorkingLanguage.Id); - - } - - return Json(new { Result = true }, JsonRequestBehavior.AllowGet); - } - - [HttpPost] - public ActionResult OrderNoteDelete(int id, int orderId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); - - //a vendor does not have access to this functionality - if (_workContext.CurrentVendor != null) - return RedirectToAction("Edit", "Order", new { id = orderId }); - - var orderNote = order.OrderNotes.FirstOrDefault(on => on.Id == id); - if (orderNote == null) - throw new ArgumentException("No order note found with the specified id"); - _orderService.DeleteOrderNote(orderNote); - - return new NullJsonResult(); - } - - #endregion - - #region Reports - - [NonAction] - protected DataSourceResult GetBestsellersBriefReportModel(int pageIndex, - int pageSize, int orderBy) - { - //a vendor should have access only to his products - int vendorId = 0; - if (_workContext.CurrentVendor != null) - vendorId = _workContext.CurrentVendor.Id; - - var items = _orderReportService.BestSellersReport( - vendorId : vendorId, - orderBy: orderBy, - pageIndex: pageIndex, - pageSize: pageSize, - showHidden: true); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - { - var m = new BestsellersReportLineModel - { - ProductId = x.ProductId, - TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), - TotalQuantity = x.TotalQuantity, - }; - var product = _productService.GetProductById(x.ProductId); - if (product != null) - m.ProductName = product.Name; - return m; - }), - Total = items.TotalCount - }; - return gridModel; - } - public ActionResult BestsellersBriefReportByQuantity() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - return PartialView(); - } - [HttpPost] - public ActionResult BestsellersBriefReportByQuantityList(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - var gridModel = GetBestsellersBriefReportModel(command.Page - 1, - command.PageSize, 1); - - return Json(gridModel); - } - public ActionResult BestsellersBriefReportByAmount() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - return PartialView(); - } - [HttpPost] - public ActionResult BestsellersBriefReportByAmountList(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - var gridModel = GetBestsellersBriefReportModel(command.Page - 1, - command.PageSize, 2); - - return Json(gridModel); - } - - - - public ActionResult BestsellersReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var model = new BestsellersReportModel(); - //vendor - model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; - - //stores - model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); - - //order statuses - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //payment statuses - model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //categories - model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); - foreach (var c in categories) - model.AvailableCategories.Add(c); - - //manufacturers - model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); - foreach (var m in manufacturers) - model.AvailableManufacturers.Add(m); - - //billing countries - foreach (var c in _countryService.GetAllCountriesForBilling(showHidden: true)) - model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); - model.AvailableCountries.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //vendors - model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); - foreach (var v in vendors) - model.AvailableVendors.Add(v); - - return View(model); - } - [HttpPost] - public ActionResult BestsellersReportList(DataSourceRequest command, BestsellersReportModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - model.VendorId = _workContext.CurrentVendor.Id; - } - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; - PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; - - var items = _orderReportService.BestSellersReport( - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - os: orderStatus, - ps: paymentStatus, - billingCountryId: model.BillingCountryId, - orderBy: 2, - vendorId: model.VendorId, - categoryId: model.CategoryId, - manufacturerId: model.ManufacturerId, - storeId: model.StoreId, - pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - { - var m = new BestsellersReportLineModel - { - ProductId = x.ProductId, - TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), - TotalQuantity = x.TotalQuantity, - }; - var product = _productService.GetProductById(x.ProductId); - if (product!= null) - m.ProductName = product.Name; - return m; - }), - Total = items.TotalCount - }; - - return Json(gridModel); - } - - - - public ActionResult NeverSoldReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var model = new NeverSoldReportModel(); - - //a vendor should have access only to his products - model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; - - //categories - model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); - foreach (var c in categories) - model.AvailableCategories.Add(c); - - //manufacturers - model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); - foreach (var m in manufacturers) - model.AvailableManufacturers.Add(m); - - //stores - model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - foreach (var s in _storeService.GetAllStores()) - model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); - - //vendors - model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); - foreach (var v in vendors) - model.AvailableVendors.Add(v); - - - return View(model); - } - [HttpPost] - public ActionResult NeverSoldReportList(DataSourceRequest command, NeverSoldReportModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor should have access only to his products - if (_workContext.CurrentVendor != null) - { - model.SearchVendorId = _workContext.CurrentVendor.Id; - } - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var items = _orderReportService.ProductsNeverSold(vendorId: model.SearchVendorId, - storeId: model.SearchStoreId, - categoryId: model.SearchCategoryId, - manufacturerId: model.SearchManufacturerId, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - pageIndex : command.Page - 1, - pageSize: command.PageSize, - showHidden: true); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - new NeverSoldReportLineModel - { - ProductId = x.Id, - ProductName = x.Name, - }), - Total = items.TotalCount - }; - - return Json(gridModel); - } - - - [ChildActionOnly] - public ActionResult OrderAverageReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - return PartialView(); - } - [HttpPost] - public ActionResult OrderAverageReportList(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor doesn't have access to this report - if (_workContext.CurrentVendor != null) - return Content(""); - - var report = new List(); - report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Pending)); - report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Processing)); - report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Complete)); - report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Cancelled)); - var model = report.Select(x => new OrderAverageReportLineSummaryModel - { - OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - SumTodayOrders = _priceFormatter.FormatPrice(x.SumTodayOrders, true, false), - SumThisWeekOrders = _priceFormatter.FormatPrice(x.SumThisWeekOrders, true, false), - SumThisMonthOrders = _priceFormatter.FormatPrice(x.SumThisMonthOrders, true, false), - SumThisYearOrders = _priceFormatter.FormatPrice(x.SumThisYearOrders, true, false), - SumAllTimeOrders = _priceFormatter.FormatPrice(x.SumAllTimeOrders, true, false), - }).ToList(); - - var gridModel = new DataSourceResult - { - Data = model, - Total = model.Count - }; - - return Json(gridModel); - } - - [ChildActionOnly] - public ActionResult OrderIncompleteReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - return PartialView(); - } - - [HttpPost] - public ActionResult OrderIncompleteReportList(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor doesn't have access to this report - if (_workContext.CurrentVendor != null) - return Content(""); - - var model = new List(); - //not paid - var orderStatuses = Enum.GetValues(typeof(OrderStatus)).Cast().Where(os => os != (int)OrderStatus.Cancelled).ToList(); - var paymentStatuses = new List() { (int)PaymentStatus.Pending }; - var psPending = _orderReportService.GetOrderAverageReportLine(psIds: paymentStatuses, osIds: orderStatuses); - model.Add(new OrderIncompleteReportLineModel - { - Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalUnpaidOrders"), - Count = psPending.CountOrders, - Total = _priceFormatter.FormatPrice(psPending.SumOrders, true, false), - ViewLink = Url.Action("List", "Order", new { - orderStatusIds = string.Join(",", orderStatuses), - paymentStatusIds = string.Join(",", paymentStatuses) }) - }); - //not shipped - var shippingStatuses = new List() { (int)ShippingStatus.NotYetShipped }; - var ssPending = _orderReportService.GetOrderAverageReportLine(osIds: orderStatuses, ssIds: shippingStatuses); - model.Add(new OrderIncompleteReportLineModel - { - Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalNotShippedOrders"), - Count = ssPending.CountOrders, - Total = _priceFormatter.FormatPrice(ssPending.SumOrders, true, false), - ViewLink = Url.Action("List", "Order", new { - orderStatusIds = string.Join(",", orderStatuses), - shippingStatusIds = string.Join(",", shippingStatuses) }) - }); - //pending - orderStatuses = new List() { (int)OrderStatus.Pending }; - var osPending = _orderReportService.GetOrderAverageReportLine(osIds: orderStatuses); - model.Add(new OrderIncompleteReportLineModel - { - Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalIncompleteOrders"), - Count = osPending.CountOrders, - Total = _priceFormatter.FormatPrice(osPending.SumOrders, true, false), - ViewLink = Url.Action("List", "Order", new { orderStatusIds = string.Join(",", orderStatuses) }) - }); - - var gridModel = new DataSourceResult - { - Data = model, - Total = model.Count - }; - - return Json(gridModel); - } - - public ActionResult CountryReport() - { - if (!_permissionService.Authorize(StandardPermissionProvider.OrderCountryReport)) - return AccessDeniedView(); - - var model = new CountryReportModel(); - - //order statuses - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //payment statuses - model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost] - public ActionResult CountryReportList(DataSourceRequest command, CountryReportModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.OrderCountryReport)) - return Content(""); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; - PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; - - var items = _orderReportService.GetCountryReport( - os: orderStatus, - ps: paymentStatus, - startTimeUtc: startDateValue, - endTimeUtc: endDateValue); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - { - var country = _countryService.GetCountryById(x.CountryId.HasValue ? x.CountryId.Value : 0); - var m = new CountryReportLineModel - { - CountryName = country != null ? country.Name : "Unknown", - SumOrders = _priceFormatter.FormatPrice(x.SumOrders, true, false), - TotalOrders = x.TotalOrders, - }; - return m; - }), - Total = items.Count - }; - - return Json(gridModel); - } - - [ChildActionOnly] - public ActionResult OrderStatistics() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor doesn't have access to this report - if (_workContext.CurrentVendor != null) - return Content(""); - - return PartialView(); - } - - [AcceptVerbs(HttpVerbs.Get)] - public ActionResult LoadOrderStatistics(string period) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor doesn't have access to this report - if (_workContext.CurrentVendor != null) - return Content(""); - - var result = new List(); - - var nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); - var timeZone = _dateTimeHelper.CurrentTimeZone; - - var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture); - - switch (period) - { - case "year": - //year statistics - var yearAgoDt = nowDt.AddYears(-1).AddMonths(1); - var searchYearDateUser = new DateTime(yearAgoDt.Year, yearAgoDt.Month, 1); - if (!timeZone.IsInvalidTime(searchYearDateUser)) - { - for (int i = 0; i <= 12; i++) - { - result.Add(new - { - date = searchYearDateUser.Date.ToString("Y", culture), - value = _orderService.SearchOrders( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser.AddMonths(1), timeZone), - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchYearDateUser = searchYearDateUser.AddMonths(1); - } - } - break; - - case "month": - //month statistics - var monthAgoDt = nowDt.AddDays(-30); - var searchMonthDateUser = new DateTime(monthAgoDt.Year, monthAgoDt.Month, monthAgoDt.Day); - if (!timeZone.IsInvalidTime(searchMonthDateUser)) - { - for (int i = 0; i <= 30; i++) - { - result.Add(new - { - date = searchMonthDateUser.Date.ToString("M", culture), - value = _orderService.SearchOrders( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser.AddDays(1), timeZone), - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchMonthDateUser = searchMonthDateUser.AddDays(1); - } - } - break; - - case "week": - default: - //week statistics - var weekAgoDt = nowDt.AddDays(-7); - var searchWeekDateUser = new DateTime(weekAgoDt.Year, weekAgoDt.Month, weekAgoDt.Day); - if (!timeZone.IsInvalidTime(searchWeekDateUser)) - { - for (int i = 0; i <= 7; i++) - { - result.Add(new - { - date = searchWeekDateUser.Date.ToString("d dddd", culture), - value = _orderService.SearchOrders( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser.AddDays(1), timeZone), - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchWeekDateUser = searchWeekDateUser.AddDays(1); - } - } - break; - } - - return Json(result, JsonRequestBehavior.AllowGet); - } - - [ChildActionOnly] - public ActionResult LatestOrders() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - return PartialView(); - } - - #endregion - - #region Activity log - - [NonAction] - protected void LogEditOrder(int orderId) - { - _customerActivityService.InsertActivity("EditOrder", _localizationService.GetResource("ActivityLog.EditOrder"), orderId); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Web.Mvc; +using Nop.Admin.Extensions; +using Nop.Admin.Helpers; +using Nop.Admin.Models.Orders; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Services; +using Nop.Services.Affiliates; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.ExportImport; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Media; +using Nop.Services.Messages; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Security; +using Nop.Services.Shipping; +using Nop.Services.Shipping.Tracking; +using Nop.Services.Stores; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Kendoui; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Controllers +{ + public partial class OrderController : BaseAdminController + { + #region Fields + + private readonly IOrderService _orderService; + private readonly IOrderReportService _orderReportService; + private readonly IOrderProcessingService _orderProcessingService; + private readonly IReturnRequestService _returnRequestService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ITaxService _taxService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IPriceFormatter _priceFormatter; + private readonly IDiscountService _discountService; + private readonly ILocalizationService _localizationService; + private readonly IWorkContext _workContext; + private readonly ICurrencyService _currencyService; + private readonly IEncryptionService _encryptionService; + private readonly IPaymentService _paymentService; + private readonly IMeasureService _measureService; + private readonly IPdfService _pdfService; + private readonly IAddressService _addressService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IProductService _productService; + private readonly IExportManager _exportManager; + private readonly IPermissionService _permissionService; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IProductAttributeService _productAttributeService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IShoppingCartService _shoppingCartService; + private readonly IGiftCardService _giftCardService; + private readonly IDownloadService _downloadService; + private readonly IShipmentService _shipmentService; + private readonly IShippingService _shippingService; + private readonly IStoreService _storeService; + private readonly IVendorService _vendorService; + private readonly IAddressAttributeParser _addressAttributeParser; + private readonly IAddressAttributeService _addressAttributeService; + private readonly IAddressAttributeFormatter _addressAttributeFormatter; + private readonly IAffiliateService _affiliateService; + private readonly IPictureService _pictureService; + private readonly ICustomerActivityService _customerActivityService; + private readonly ICacheManager _cacheManager; + + private readonly OrderSettings _orderSettings; + private readonly CurrencySettings _currencySettings; + private readonly TaxSettings _taxSettings; + private readonly MeasureSettings _measureSettings; + private readonly AddressSettings _addressSettings; + private readonly ShippingSettings _shippingSettings; + + #endregion + + #region Ctor + + public OrderController(IOrderService orderService, + IOrderReportService orderReportService, + IOrderProcessingService orderProcessingService, + IReturnRequestService returnRequestService, + IPriceCalculationService priceCalculationService, + ITaxService taxService, + IDateTimeHelper dateTimeHelper, + IPriceFormatter priceFormatter, + IDiscountService discountService, + ILocalizationService localizationService, + IWorkContext workContext, + ICurrencyService currencyService, + IEncryptionService encryptionService, + IPaymentService paymentService, + IMeasureService measureService, + IPdfService pdfService, + IAddressService addressService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + IProductService productService, + IExportManager exportManager, + IPermissionService permissionService, + IWorkflowMessageService workflowMessageService, + ICategoryService categoryService, + IManufacturerService manufacturerService, + IProductAttributeService productAttributeService, + IProductAttributeParser productAttributeParser, + IProductAttributeFormatter productAttributeFormatter, + IShoppingCartService shoppingCartService, + IGiftCardService giftCardService, + IDownloadService downloadService, + IShipmentService shipmentService, + IShippingService shippingService, + IStoreService storeService, + IVendorService vendorService, + IAddressAttributeParser addressAttributeParser, + IAddressAttributeService addressAttributeService, + IAddressAttributeFormatter addressAttributeFormatter, + IAffiliateService affiliateService, + IPictureService pictureService, + ICustomerActivityService customerActivityService, + ICacheManager cacheManager, + OrderSettings orderSettings, + CurrencySettings currencySettings, + TaxSettings taxSettings, + MeasureSettings measureSettings, + AddressSettings addressSettings, + ShippingSettings shippingSettings) + { + this._orderService = orderService; + this._orderReportService = orderReportService; + this._orderProcessingService = orderProcessingService; + this._returnRequestService = returnRequestService; + this._priceCalculationService = priceCalculationService; + this._taxService = taxService; + this._dateTimeHelper = dateTimeHelper; + this._priceFormatter = priceFormatter; + this._discountService = discountService; + this._localizationService = localizationService; + this._workContext = workContext; + this._currencyService = currencyService; + this._encryptionService = encryptionService; + this._paymentService = paymentService; + this._measureService = measureService; + this._pdfService = pdfService; + this._addressService = addressService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._productService = productService; + this._exportManager = exportManager; + this._permissionService = permissionService; + this._workflowMessageService = workflowMessageService; + this._categoryService = categoryService; + this._manufacturerService = manufacturerService; + this._productAttributeService = productAttributeService; + this._productAttributeParser = productAttributeParser; + this._productAttributeFormatter = productAttributeFormatter; + this._shoppingCartService = shoppingCartService; + this._giftCardService = giftCardService; + this._downloadService = downloadService; + this._shipmentService = shipmentService; + this._shippingService = shippingService; + this._storeService = storeService; + this._vendorService = vendorService; + this._addressAttributeParser = addressAttributeParser; + this._addressAttributeService = addressAttributeService; + this._addressAttributeFormatter = addressAttributeFormatter; + this._affiliateService = affiliateService; + this._pictureService = pictureService; + this._customerActivityService = customerActivityService; + this._cacheManager = cacheManager; + this._orderSettings = orderSettings; + this._currencySettings = currencySettings; + this._taxSettings = taxSettings; + this._measureSettings = measureSettings; + this._addressSettings = addressSettings; + this._shippingSettings = shippingSettings; + } + + #endregion + + #region Utilities + + [NonAction] + protected virtual bool HasAccessToOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (_workContext.CurrentVendor == null) + //not a vendor; has access + return true; + + var vendorId = _workContext.CurrentVendor.Id; + var hasVendorProducts = order.OrderItems.Any(orderItem => orderItem.Product.VendorId == vendorId); + return hasVendorProducts; + } + + [NonAction] + protected virtual bool HasAccessToOrderItem(OrderItem orderItem) + { + if (orderItem == null) + throw new ArgumentNullException("orderItem"); + + if (_workContext.CurrentVendor == null) + //not a vendor; has access + return true; + + var vendorId = _workContext.CurrentVendor.Id; + return orderItem.Product.VendorId == vendorId; + } + + [NonAction] + protected virtual bool HasAccessToProduct(Product product) + { + if (product == null) + throw new ArgumentNullException("product"); + + if (_workContext.CurrentVendor == null) + //not a vendor; has access + return true; + + var vendorId = _workContext.CurrentVendor.Id; + return product.VendorId == vendorId; + } + + [NonAction] + protected virtual bool HasAccessToShipment(Shipment shipment) + { + if (shipment == null) + throw new ArgumentNullException("shipment"); + + if (_workContext.CurrentVendor == null) + //not a vendor; has access + return true; + + var hasVendorProducts = false; + var vendorId = _workContext.CurrentVendor.Id; + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem != null) + { + if (orderItem.Product.VendorId == vendorId) + { + hasVendorProducts = true; + break; + } + } + } + return hasVendorProducts; + } + + /// + /// Parse product attributes on the add product to order details page + /// + /// Product + /// Form + /// Parsed attributes + [NonAction] + protected virtual string ParseProductAttributes(Product product, FormCollection form) + { + var attributesXml = string.Empty; + + #region Product attributes + + var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in productAttributes) + { + var controlId = string.Format("product_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + int selectedAttributeId = int.Parse(ctrlAttributes); + if (selectedAttributeId > 0) + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.Checkboxes: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + foreach (var item in ctrlAttributes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + int selectedAttributeId = int.Parse(item); + if (selectedAttributeId > 0) + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + } + break; + case AttributeControlType.ReadonlyCheckboxes: + { + //load read-only (already server-side selected) values + var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + string enteredText = ctrlAttributes.Trim(); + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, enteredText); + } + } + break; + case AttributeControlType.Datepicker: + { + var day = form[controlId + "_day"]; + var month = form[controlId + "_month"]; + var year = form[controlId + "_year"]; + DateTime? selectedDate = null; + try + { + selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day)); + } + catch { } + if (selectedDate.HasValue) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedDate.Value.ToString("D")); + } + } + break; + case AttributeControlType.FileUpload: + { + Guid downloadGuid; + Guid.TryParse(form[controlId], out downloadGuid); + var download = _downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, download.DownloadGuid.ToString()); + } + } + break; + default: + break; + } + } + //validate conditional attributes (if specified) + foreach (var attribute in productAttributes) + { + var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributesXml); + if (conditionMet.HasValue && !conditionMet.Value) + { + attributesXml = _productAttributeParser.RemoveProductAttribute(attributesXml, attribute); + } + } + + #endregion + + return attributesXml; + } + + /// + /// Parse rental dates on the add product to order details page + /// + /// Form + /// Start date + /// End date + [NonAction] + protected virtual void ParseRentalDates(FormCollection form, + out DateTime? startDate, out DateTime? endDate) + { + startDate = null; + endDate = null; + + var ctrlStartDate = form["rental_start_date"]; + var ctrlEndDate = form["rental_end_date"]; + try + { + const string datePickerFormat = "MM/dd/yyyy"; + startDate = DateTime.ParseExact(ctrlStartDate, datePickerFormat, CultureInfo.InvariantCulture); + endDate = DateTime.ParseExact(ctrlEndDate, datePickerFormat, CultureInfo.InvariantCulture); + } + catch + { + } + } + + [NonAction] + protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (model == null) + throw new ArgumentNullException("model"); + + model.Id = order.Id; + model.InvoiceId = order.InvoiceId; + model.InvoiceDateUtc = order.InvoiceDateUtc; + model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); + model.OrderStatusId = order.OrderStatusId; + model.OrderGuid = order.OrderGuid; + var store = _storeService.GetStoreById(order.StoreId); + model.StoreName = store != null ? store.Name : "Unknown"; + model.CustomerId = order.CustomerId; + var customer = order.Customer; + model.CustomerInfo = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); + model.CustomerIp = order.CustomerIp; + model.VatNumber = order.VatNumber; + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); + model.AllowCustomersToSelectTaxDisplayType = _taxSettings.AllowCustomersToSelectTaxDisplayType; + model.TaxDisplayType = _taxSettings.TaxDisplayType; + + var affiliate = _affiliateService.GetAffiliateById(order.AffiliateId); + if (affiliate != null) + { + model.AffiliateId = affiliate.Id; + model.AffiliateName = affiliate.GetFullName(); + } + + //a vendor should have access only to his products + model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; + //custom values + model.CustomValues = order.DeserializeCustomValues(); + + #region Order totals + + var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + if (primaryStoreCurrency == null) + throw new Exception("Cannot load primary store currency"); + + //subtotal + model.OrderSubtotalInclTax = _priceFormatter.FormatPrice(order.OrderSubtotalInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); + model.OrderSubtotalExclTax = _priceFormatter.FormatPrice(order.OrderSubtotalExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + model.OrderSubtotalInclTaxValue = order.OrderSubtotalInclTax; + model.OrderSubtotalExclTaxValue = order.OrderSubtotalExclTax; + //discount (applied to order subtotal) + string orderSubtotalDiscountInclTaxStr = _priceFormatter.FormatPrice(order.OrderSubTotalDiscountInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); + string orderSubtotalDiscountExclTaxStr = _priceFormatter.FormatPrice(order.OrderSubTotalDiscountExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + if (order.OrderSubTotalDiscountInclTax > decimal.Zero) + model.OrderSubTotalDiscountInclTax = orderSubtotalDiscountInclTaxStr; + if (order.OrderSubTotalDiscountExclTax > decimal.Zero) + model.OrderSubTotalDiscountExclTax = orderSubtotalDiscountExclTaxStr; + model.OrderSubTotalDiscountInclTaxValue = order.OrderSubTotalDiscountInclTax; + model.OrderSubTotalDiscountExclTaxValue = order.OrderSubTotalDiscountExclTax; + + //shipping + model.OrderShippingInclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); + model.OrderShippingExclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + model.OrderShippingInclTaxValue = order.OrderShippingInclTax; + model.OrderShippingExclTaxValue = order.OrderShippingExclTax; + + //payment method additional fee + if (order.PaymentMethodAdditionalFeeInclTax > decimal.Zero) + { + model.PaymentMethodAdditionalFeeInclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); + model.PaymentMethodAdditionalFeeExclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + } + model.PaymentMethodAdditionalFeeInclTaxValue = order.PaymentMethodAdditionalFeeInclTax; + model.PaymentMethodAdditionalFeeExclTaxValue = order.PaymentMethodAdditionalFeeExclTax; + + + //tax + model.Tax = _priceFormatter.FormatPrice(order.OrderTax, true, false); + SortedDictionary taxRates = order.TaxRatesDictionary; + bool displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); + bool displayTax = !displayTaxRates; + foreach (var tr in order.TaxRatesDictionary) + { + model.TaxRates.Add(new OrderModel.TaxRate + { + Rate = _priceFormatter.FormatTaxRate(tr.Key), + Amount = _priceFormatter.FormatPrice(tr.Value.Amount, true, false), + DiscountAmount = _priceFormatter.FormatPrice(tr.Value.DiscountAmount, true, false), + BaseAmount = _priceFormatter.FormatPrice(tr.Value.BaseAmount, true, false), + VatAmount = _priceFormatter.FormatPrice(tr.Value.VatAmount, true, false), + }); + } + model.DisplayTaxRates = displayTaxRates; + model.DisplayTax = displayTax; + model.TaxValue = order.OrderTax; + model.TaxRatesValue = order.TaxRates; + + //discount + if (order.OrderDiscount > 0) + model.OrderTotalDiscount = _priceFormatter.FormatPrice(-order.OrderDiscount, true, false); + model.OrderTotalDiscountValue = order.OrderDiscount; + + //gift cards + foreach (var gcuh in order.GiftCardUsageHistory) + { + model.GiftCards.Add(new OrderModel.GiftCard + { + CouponCode = gcuh.GiftCard.GiftCardCouponCode, + Amount = _priceFormatter.FormatPrice(-gcuh.UsedValue, true, false), + }); + } + + //reward points + if (order.RedeemedRewardPointsEntry != null) + { + model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; + model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-order.RedeemedRewardPointsEntry.UsedAmount, true, false); + } + + //total + model.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); + model.OrderTotalValue = order.OrderTotal; + model.OrderAmount = _priceFormatter.FormatPrice(order.OrderAmount, true, false); + model.OrderAmountValue = order.OrderAmount; + model.OrderAmountIncl = _priceFormatter.FormatPrice(order.OrderAmountIncl, true, false); + model.OrderAmountInclValue = order.OrderAmountIncl; + + //refunded amount + if (order.RefundedAmount > decimal.Zero) + model.RefundedAmount = _priceFormatter.FormatPrice(order.RefundedAmount, true, false); + + //used discounts + var duh = _discountService.GetAllDiscountUsageHistory(orderId: order.Id); + foreach (var d in duh) + { + model.UsedDiscounts.Add(new OrderModel.UsedDiscountModel + { + DiscountId = d.DiscountId, + DiscountName = d.Discount.Name + }); + } + + //profit (hide for vendors) + if (_workContext.CurrentVendor == null) + { + var profit = _orderReportService.ProfitReport(orderId: order.Id); + model.Profit = _priceFormatter.FormatPrice(profit, true, false); + } + + #endregion + + #region Payment info + + if (order.AllowStoringCreditCardNumber) + { + //card type + model.CardType = _encryptionService.DecryptText(order.CardType); + //cardholder name + model.CardName = _encryptionService.DecryptText(order.CardName); + //card number + model.CardNumber = _encryptionService.DecryptText(order.CardNumber); + //cvv + model.CardCvv2 = _encryptionService.DecryptText(order.CardCvv2); + //expiry date + string cardExpirationMonthDecrypted = _encryptionService.DecryptText(order.CardExpirationMonth); + if (!String.IsNullOrEmpty(cardExpirationMonthDecrypted) && cardExpirationMonthDecrypted != "0") + model.CardExpirationMonth = cardExpirationMonthDecrypted; + string cardExpirationYearDecrypted = _encryptionService.DecryptText(order.CardExpirationYear); + if (!String.IsNullOrEmpty(cardExpirationYearDecrypted) && cardExpirationYearDecrypted != "0") + model.CardExpirationYear = cardExpirationYearDecrypted; + + model.AllowStoringCreditCardNumber = true; + } + else + { + string maskedCreditCardNumberDecrypted = _encryptionService.DecryptText(order.MaskedCreditCardNumber); + if (!String.IsNullOrEmpty(maskedCreditCardNumberDecrypted)) + model.CardNumber = maskedCreditCardNumberDecrypted; + } + + + //payment transaction info + model.AuthorizationTransactionId = order.AuthorizationTransactionId; + model.CaptureTransactionId = order.CaptureTransactionId; + model.SubscriptionTransactionId = order.SubscriptionTransactionId; + + //payment method info + var pm = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); + model.PaymentMethod = pm != null ? pm.PluginDescriptor.FriendlyName : order.PaymentMethodSystemName; + model.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); + + //payment method buttons + model.CanCancelOrder = _orderProcessingService.CanCancelOrder(order); + model.CanCapture = _orderProcessingService.CanCapture(order); + model.CanMarkOrderAsPaid = _orderProcessingService.CanMarkOrderAsPaid(order); + model.CanRefund = _orderProcessingService.CanRefund(order); + model.CanRefundOffline = _orderProcessingService.CanRefundOffline(order); + model.CanPartiallyRefund = _orderProcessingService.CanPartiallyRefund(order, decimal.Zero); + model.CanPartiallyRefundOffline = _orderProcessingService.CanPartiallyRefundOffline(order, decimal.Zero); + model.CanVoid = _orderProcessingService.CanVoid(order); + model.CanVoidOffline = _orderProcessingService.CanVoidOffline(order); + + model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + model.MaxAmountToRefund = order.OrderTotal - order.RefundedAmount; + + //recurring payment record + var recurringPayment = _orderService.SearchRecurringPayments(initialOrderId: order.Id, showHidden: true).FirstOrDefault(); + if (recurringPayment != null) + { + model.RecurringPaymentId = recurringPayment.Id; + } + #endregion + + #region Billing & shipping info + + model.BillingAddress = order.BillingAddress.ToModel(); + model.BillingAddress.FormattedCustomAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.BillingAddress.CustomAttributes); + model.BillingAddress.FirstNameEnabled = true; + model.BillingAddress.FirstNameRequired = true; + model.BillingAddress.LastNameEnabled = true; + model.BillingAddress.LastNameRequired = true; + model.BillingAddress.EmailEnabled = true; + model.BillingAddress.EmailRequired = true; + model.BillingAddress.CompanyEnabled = _addressSettings.CompanyEnabled; + model.BillingAddress.CompanyRequired = _addressSettings.CompanyRequired; + model.BillingAddress.CountryEnabled = _addressSettings.CountryEnabled; + model.BillingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled + model.BillingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.BillingAddress.CityEnabled = _addressSettings.CityEnabled; + model.BillingAddress.CityRequired = _addressSettings.CityRequired; + model.BillingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; + model.BillingAddress.StreetAddressRequired = _addressSettings.StreetAddressRequired; + model.BillingAddress.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; + model.BillingAddress.StreetAddress2Required = _addressSettings.StreetAddress2Required; + model.BillingAddress.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; + model.BillingAddress.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; + model.BillingAddress.PhoneEnabled = _addressSettings.PhoneEnabled; + model.BillingAddress.PhoneRequired = _addressSettings.PhoneRequired; + model.BillingAddress.FaxEnabled = _addressSettings.FaxEnabled; + model.BillingAddress.FaxRequired = _addressSettings.FaxRequired; + + model.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); ; + if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + model.IsShippable = true; + + model.PickUpInStore = order.PickUpInStore; + if (!order.PickUpInStore) + { + model.ShippingAddress = order.ShippingAddress.ToModel(); + model.ShippingAddress.FormattedCustomAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); + model.ShippingAddress.FirstNameEnabled = true; + model.ShippingAddress.FirstNameRequired = true; + model.ShippingAddress.LastNameEnabled = true; + model.ShippingAddress.LastNameRequired = true; + model.ShippingAddress.EmailEnabled = true; + model.ShippingAddress.EmailRequired = true; + model.ShippingAddress.CompanyEnabled = _addressSettings.CompanyEnabled; + model.ShippingAddress.CompanyRequired = _addressSettings.CompanyRequired; + model.ShippingAddress.CountryEnabled = _addressSettings.CountryEnabled; + model.ShippingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled + model.ShippingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.ShippingAddress.CityEnabled = _addressSettings.CityEnabled; + model.ShippingAddress.CityRequired = _addressSettings.CityRequired; + model.ShippingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; + model.ShippingAddress.StreetAddressRequired = _addressSettings.StreetAddressRequired; + model.ShippingAddress.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; + model.ShippingAddress.StreetAddress2Required = _addressSettings.StreetAddress2Required; + model.ShippingAddress.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; + model.ShippingAddress.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; + model.ShippingAddress.PhoneEnabled = _addressSettings.PhoneEnabled; + model.ShippingAddress.PhoneRequired = _addressSettings.PhoneRequired; + model.ShippingAddress.FaxEnabled = _addressSettings.FaxEnabled; + model.ShippingAddress.FaxRequired = _addressSettings.FaxRequired; + + model.ShippingAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", Server.UrlEncode(order.ShippingAddress.Address1 + " " + order.ShippingAddress.ZipPostalCode + " " + order.ShippingAddress.City + " " + (order.ShippingAddress.Country != null ? order.ShippingAddress.Country.Name : ""))); + } + else + { + if (order.PickupAddress != null) + { + model.PickupAddress = order.PickupAddress.ToModel(); + model.PickupAddressGoogleMapsUrl = string.Format("http://maps.google.com/maps?f=q&hl=en&ie=UTF8&oe=UTF8&geocode=&q={0}", + Server.UrlEncode(string.Format("{0} {1} {2} {3}", order.PickupAddress.Address1, order.PickupAddress.ZipPostalCode, order.PickupAddress.City, + order.PickupAddress.Country != null ? order.PickupAddress.Country.Name : string.Empty))); + } + } + model.ShippingMethod = order.ShippingMethod; + + model.CanAddNewShipments = order.HasItemsToAddToShipment(); + } + + #endregion + + #region Products + + model.CheckoutAttributeInfo = order.CheckoutAttributeDescription; + bool hasDownloadableItems = false; + var products = order.OrderItems; + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + products = products + .Where(orderItem => orderItem.Product.VendorId == _workContext.CurrentVendor.Id) + .ToList(); + } + foreach (var orderItem in products) + { + if (orderItem.Product.IsDownload) + hasDownloadableItems = true; + + var orderItemModel = new OrderModel.OrderItemModel + { + Id = orderItem.Id, + ProductId = orderItem.ProductId, + ProductName = orderItem.Product.Name, + Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), + Quantity = orderItem.Quantity, + IsDownload = orderItem.Product.IsDownload, + DownloadCount = orderItem.DownloadCount, + DownloadActivationType = orderItem.Product.DownloadActivationType, + IsDownloadActivated = orderItem.IsDownloadActivated, + VatRate = orderItem.VatRate + }; + //picture + var orderItemPicture = orderItem.Product.GetProductPicture(orderItem.AttributesXml, _pictureService, _productAttributeParser); + orderItemModel.PictureThumbnailUrl = _pictureService.GetPictureUrl(orderItemPicture, 75, true); + + //license file + if (orderItem.LicenseDownloadId.HasValue) + { + var licenseDownload = _downloadService.GetDownloadById(orderItem.LicenseDownloadId.Value); + if (licenseDownload != null) + { + orderItemModel.LicenseDownloadGuid = licenseDownload.DownloadGuid; + } + } + //vendor + var vendor = _vendorService.GetVendorById(orderItem.Product.VendorId); + orderItemModel.VendorName = vendor != null ? vendor.Name : ""; + + //unit price + orderItemModel.UnitPriceInclTaxValue = orderItem.UnitPriceInclTax; + orderItemModel.UnitPriceExclTaxValue = orderItem.UnitPriceExclTax; + orderItemModel.UnitPriceInclTax = _priceFormatter.FormatPrice(orderItem.UnitPriceInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); + orderItemModel.UnitPriceExclTax = _priceFormatter.FormatPrice(orderItem.UnitPriceExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); + //discounts + orderItemModel.DiscountInclTaxValue = orderItem.DiscountAmountInclTax; + orderItemModel.DiscountExclTaxValue = orderItem.DiscountAmountExclTax; + orderItemModel.DiscountInclTax = _priceFormatter.FormatPrice(orderItem.DiscountAmountInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); + orderItemModel.DiscountExclTax = _priceFormatter.FormatPrice(orderItem.DiscountAmountExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); + //subtotal + orderItemModel.SubTotalInclTaxValue = orderItem.PriceInclTax; + orderItemModel.SubTotalExclTaxValue = orderItem.PriceExclTax; + orderItemModel.SubTotalInclTax = _priceFormatter.FormatPrice(orderItem.PriceInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true, true); + orderItemModel.SubTotalExclTax = _priceFormatter.FormatPrice(orderItem.PriceExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, true); + + orderItemModel.AttributeInfo = orderItem.AttributeDescription; + if (orderItem.Product.IsRecurring) + orderItemModel.RecurringInfo = string.Format(_localizationService.GetResource("Admin.Orders.Products.RecurringPeriod"), orderItem.Product.RecurringCycleLength, orderItem.Product.RecurringCyclePeriod.GetLocalizedEnum(_localizationService, _workContext)); + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + orderItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + } + + //return requests + orderItemModel.ReturnRequests = _returnRequestService + .SearchReturnRequests(orderItemId: orderItem.Id) + .Select(item => new OrderModel.OrderItemModel.ReturnRequestBriefModel + { + CustomNumber = item.CustomNumber, + Id = item.Id + }).ToList(); + + //gift cards + orderItemModel.PurchasedGiftCardIds = _giftCardService.GetGiftCardsByPurchasedWithOrderItemId(orderItem.Id) + .Select(gc => gc.Id).ToList(); + + model.Items.Add(orderItemModel); + } + model.HasDownloadableProducts = hasDownloadableItems; + #endregion + } + + [NonAction] + protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAddProductToOrderModel(int orderId, int productId) + { + var product = _productService.GetProductById(productId); + if (product == null) + throw new ArgumentException("No product found with the specified id"); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + throw new ArgumentException("No order found with the specified id"); + + var presetQty = 1; + var presetPrice = _priceCalculationService.GetFinalPrice(product, order.Customer, decimal.Zero, true, presetQty); + decimal taxRate; + decimal presetPriceInclTax = _taxService.GetProductPrice(product, presetPrice, true, order.Customer, out taxRate); + decimal presetPriceExclTax = _taxService.GetProductPrice(product, presetPrice, false, order.Customer, out taxRate); + + var model = new OrderModel.AddOrderProductModel.ProductDetailsModel + { + ProductId = productId, + OrderId = orderId, + Name = product.Name, + ProductType = product.ProductType, + UnitPriceExclTax = presetPriceExclTax, + UnitPriceInclTax = presetPriceInclTax, + Quantity = presetQty, + SubTotalExclTax = presetPriceExclTax, + SubTotalInclTax = presetPriceInclTax, + AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder, + VatRate = taxRate + }; + + //attributes + string attributesXml = ""; + var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in attributes) + { + var attributeModel = new OrderModel.AddOrderProductModel.ProductAttributeModel + { + Id = attribute.Id, + ProductAttributeId = attribute.ProductAttributeId, + Name = attribute.ProductAttribute.Name, + TextPrompt = attribute.TextPrompt, + IsRequired = attribute.IsRequired, + AttributeControlType = attribute.AttributeControlType, + HasCondition = !String.IsNullOrEmpty(attribute.ConditionAttributeXml) + }; + if (!String.IsNullOrEmpty(attribute.ValidationFileAllowedExtensions)) + { + attributeModel.AllowedFileExtensions = attribute.ValidationFileAllowedExtensions + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + } + + if (attribute.ShouldHaveValues()) + { + //values + var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); + foreach (var attributeValue in attributeValues) + { + var attributeValueModel = new OrderModel.AddOrderProductModel.ProductAttributeValueModel + { + Id = attributeValue.Id, + Name = attributeValue.Name, + IsPreSelected = attributeValue.IsPreSelected + }; + attributeModel.Values.Add(attributeValueModel); + } + + //creating XML for "read-only checkboxes" attributes + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + + model.ProductAttributes.Add(attributeModel); + } + + //Get final price using attributes + List scDiscounts; + decimal discountAmount; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + order.Customer, + ShoppingCartType.ShoppingCart, + 1, ref attributesXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + decimal finalPriceInclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, true, order.Customer, out taxRate)); + decimal finalPriceExclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, false, order.Customer, out taxRate)); + model.UnitPriceExclTax = finalPriceExclTax; + model.UnitPriceInclTax = finalPriceInclTax; + model.SubTotalExclTax = RoundingHelper.RoundAmount(finalPriceExclTax); + model.SubTotalInclTax = RoundingHelper.RoundAmount(finalPriceInclTax); + + model.HasCondition = model.ProductAttributes.Any(a => a.HasCondition); + //gift card + model.GiftCard.IsGiftCard = product.IsGiftCard; + if (model.GiftCard.IsGiftCard) + { + model.GiftCard.GiftCardType = product.GiftCardType; + } + //rental + model.IsRental = product.IsRental; + return model; + } + + [NonAction] + protected virtual ShipmentModel PrepareShipmentModel(Shipment shipment, bool prepareProducts, bool prepareShipmentEvent = false) + { + //measures + var baseWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var baseWeightIn = baseWeight != null ? baseWeight.Name : ""; + var baseDimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId); + var baseDimensionIn = baseDimension != null ? baseDimension.Name : ""; + + var model = new ShipmentModel + { + Id = shipment.Id, + OrderId = shipment.OrderId, + TrackingNumber = shipment.TrackingNumber, + TotalWeight = shipment.TotalWeight.HasValue ? string.Format("{0:F2} [{1}]", shipment.TotalWeight, baseWeightIn) : "", + ShippedDate = shipment.ShippedDateUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc).ToString() : _localizationService.GetResource("Admin.Orders.Shipments.ShippedDate.NotYet"), + ShippedDateUtc = shipment.ShippedDateUtc, + CanShip = !shipment.ShippedDateUtc.HasValue, + DeliveryDate = shipment.DeliveryDateUtc.HasValue ? _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc).ToString() : _localizationService.GetResource("Admin.Orders.Shipments.DeliveryDate.NotYet"), + DeliveryDateUtc = shipment.DeliveryDateUtc, + CanDeliver = shipment.ShippedDateUtc.HasValue && !shipment.DeliveryDateUtc.HasValue, + AdminComment = shipment.AdminComment, + }; + + if (prepareProducts) + { + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + //quantities + var qtyInThisShipment = shipmentItem.Quantity; + var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + var qtyOrdered = orderItem.Quantity; + var qtyInAllShipments = orderItem.GetTotalNumberOfItemsInAllShipment(); + + var warehouse = _shippingService.GetWarehouseById(shipmentItem.WarehouseId); + var shipmentItemModel = new ShipmentModel.ShipmentItemModel + { + Id = shipmentItem.Id, + OrderItemId = orderItem.Id, + ProductId = orderItem.ProductId, + ProductName = orderItem.Product.Name, + Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), + AttributeInfo = orderItem.AttributeDescription, + ShippedFromWarehouse = warehouse != null ? warehouse.Name : null, + ShipSeparately = orderItem.Product.ShipSeparately, + ItemWeight = orderItem.ItemWeight.HasValue ? string.Format("{0:F2} [{1}]", orderItem.ItemWeight, baseWeightIn) : "", + ItemDimensions = string.Format("{0:F2} x {1:F2} x {2:F2} [{3}]", orderItem.Product.Length, orderItem.Product.Width, orderItem.Product.Height, baseDimensionIn), + QuantityOrdered = qtyOrdered, + QuantityInThisShipment = qtyInThisShipment, + QuantityInAllShipments = qtyInAllShipments, + QuantityToAdd = maxQtyToAdd, + }; + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + shipmentItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + } + + model.Items.Add(shipmentItemModel); + } + } + + if (prepareShipmentEvent && !String.IsNullOrEmpty(shipment.TrackingNumber)) + { + var shipmentTracker = shipment.GetShipmentTracker(_shippingService, _shippingSettings); + if (shipmentTracker != null) + { + model.TrackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber); + if (_shippingSettings.DisplayShipmentEventsToStoreOwner) + { + var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber); + if (shipmentEvents != null) + foreach (var shipmentEvent in shipmentEvents) + { + var shipmentStatusEventModel = new ShipmentModel.ShipmentStatusEventModel(); + var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); + shipmentStatusEventModel.Country = shipmentEventCountry != null + ? shipmentEventCountry.GetLocalized(x => x.Name) + : shipmentEvent.CountryCode; + shipmentStatusEventModel.Date = shipmentEvent.Date; + shipmentStatusEventModel.EventName = shipmentEvent.EventName; + shipmentStatusEventModel.Location = shipmentEvent.Location; + model.ShipmentStatusEvents.Add(shipmentStatusEventModel); + } + } + } + } + + return model; + } + + #endregion + + #region Order list + + public ActionResult Index() + { + return RedirectToAction("List"); + } + + public ActionResult List( + [ModelBinder(typeof(CommaSeparatedModelBinder))] List orderStatusIds = null, + [ModelBinder(typeof(CommaSeparatedModelBinder))] List paymentStatusIds = null, + [ModelBinder(typeof(CommaSeparatedModelBinder))] List shippingStatusIds = null) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //order statuses + var model = new OrderListModel(); + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.AvailableOrderStatuses.Insert(0, new SelectListItem + { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); + if (orderStatusIds != null && orderStatusIds.Any()) + { + foreach (var item in model.AvailableOrderStatuses.Where(os => orderStatusIds.Contains(os.Value))) + item.Selected = true; + model.AvailableOrderStatuses.First().Selected = false; + } + //payment statuses + model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.AvailablePaymentStatuses.Insert(0, new SelectListItem + { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); + if (paymentStatusIds != null && paymentStatusIds.Any()) + { + foreach (var item in model.AvailablePaymentStatuses.Where(ps => paymentStatusIds.Contains(ps.Value))) + item.Selected = true; + model.AvailablePaymentStatuses.First().Selected = false; + } + + //shipping statuses + model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.AvailableShippingStatuses.Insert(0, new SelectListItem + { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0", Selected = true }); + if (shippingStatusIds != null && shippingStatusIds.Any()) + { + foreach (var item in model.AvailableShippingStatuses.Where(ss => shippingStatusIds.Contains(ss.Value))) + item.Selected = true; + model.AvailableShippingStatuses.First().Selected = false; + } + + //stores + model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var s in _storeService.GetAllStores()) + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + + //vendors + model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); + foreach (var v in vendors) + model.AvailableVendors.Add(v); + + //warehouses + model.AvailableWarehouses.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var w in _shippingService.GetAllWarehouses()) + model.AvailableWarehouses.Add(new SelectListItem { Text = w.Name, Value = w.Id.ToString() }); + + //payment methods + model.AvailablePaymentMethods.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "" }); + foreach (var pm in _paymentService.LoadAllPaymentMethods()) + model.AvailablePaymentMethods.Add(new SelectListItem { Text = pm.PluginDescriptor.FriendlyName, Value = pm.PluginDescriptor.SystemName }); + + //billing countries + foreach (var c in _countryService.GetAllCountriesForBilling(showHidden: true)) + { + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); + } + model.AvailableCountries.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //a vendor should have access only to orders with his products + model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; + + return View(model); + } + + [HttpPost] + public ActionResult OrderList(DataSourceRequest command, OrderListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + model.VendorId = _workContext.CurrentVendor.Id; + } + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; + var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; + var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; + + var filterByProductId = 0; + var product = _productService.GetProductById(model.ProductId); + if (product != null && HasAccessToProduct(product)) + filterByProductId = model.ProductId; + + //load orders + var orders = _orderService.SearchOrders(storeId: model.StoreId, + vendorId: model.VendorId, + productId: filterByProductId, + warehouseId: model.WarehouseId, + paymentMethodSystemName: model.PaymentMethodSystemName, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes, + pageIndex: command.Page - 1, + pageSize: command.PageSize); + var gridModel = new DataSourceResult + { + Data = orders.Select(x => + { + var store = _storeService.GetStoreById(x.StoreId); + return new OrderModel + { + Id = x.Id, + StoreName = store != null ? store.Name : "Unknown", + OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + OrderStatusId = x.OrderStatusId, + PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), + PaymentStatusId = x.PaymentStatusId, + ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), + ShippingStatusId = x.ShippingStatusId, + CustomerEmail = x.BillingAddress.Email, + CustomerFullName = string.Format("{0} {1}", x.BillingAddress.FirstName, x.BillingAddress.LastName), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }; + }), + Total = orders.TotalCount + }; + + //summary report + //currently we do not support productId and warehouseId parameters for this report + var reportSummary = _orderReportService.GetOrderAverageReportLine( + storeId: model.StoreId, + vendorId: model.VendorId, + orderId: 0, + paymentMethodSystemName: model.PaymentMethodSystemName, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + startTimeUtc: startDateValue, + endTimeUtc: endDateValue, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes); + + var profit = _orderReportService.ProfitReport( + storeId: model.StoreId, + vendorId: model.VendorId, + paymentMethodSystemName: model.PaymentMethodSystemName, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + startTimeUtc: startDateValue, + endTimeUtc: endDateValue, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes); + var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + if (primaryStoreCurrency == null) + throw new Exception("Cannot load primary store currency"); + + gridModel.ExtraData = new OrderAggreratorModel + { + aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), + aggregatorshipping = _priceFormatter.FormatShippingPrice(reportSummary.SumShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false), + aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), + aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) + }; + return new JsonResult + { + Data = gridModel + }; + } + + [HttpPost, ActionName("List")] + [FormValueRequired("go-to-order-by-number")] + public ActionResult GoToOrderId(OrderListModel model) + { + var order = _orderService.GetOrderById(model.GoDirectlyToNumber); + if (order == null) + return List(); + + return RedirectToAction("Edit", "Order", new { id = order.Id }); + } + + public ActionResult ProductSearchAutoComplete(string term) + { + const int searchTermMinimumLength = 3; + if (String.IsNullOrWhiteSpace(term) || term.Length < searchTermMinimumLength) + return Content(""); + + //a vendor should have access only to his products + var vendorId = 0; + if (_workContext.CurrentVendor != null) + { + vendorId = _workContext.CurrentVendor.Id; + } + + //products + const int productNumber = 15; + var products = _productService.SearchProducts( + vendorId: vendorId, + keywords: term, + pageSize: productNumber, + showHidden: true); + + var result = (from p in products + select new + { + label = p.Name, + productid = p.Id + }) + .ToList(); + return Json(result, JsonRequestBehavior.AllowGet); + } + + #endregion + + #region Export / Import + + [HttpPost, ActionName("List")] + [FormValueRequired("exportxml-all")] + public ActionResult ExportXmlAll(OrderListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor cannot export orders + if (_workContext.CurrentVendor != null) + return AccessDeniedView(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; + var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; + var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; + + var filterByProductId = 0; + var product = _productService.GetProductById(model.ProductId); + if (product != null && HasAccessToProduct(product)) + filterByProductId = model.ProductId; + + //load orders + var orders = _orderService.SearchOrders(storeId: model.StoreId, + vendorId: model.VendorId, + productId: filterByProductId, + warehouseId: model.WarehouseId, + paymentMethodSystemName: model.PaymentMethodSystemName, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes); + + try + { + var xml = _exportManager.ExportOrdersToXml(orders); + return new XmlDownloadResult(xml, "orders.xml"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + [HttpPost] + public ActionResult ExportXmlSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor cannot export orders + if (_workContext.CurrentVendor != null) + return AccessDeniedView(); + + var orders = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + orders.AddRange(_orderService.GetOrdersByIds(ids)); + } + + var xml = _exportManager.ExportOrdersToXml(orders); + return new XmlDownloadResult(xml, "orders.xml"); + } + + [HttpPost, ActionName("List")] + [FormValueRequired("exportexcel-all")] + public ActionResult ExportExcelAll(OrderListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor cannot export orders + if (_workContext.CurrentVendor != null) + return AccessDeniedView(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; + var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; + var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; + + var filterByProductId = 0; + var product = _productService.GetProductById(model.ProductId); + if (product != null && HasAccessToProduct(product)) + filterByProductId = model.ProductId; + + //load orders + var orders = _orderService.SearchOrders(storeId: model.StoreId, + vendorId: model.VendorId, + productId: filterByProductId, + warehouseId: model.WarehouseId, + paymentMethodSystemName: model.PaymentMethodSystemName, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes); + + try + { + byte[] bytes = _exportManager.ExportOrdersToXlsx(orders); + return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + [HttpPost] + public ActionResult ExportExcelSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor cannot export orders + if (_workContext.CurrentVendor != null) + return AccessDeniedView(); + + var orders = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + orders.AddRange(_orderService.GetOrdersByIds(ids)); + } + + try + { + byte[] bytes = _exportManager.ExportOrdersToXlsx(orders); + return File(bytes, MimeTypes.TextXlsx, "orders.xlsx"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + #endregion + + #region Order details + + #region Payments and other order workflow + + [HttpPost, ActionName("Edit")] + [FormValueRequired("cancelorder")] + public ActionResult CancelOrder(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + _orderProcessingService.CancelOrder(order, true); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("captureorder")] + public ActionResult CaptureOrder(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + var errors = _orderProcessingService.Capture(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + foreach (var error in errors) + ErrorNotification(error, false); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("markorderaspaid")] + public ActionResult MarkOrderAsPaid(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + _orderProcessingService.MarkOrderAsPaid(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("refundorder")] + public ActionResult RefundOrder(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + var errors = _orderProcessingService.Refund(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + foreach (var error in errors) + ErrorNotification(error, false); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("refundorderoffline")] + public ActionResult RefundOrderOffline(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + _orderProcessingService.RefundOffline(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("voidorder")] + public ActionResult VoidOrder(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + var errors = _orderProcessingService.Void(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + foreach (var error in errors) + ErrorNotification(error, false); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("voidorderoffline")] + public ActionResult VoidOrderOffline(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + _orderProcessingService.VoidOffline(order); + LogEditOrder(order.Id); + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + return View(model); + } + catch (Exception exc) + { + //error + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + public ActionResult PartiallyRefundOrderPopup(int id, bool online) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + + return View(model); + } + + [HttpPost] + [FormValueRequired("partialrefundorder")] + public ActionResult PartiallyRefundOrderPopup(string btnId, string formId, int id, bool online, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + decimal amountToRefund = model.AmountToRefund; + if (amountToRefund <= decimal.Zero) + throw new NopException("Enter amount to refund"); + + decimal maxAmountToRefund = order.OrderTotal - order.RefundedAmount; + if (amountToRefund > maxAmountToRefund) + amountToRefund = maxAmountToRefund; + + var errors = new List(); + if (online) + errors = _orderProcessingService.PartiallyRefund(order, amountToRefund).ToList(); + else + _orderProcessingService.PartiallyRefundOffline(order, amountToRefund); + + LogEditOrder(order.Id); + + if (!errors.Any()) + { + //success + ViewBag.RefreshPage = true; + ViewBag.btnId = btnId; + ViewBag.formId = formId; + + PrepareOrderDetailsModel(model, order); + return View(model); + } + + //error + PrepareOrderDetailsModel(model, order); + foreach (var error in errors) + ErrorNotification(error, false); + return View(model); + } + catch (Exception exc) + { + //error + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("btnSaveOrderStatus")] + public ActionResult ChangeOrderStatus(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + try + { + order.OrderStatusId = model.OrderStatusId; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order status has been edited. New status: {0}", order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext)), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + return View(model); + } + catch (Exception exc) + { + //error + model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + ErrorNotification(exc, false); + return View(model); + } + } + + #endregion + + #region Edit, delete + + public ActionResult Edit(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null || order.Deleted) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) + return RedirectToAction("List"); + + 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); + } + + [HttpPost] + public ActionResult Delete(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + _orderProcessingService.DeleteOrder(order); + + //activity log + _customerActivityService.InsertActivity("DeleteOrder", _localizationService.GetResource("ActivityLog.DeleteOrder"), order.Id); + + return RedirectToAction("List"); + } + + public ActionResult PdfInvoice(int orderId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor should have access only to his products + var vendorId = 0; + if (_workContext.CurrentVendor != null) + { + vendorId = _workContext.CurrentVendor.Id; + } + + var order = _orderService.GetOrderById(orderId); + var orders = new List(); + orders.Add(order); + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, vendorId); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, string.Format("order_{0}.pdf", order.Id)); + } + + [HttpPost, ActionName("List")] + [FormValueRequired("pdf-invoice-all")] + public ActionResult PdfInvoiceAll(OrderListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + model.VendorId = _workContext.CurrentVendor.Id; + } + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; + var paymentStatusIds = !model.PaymentStatusIds.Contains(0) ? model.PaymentStatusIds : null; + var shippingStatusIds = !model.ShippingStatusIds.Contains(0) ? model.ShippingStatusIds : null; + + var filterByProductId = 0; + var product = _productService.GetProductById(model.ProductId); + if (product != null && HasAccessToProduct(product)) + filterByProductId = model.ProductId; + + //load orders + var orders = _orderService.SearchOrders(storeId: model.StoreId, + vendorId: model.VendorId, + productId: filterByProductId, + warehouseId: model.WarehouseId, + paymentMethodSystemName: model.PaymentMethodSystemName, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + billingEmail: model.BillingEmail, + billingLastName: model.BillingLastName, + billingCountryId: model.BillingCountryId, + orderNotes: model.OrderNotes); + + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, model.VendorId); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, "orders.pdf"); + } + + [HttpPost] + public ActionResult PdfInvoiceSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var orders = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + orders.AddRange(_orderService.GetOrdersByIds(ids)); + } + + //a vendor should have access only to his products + var vendorId = 0; + if (_workContext.CurrentVendor != null) + { + orders = orders.Where(HasAccessToOrder).ToList(); + vendorId = _workContext.CurrentVendor.Id; + } + + //ensure that we at least one order selected + if (!orders.Any()) + { + ErrorNotification(_localizationService.GetResource("Admin.Orders.PdfInvoice.NoOrders")); + return RedirectToAction("List"); + } + + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintOrdersToPdf(stream, orders, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id, vendorId); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, "orders.pdf"); + } + + //currently we use this method on the add product to order details pages + [HttpPost] + [ValidateInput(false)] + public ActionResult ProductDetails_AttributeChange(int productId, bool validateAttributeConditions, + FormCollection form) + { + var product = _productService.GetProductById(productId); + if (product == null) + return new NullJsonResult(); + + var attributeXml = ParseProductAttributes(product, form); + + //Get final price using attributes + decimal finalPriceInclTax = decimal.Zero; decimal finalPriceExclTax = decimal.Zero; + decimal finalSubInclTax = decimal.Zero; decimal finalSubExclTax = decimal.Zero; + + List scDiscounts; + decimal discountAmount; + decimal taxRate; + int quantity; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributeXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + finalPriceInclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, true, _workContext.CurrentCustomer, out taxRate)); + finalPriceExclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, false, _workContext.CurrentCustomer, out taxRate)); + finalSubInclTax = RoundingHelper.RoundAmount(finalPriceInclTax); + finalSubExclTax = RoundingHelper.RoundAmount(finalPriceExclTax); + if (int.TryParse(form["Quantity"], out quantity)) + { + finalSubInclTax = RoundingHelper.RoundAmount(_taxService.GetProductPrice(product, finalPriceInclTax * quantity, true, _workContext.CurrentCustomer, out taxRate)); + finalSubExclTax = RoundingHelper.RoundAmount(_taxService.GetProductPrice(product, finalPriceInclTax * quantity, false, _workContext.CurrentCustomer, out taxRate)); + } + + //conditional attributes + var enabledAttributeMappingIds = new List(); + var disabledAttributeMappingIds = new List(); + if (validateAttributeConditions) + { + var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in attributes) + { + var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributeXml); + if (conditionMet.HasValue) + { + if (conditionMet.Value) + enabledAttributeMappingIds.Add(attribute.Id); + else + disabledAttributeMappingIds.Add(attribute.Id); + } + } + } + + return Json(new + { + enabledattributemappingids = enabledAttributeMappingIds.ToArray(), + disabledattributemappingids = disabledAttributeMappingIds.ToArray(), + finalPriceInclTax, + finalPriceExclTax, + finalSubInclTax, + finalSubExclTax + }); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("btnSaveCC")] + public ActionResult EditCreditCardInfo(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + if (order.AllowStoringCreditCardNumber) + { + string cardType = model.CardType; + string cardName = model.CardName; + string cardNumber = model.CardNumber; + string cardCvv2 = model.CardCvv2; + string cardExpirationMonth = model.CardExpirationMonth; + string cardExpirationYear = model.CardExpirationYear; + + order.CardType = _encryptionService.EncryptText(cardType); + order.CardName = _encryptionService.EncryptText(cardName); + order.CardNumber = _encryptionService.EncryptText(cardNumber); + order.MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(cardNumber)); + order.CardCvv2 = _encryptionService.EncryptText(cardCvv2); + order.CardExpirationMonth = _encryptionService.EncryptText(cardExpirationMonth); + order.CardExpirationYear = _encryptionService.EncryptText(cardExpirationYear); + _orderService.UpdateOrder(order); + } + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Credit card info has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + PrepareOrderDetailsModel(model, order); + return View(model); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("btnSaveOrderTotals")] + public ActionResult EditOrderTotals(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + order.OrderSubtotalInclTax = model.OrderSubtotalInclTaxValue; + order.OrderSubtotalExclTax = model.OrderSubtotalExclTaxValue; + order.OrderSubTotalDiscountInclTax = model.OrderSubTotalDiscountInclTaxValue; + order.OrderSubTotalDiscountExclTax = model.OrderSubTotalDiscountExclTaxValue; + order.OrderShippingInclTax = model.OrderShippingInclTaxValue; + order.OrderShippingExclTax = model.OrderShippingExclTaxValue; + order.PaymentMethodAdditionalFeeInclTax = model.PaymentMethodAdditionalFeeInclTaxValue; + order.PaymentMethodAdditionalFeeExclTax = model.PaymentMethodAdditionalFeeExclTaxValue; + order.TaxRates = model.TaxRatesValue; + order.OrderTax = model.TaxValue; + order.OrderDiscount = model.OrderTotalDiscountValue; + order.OrderTotal = model.OrderTotalValue; + order.OrderAmount = model.OrderAmountValue; + order.OrderAmountIncl = model.OrderAmountInclValue; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order totals have been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + PrepareOrderDetailsModel(model, order); + return View(model); + } + [HttpPost, ActionName("Edit")] + [FormValueRequired("btnSaveIV")] + public ActionResult EditInvoiceInfo(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + //assign invoice id when null + if (model.InvoiceId == null) + { + order.InvoiceId = _orderProcessingService.GetInvoiceId(); + order.InvoiceDateUtc = DateTime.UtcNow; + //not needed, we use redirection now + //ModelState.SetModelValue("InvoiceId", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceId"].Value.Culture)); + //ModelState.SetModelValue("InvoiceDateUtc", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceDateUtc"].Value.Culture)); + } + else + { + order.InvoiceId = model.InvoiceId; + order.InvoiceDateUtc = model.InvoiceDateUtc; + } + + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Invoice data has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + //use redirect instead of passing the new model to have correct browser refresh + //PrepareOrderDetailsModel(model, order); + //return View(model); + return RedirectToAction("Edit", "Order", new { id = id }); + } + + //something failed, redisplay form + return View(model); + } + [HttpPost, ActionName("Edit")] + [FormValueRequired("save-shipping-method")] + public ActionResult EditShippingMethod(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + order.ShippingMethod = model.ShippingMethod; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Shipping method has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + PrepareOrderDetailsModel(model, order); + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired(FormValueRequirement.StartsWith, "btnSaveOrderItem")] + [ValidateInput(false)] + public ActionResult EditOrderItem(int id, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + //get order item identifier + int orderItemId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("btnSaveOrderItem", StringComparison.InvariantCultureIgnoreCase)) + orderItemId = Convert.ToInt32(formValue.Substring("btnSaveOrderItem".Length)); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + + decimal unitPriceInclTax, unitPriceExclTax, discountInclTax, discountExclTax,priceInclTax,priceExclTax; + int quantity; decimal vatrate; + if (!decimal.TryParse(form["pvUnitPriceInclTax" + orderItemId], out unitPriceInclTax)) + unitPriceInclTax = orderItem.UnitPriceInclTax; + if (!decimal.TryParse(form["pvUnitPriceExclTax" + orderItemId], out unitPriceExclTax)) + unitPriceExclTax = orderItem.UnitPriceExclTax; + if (!int.TryParse(form["pvQuantity" + orderItemId], out quantity)) + quantity = orderItem.Quantity; + if (!decimal.TryParse(form["pvDiscountInclTax" + orderItemId], out discountInclTax)) + discountInclTax = orderItem.DiscountAmountInclTax; + if (!decimal.TryParse(form["pvDiscountExclTax" + orderItemId], out discountExclTax)) + discountExclTax = orderItem.DiscountAmountExclTax; + if (!decimal.TryParse(form["pvPriceInclTax" + orderItemId], out priceInclTax)) + priceInclTax = orderItem.PriceInclTax; + if (!decimal.TryParse(form["pvPriceExclTax" + orderItemId], out priceExclTax)) + priceExclTax = orderItem.PriceExclTax; + if (!decimal.TryParse(form["pvVatRate" + orderItemId], out vatrate)) + vatrate = orderItem.VatRate; + + if (quantity > 0) + { + int qtyDifference = orderItem.Quantity - quantity; + + if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) + { + orderItem.UnitPriceInclTax = unitPriceInclTax; + orderItem.UnitPriceExclTax = unitPriceExclTax; + orderItem.Quantity = quantity; + orderItem.DiscountAmountInclTax = discountInclTax; + orderItem.DiscountAmountExclTax = discountExclTax; + orderItem.PriceInclTax = priceInclTax; + orderItem.PriceExclTax = priceExclTax; + orderItem.VatRate = vatrate; + _orderService.UpdateOrder(order); + } + + //adjust inventory + _productService.AdjustInventory(orderItem.Product, qtyDifference, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditOrder"), order.Id)); + + } + else + { + //adjust inventory + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrderItem"), order.Id)); + + //delete item + _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, + VatRate = vatrate + }; + _orderProcessingService.UpdateOrderTotals(updateOrderParameters); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order item has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + model.Warnings = updateOrderParameters.Warnings; + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired(FormValueRequirement.StartsWith, "btnDeleteOrderItem")] + [ValidateInput(false)] + public ActionResult DeleteOrderItem(int id, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + //get order item identifier + int orderItemId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("btnDeleteOrderItem", StringComparison.InvariantCultureIgnoreCase)) + orderItemId = Convert.ToInt32(formValue.Substring("btnDeleteOrderItem".Length)); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + if (_giftCardService.GetGiftCardsByPurchasedWithOrderItemId(orderItem.Id).Any()) + { + //we cannot delete an order item with associated gift cards + //a store owner should delete them first + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + + ErrorNotification(_localizationService.GetResource("Admin.Orders.OrderItem.DeleteAssociatedGiftCardRecordError"), false); + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + + } + else + { + //adjust inventory + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrderItem"), order.Id)); + + //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 + { + Note = "Order item has been deleted", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + model.Warnings = updateOrderParameters.Warnings; + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired(FormValueRequirement.StartsWith, "btnResetDownloadCount")] + [ValidateInput(false)] + public ActionResult ResetDownloadCount(int id, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //get order item identifier + int orderItemId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("btnResetDownloadCount", StringComparison.InvariantCultureIgnoreCase)) + orderItemId = Convert.ToInt32(formValue.Substring("btnResetDownloadCount".Length)); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + //ensure a vendor has access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) + return RedirectToAction("List"); + + orderItem.DownloadCount = 0; + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired(FormValueRequirement.StartsWith, "btnPvActivateDownload")] + [ValidateInput(false)] + public ActionResult ActivateDownloadItem(int id, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //get order item identifier + int orderItemId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("btnPvActivateDownload", StringComparison.InvariantCultureIgnoreCase)) + orderItemId = Convert.ToInt32(formValue.Substring("btnPvActivateDownload".Length)); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + //ensure a vendor has access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) + return RedirectToAction("List"); + + orderItem.IsDownloadActivated = !orderItem.IsDownloadActivated; + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + var model = new OrderModel(); + PrepareOrderDetailsModel(model, order); + + //selected tab + SaveSelectedTabName(persistForTheNextRequest: false); + + return View(model); + } + + public ActionResult UploadLicenseFilePopup(int id, int orderItemId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == orderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + if (!orderItem.Product.IsDownload) + throw new ArgumentException("Product is not downloadable"); + + //ensure a vendor has access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) + return RedirectToAction("List"); + + var model = new OrderModel.UploadLicenseModel + { + LicenseDownloadId = orderItem.LicenseDownloadId.HasValue ? orderItem.LicenseDownloadId.Value : 0, + OrderId = order.Id, + OrderItemId = orderItem.Id + }; + + return View(model); + } + + [HttpPost] + [FormValueRequired("uploadlicense")] + public ActionResult UploadLicenseFilePopup(string btnId, string formId, OrderModel.UploadLicenseModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(model.OrderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + //ensure a vendor has access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) + return RedirectToAction("List"); + + //attach license + if (model.LicenseDownloadId > 0) + orderItem.LicenseDownloadId = model.LicenseDownloadId; + else + orderItem.LicenseDownloadId = null; + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + //success + ViewBag.RefreshPage = true; + ViewBag.btnId = btnId; + ViewBag.formId = formId; + + return View(model); + } + + [HttpPost, ActionName("UploadLicenseFilePopup")] + [FormValueRequired("deletelicense")] + public ActionResult DeleteLicenseFilePopup(string btnId, string formId, OrderModel.UploadLicenseModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(model.OrderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + var orderItem = order.OrderItems.FirstOrDefault(x => x.Id == model.OrderItemId); + if (orderItem == null) + throw new ArgumentException("No order item found with the specified id"); + + //ensure a vendor has access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) + return RedirectToAction("List"); + + //attach license + orderItem.LicenseDownloadId = null; + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + //success + ViewBag.RefreshPage = true; + ViewBag.btnId = btnId; + ViewBag.formId = formId; + + return View(model); + } + + public ActionResult AddProductToOrder(int orderId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = orderId }); + + var model = new OrderModel.AddOrderProductModel(); + model.OrderId = orderId; + //categories + model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); + foreach (var c in categories) + model.AvailableCategories.Add(c); + + //manufacturers + model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); + foreach (var m in manufacturers) + model.AvailableManufacturers.Add(m); + + //product types + model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + model.AvailableProductTypes.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + return View(model); + } + + [HttpPost] + public ActionResult AddProductToOrder(DataSourceRequest command, OrderModel.AddOrderProductModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return Content(""); + + var gridModel = new DataSourceResult(); + var products = _productService.SearchProducts(categoryIds: new List {model.SearchCategoryId}, + manufacturerId: model.SearchManufacturerId, + productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, + keywords: model.SearchProductName, + pageIndex: command.Page - 1, + pageSize: command.PageSize, + showHidden: true); + gridModel.Data = products.Select(x => + { + var productModel = new OrderModel.AddOrderProductModel.ProductModel + { + Id = x.Id, + Name = x.Name, + Sku = x.Sku, + }; + + return productModel; + }); + gridModel.Total = products.TotalCount; + + return Json(gridModel); + } + + public ActionResult AddProductToOrderDetails(int orderId, int productId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = orderId }); + + var model = PrepareAddProductToOrderModel(orderId, productId); + return View(model); + } + + [HttpPost] + public ActionResult AddProductToOrderDetails(int orderId, int productId, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = orderId }); + + var order = _orderService.GetOrderById(orderId); + var product = _productService.GetProductById(productId); + //save order item + + //basic properties + decimal unitPriceInclTax; + decimal.TryParse(form["UnitPriceInclTax"], out unitPriceInclTax); + decimal unitPriceExclTax; + decimal.TryParse(form["UnitPriceExclTax"], out unitPriceExclTax); + int quantity; + int.TryParse(form["Quantity"], out quantity); + decimal priceInclTax; + decimal.TryParse(form["SubTotalInclTax"], out priceInclTax); + decimal priceExclTax; + decimal.TryParse(form["SubTotalExclTax"], out priceExclTax); + decimal vatRate; + decimal.TryParse(form["VatRate"], out vatRate); + + //warnings + var warnings = new List(); + + //attributes + var attributesXml = ParseProductAttributes(product, form); + + //attributes tax + List scDiscounts; + decimal discountAmount; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributesXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + + #region Gift cards + + string recipientName = ""; + string recipientEmail = ""; + string senderName = ""; + string senderEmail = ""; + string giftCardMessage = ""; + if (product.IsGiftCard) + { + foreach (string formKey in form.AllKeys) + { + if (formKey.Equals("giftcard.RecipientName", StringComparison.InvariantCultureIgnoreCase)) + { + recipientName = form[formKey]; + continue; + } + if (formKey.Equals("giftcard.RecipientEmail", StringComparison.InvariantCultureIgnoreCase)) + { + recipientEmail = form[formKey]; + continue; + } + if (formKey.Equals("giftcard.SenderName", StringComparison.InvariantCultureIgnoreCase)) + { + senderName = form[formKey]; + continue; + } + if (formKey.Equals("giftcard.SenderEmail", StringComparison.InvariantCultureIgnoreCase)) + { + senderEmail = form[formKey]; + continue; + } + if (formKey.Equals("giftcard.Message", StringComparison.InvariantCultureIgnoreCase)) + { + giftCardMessage = form[formKey]; + continue; + } + } + + attributesXml = _productAttributeParser.AddGiftCardAttribute(attributesXml, + recipientName, recipientEmail, senderName, senderEmail, giftCardMessage); + } + + #endregion + + #region Rental product + + DateTime? rentalStartDate = null; + DateTime? rentalEndDate = null; + if (product.IsRental) + { + ParseRentalDates(form, out rentalStartDate, out rentalEndDate); + } + + #endregion + + //warnings + warnings.AddRange(_shoppingCartService.GetShoppingCartItemAttributeWarnings(order.Customer, ShoppingCartType.ShoppingCart, product, quantity, attributesXml)); + warnings.AddRange(_shoppingCartService.GetShoppingCartItemGiftCardWarnings(ShoppingCartType.ShoppingCart, product, attributesXml)); + warnings.AddRange(_shoppingCartService.GetRentalProductWarnings(product, rentalStartDate, rentalEndDate)); + if (!warnings.Any()) + { + //no errors + + //attributes + var attributeDescription = _productAttributeFormatter.FormatAttributes(product, attributesXml, order.Customer, subTotal: order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? priceInclTax : priceExclTax); + + //save item + var orderItem = new OrderItem + { + OrderItemGuid = Guid.NewGuid(), + Order = order, + ProductId = product.Id, + UnitPriceInclTax = unitPriceInclTax, + UnitPriceExclTax = unitPriceExclTax, + PriceInclTax = priceInclTax, + PriceExclTax = priceExclTax, + OriginalProductCost = _priceCalculationService.GetProductCost(product, attributesXml), + AttributeDescription = attributeDescription, + AttributesXml = attributesXml, + Quantity = quantity, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + RentalStartDateUtc = rentalStartDate, + RentalEndDateUtc = rentalEndDate, + VatRate = vatRate + }; + order.OrderItems.Add(orderItem); + _orderService.UpdateOrder(order); + + //adjust inventory + _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.EditOrder"), order.Id)); + + //update order totals + var updateOrderParameters = new UpdateOrderParameters + { + UpdatedOrder = order, + UpdatedOrderItem = orderItem, + PriceInclTax = unitPriceInclTax, + PriceExclTax = unitPriceExclTax, + SubTotalInclTax = priceInclTax, + SubTotalExclTax = priceExclTax, + Quantity = quantity, + VatRate = vatRate + }; + _orderProcessingService.UpdateOrderTotals(updateOrderParameters); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "A new order item has been added", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + //gift cards + if (product.IsGiftCard) + { + for (int i = 0; i < orderItem.Quantity; i++) + { + var gc = new GiftCard + { + GiftCardType = product.GiftCardType, + PurchasedWithOrderItem = orderItem, + Amount = unitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = recipientName, + RecipientEmail = recipientEmail, + SenderName = senderName, + SenderEmail = senderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }; + _giftCardService.InsertGiftCard(gc); + } + } + + //redirect to order details page + TempData["nop.admin.order.warnings"] = updateOrderParameters.Warnings; + return RedirectToAction("Edit", "Order", new { id = order.Id }); + } + + //errors + var model = PrepareAddProductToOrderModel(order.Id, product.Id); + model.Warnings.AddRange(warnings); + return View(model); + } + + #endregion + + #endregion + + #region Addresses + + public ActionResult AddressEdit(int addressId, int orderId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = orderId }); + + var address = _addressService.GetAddressById(addressId); + if (address == null) + throw new ArgumentException("No address found with the specified id", "addressId"); + + var model = new OrderAddressModel(); + model.OrderId = orderId; + model.Address = address.ToModel(); + model.Address.FirstNameEnabled = true; + model.Address.FirstNameRequired = true; + model.Address.LastNameEnabled = true; + model.Address.LastNameRequired = true; + model.Address.EmailEnabled = true; + model.Address.EmailRequired = true; + model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; + model.Address.CompanyRequired = _addressSettings.CompanyRequired; + model.Address.CountryEnabled = _addressSettings.CountryEnabled; + model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled + model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.Address.CityEnabled = _addressSettings.CityEnabled; + model.Address.CityRequired = _addressSettings.CityRequired; + model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; + model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; + model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; + model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; + model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; + model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; + model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; + model.Address.PhoneRequired = _addressSettings.PhoneRequired; + model.Address.FaxEnabled = _addressSettings.FaxEnabled; + model.Address.FaxRequired = _addressSettings.FaxRequired; + + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) }); + //states + var states = address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + //customer attribute services + model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); + + return View(model); + } + + [HttpPost] + [ValidateInput(false)] + public ActionResult AddressEdit(OrderAddressModel model, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(model.OrderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = order.Id }); + + var address = _addressService.GetAddressById(model.Address.Id); + if (address == null) + throw new ArgumentException("No address found with the specified id"); + + //custom address attributes + var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); + var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); + foreach (var error in customAttributeWarnings) + { + ModelState.AddModelError("", error); + } + + if (ModelState.IsValid) + { + address = model.Address.ToEntity(address); + address.CustomAttributes = customAttributes; + _addressService.UpdateAddress(address); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Address has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, orderId = model.OrderId }); + } + + //If we got this far, something failed, redisplay form + model.OrderId = order.Id; + model.Address = address.ToModel(); + model.Address.FirstNameEnabled = true; + model.Address.FirstNameRequired = true; + model.Address.LastNameEnabled = true; + model.Address.LastNameRequired = true; + model.Address.EmailEnabled = true; + model.Address.EmailRequired = true; + model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; + model.Address.CompanyRequired = _addressSettings.CompanyRequired; + model.Address.CountryEnabled = _addressSettings.CountryEnabled; + model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled + model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.Address.CityEnabled = _addressSettings.CityEnabled; + model.Address.CityRequired = _addressSettings.CityRequired; + model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; + model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; + model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; + model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; + model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; + model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; + model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; + model.Address.PhoneRequired = _addressSettings.PhoneRequired; + model.Address.FaxEnabled = _addressSettings.FaxEnabled; + model.Address.FaxRequired = _addressSettings.FaxRequired; + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) }); + //states + var states = address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + //customer attribute services + model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); + + return View(model); + } + + #endregion + + #region Shipments + + public ActionResult ShipmentList() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var model = new ShipmentListModel(); + //countries + model.AvailableCountries.Add(new SelectListItem { Text = "*", Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); + //states + model.AvailableStates.Add(new SelectListItem { Text = "*", Value = "0" }); + + //warehouses + model.AvailableWarehouses.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var w in _shippingService.GetAllWarehouses()) + model.AvailableWarehouses.Add(new SelectListItem { Text = w.Name, Value = w.Id.ToString() }); + + return View(model); + } + + [HttpPost] + public ActionResult ShipmentListSelect(DataSourceRequest command, ShipmentListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + //a vendor should have access only to his products + int vendorId = 0; + if (_workContext.CurrentVendor != null) + vendorId = _workContext.CurrentVendor.Id; + + //load shipments + var shipments = _shipmentService.GetAllShipments(vendorId: vendorId, + warehouseId: model.WarehouseId, + shippingCountryId: model.CountryId, + shippingStateId: model.StateProvinceId, + shippingCity: model.City, + trackingNumber: model.TrackingNumber, + loadNotShipped: model.LoadNotShipped, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + pageIndex: command.Page - 1, + pageSize: command.PageSize); + var gridModel = new DataSourceResult + { + Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false)), + Total = shipments.TotalCount + }; + return new JsonResult + { + Data = gridModel + }; + } + + [HttpPost] + public ActionResult ShipmentsByOrder(int orderId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + throw new ArgumentException("No order found with the specified id"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) + return Content(""); + + //shipments + var shipmentModels = new List(); + var shipments = order.Shipments + //a vendor should have access only to his products + .Where(s => _workContext.CurrentVendor == null || HasAccessToShipment(s)) + .OrderBy(s => s.CreatedOnUtc) + .ToList(); + foreach (var shipment in shipments) + shipmentModels.Add(PrepareShipmentModel(shipment, false)); + + var gridModel = new DataSourceResult + { + Data = shipmentModels, + Total = shipmentModels.Count + }; + + + return Json(gridModel); + } + + [HttpPost] + public ActionResult ShipmentsItemsByShipmentId(int shipmentId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(shipmentId); + if (shipment == null) + throw new ArgumentException("No shipment found with the specified id"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return Content(""); + + var order = _orderService.GetOrderById(shipment.OrderId); + if (order == null) + throw new ArgumentException("No order found with the specified id"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) + return Content(""); + + //shipments + var shipmentModel = PrepareShipmentModel(shipment, true); + var gridModel = new DataSourceResult + { + Data = shipmentModel.Items, + Total = shipmentModel.Items.Count + }; + + return Json(gridModel); + } + + public ActionResult AddShipment(int orderId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) + return RedirectToAction("List"); + + var model = new ShipmentModel + { + OrderId = order.Id, + }; + + //measures + var baseWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var baseWeightIn = baseWeight != null ? baseWeight.Name : ""; + var baseDimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId); + var baseDimensionIn = baseDimension != null ? baseDimension.Name : ""; + + var orderItems = order.OrderItems; + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + orderItems = orderItems.Where(HasAccessToOrderItem).ToList(); + } + + foreach (var orderItem in orderItems) + { + //we can ship only shippable products + if (!orderItem.Product.IsShipEnabled) + continue; + + //quantities + var qtyInThisShipment = 0; + var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + var qtyOrdered = orderItem.Quantity; + var qtyInAllShipments = orderItem.GetTotalNumberOfItemsInAllShipment(); + + //ensure that this product can be added to a shipment + if (maxQtyToAdd <= 0) + continue; + + var shipmentItemModel = new ShipmentModel.ShipmentItemModel + { + OrderItemId = orderItem.Id, + ProductId = orderItem.ProductId, + ProductName = orderItem.Product.Name, + Sku = orderItem.Product.FormatSku(orderItem.AttributesXml, _productAttributeParser), + AttributeInfo = orderItem.AttributeDescription, + ShipSeparately = orderItem.Product.ShipSeparately, + ItemWeight = orderItem.ItemWeight.HasValue ? string.Format("{0:F2} [{1}]", orderItem.ItemWeight, baseWeightIn) : "", + ItemDimensions = string.Format("{0:F2} x {1:F2} x {2:F2} [{3}]", orderItem.Product.Length, orderItem.Product.Width, orderItem.Product.Height, baseDimensionIn), + QuantityOrdered = qtyOrdered, + QuantityInThisShipment = qtyInThisShipment, + QuantityInAllShipments = qtyInAllShipments, + QuantityToAdd = maxQtyToAdd, + }; + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + shipmentItemModel.RentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + } + + if (orderItem.Product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && + orderItem.Product.UseMultipleWarehouses) + { + //multiple warehouses supported + shipmentItemModel.AllowToChooseWarehouse = true; + foreach (var pwi in orderItem.Product.ProductWarehouseInventory + .OrderBy(w => w.WarehouseId).ToList()) + { + var warehouse = pwi.Warehouse; + if (warehouse != null) + { + shipmentItemModel.AvailableWarehouses.Add(new ShipmentModel.ShipmentItemModel.WarehouseInfo + { + WarehouseId = warehouse.Id, + WarehouseName = warehouse.Name, + StockQuantity = pwi.StockQuantity, + ReservedQuantity = pwi.ReservedQuantity, + PlannedQuantity = _shipmentService.GetQuantityInShipments(orderItem.Product, warehouse.Id, true, true) + }); + } + } + } + else + { + //multiple warehouses are not supported + var warehouse = _shippingService.GetWarehouseById(orderItem.Product.WarehouseId); + if (warehouse != null) + { + shipmentItemModel.AvailableWarehouses.Add(new ShipmentModel.ShipmentItemModel.WarehouseInfo + { + WarehouseId = warehouse.Id, + WarehouseName = warehouse.Name, + StockQuantity = orderItem.Product.StockQuantity + }); + } + } + + model.Items.Add(shipmentItemModel); + } + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult AddShipment(int orderId, FormCollection form, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToOrder(order)) + return RedirectToAction("List"); + + var orderItems = order.OrderItems; + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + orderItems = orderItems.Where(HasAccessToOrderItem).ToList(); + } + + Shipment shipment = null; + decimal? totalWeight = null; + foreach (var orderItem in orderItems) + { + //is shippable + if (!orderItem.Product.IsShipEnabled) + continue; + + //ensure that this product can be shipped (have at least one item to ship) + var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + if (maxQtyToAdd <= 0) + continue; + + int qtyToAdd = 0; //parse quantity + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) + { + int.TryParse(form[formKey], out qtyToAdd); + break; + } + + int warehouseId = 0; + if (orderItem.Product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && + orderItem.Product.UseMultipleWarehouses) + { + //multiple warehouses supported + //warehouse is chosen by a store owner + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("warehouse_{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) + { + int.TryParse(form[formKey], out warehouseId); + break; + } + } + else + { + //multiple warehouses are not supported + warehouseId = orderItem.Product.WarehouseId; + } + + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) + { + int.TryParse(form[formKey], out qtyToAdd); + break; + } + + //validate quantity + if (qtyToAdd <= 0) + continue; + if (qtyToAdd > maxQtyToAdd) + qtyToAdd = maxQtyToAdd; + + //ok. we have at least one item. let's create a shipment (if it does not exist) + + var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; + if (orderItemTotalWeight.HasValue) + { + if (!totalWeight.HasValue) + totalWeight = 0; + totalWeight += orderItemTotalWeight.Value; + } + if (shipment == null) + { + var trackingNumber = form["TrackingNumber"]; + var adminComment = form["AdminComment"]; + shipment = new Shipment + { + OrderId = order.Id, + TrackingNumber = trackingNumber, + TotalWeight = null, + ShippedDateUtc = null, + DeliveryDateUtc = null, + AdminComment = adminComment, + CreatedOnUtc = DateTime.UtcNow, + }; + } + //create a shipment item + var shipmentItem = new ShipmentItem + { + OrderItemId = orderItem.Id, + Quantity = qtyToAdd, + WarehouseId = warehouseId + }; + shipment.ShipmentItems.Add(shipmentItem); + } + + //if we have at least one item in the shipment, then save it + if (shipment != null && shipment.ShipmentItems.Any()) + { + shipment.TotalWeight = totalWeight; + _shipmentService.InsertShipment(shipment); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "A shipment has been added", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Orders.Shipments.Added")); + return continueEditing + ? RedirectToAction("ShipmentDetails", new {id = shipment.Id}) + : RedirectToAction("Edit", new { id = orderId }); + } + + ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoProductsSelected")); + return RedirectToAction("AddShipment", new { orderId = orderId }); + } + + public ActionResult ShipmentDetails(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + var model = PrepareShipmentModel(shipment, true, true); + return View(model); + } + + [HttpPost] + public ActionResult DeleteShipment(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteShipment"), shipment.OrderId)); + } + + var orderId = shipment.OrderId; + _shipmentService.DeleteShipment(shipment); + + var order =_orderService.GetOrderById(orderId); + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "A shipment has been deleted", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Orders.Shipments.Deleted")); + return RedirectToAction("Edit", new { id = orderId }); + } + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("settrackingnumber")] + public ActionResult SetTrackingNumber(ShipmentModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(model.Id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + shipment.TrackingNumber = model.TrackingNumber; + _shipmentService.UpdateShipment(shipment); + + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("setadmincomment")] + public ActionResult SetShipmentAdminComment(ShipmentModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(model.Id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + shipment.AdminComment = model.AdminComment; + _shipmentService.UpdateShipment(shipment); + + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("setasshipped")] + public ActionResult SetAsShipped(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + try + { + _orderProcessingService.Ship(shipment, true); + LogEditOrder(shipment.OrderId); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + catch (Exception exc) + { + //error + ErrorNotification(exc, true); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + } + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("saveshippeddate")] + public ActionResult EditShippedDate(ShipmentModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(model.Id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + try + { + if (!model.ShippedDateUtc.HasValue) + { + throw new Exception("Enter shipped date"); + } + shipment.ShippedDateUtc = model.ShippedDateUtc; + _shipmentService.UpdateShipment(shipment); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + catch (Exception exc) + { + //error + ErrorNotification(exc, true); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + } + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("setasdelivered")] + public ActionResult SetAsDelivered(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + try + { + _orderProcessingService.Deliver(shipment, true); + LogEditOrder(shipment.OrderId); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + catch (Exception exc) + { + //error + ErrorNotification(exc, true); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + } + + + [HttpPost, ActionName("ShipmentDetails")] + [FormValueRequired("savedeliverydate")] + public ActionResult EditDeliveryDate(ShipmentModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(model.Id); + if (shipment == null) + //No shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + try + { + if (!model.DeliveryDateUtc.HasValue) + { + throw new Exception("Enter delivery date"); + } + shipment.DeliveryDateUtc = model.DeliveryDateUtc; + _shipmentService.UpdateShipment(shipment); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + catch (Exception exc) + { + //error + ErrorNotification(exc, true); + return RedirectToAction("ShipmentDetails", new { id = shipment.Id }); + } + } + + public ActionResult PdfPackagingSlip(int shipmentId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipment = _shipmentService.GetShipmentById(shipmentId); + if (shipment == null) + //no shipment found with the specified id + return RedirectToAction("List"); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null && !HasAccessToShipment(shipment)) + return RedirectToAction("List"); + + var shipments = new List(); + shipments.Add(shipment); + + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, string.Format("packagingslip_{0}.pdf", shipment.Id)); + } + + [HttpPost, ActionName("ShipmentList")] + [FormValueRequired("exportpackagingslips-all")] + public ActionResult PdfPackagingSlipAll(ShipmentListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + //a vendor should have access only to his products + int vendorId = 0; + if (_workContext.CurrentVendor != null) + vendorId = _workContext.CurrentVendor.Id; + + //load shipments + var shipments = _shipmentService.GetAllShipments(vendorId: vendorId, + warehouseId: model.WarehouseId, + shippingCountryId: model.CountryId, + shippingStateId: model.StateProvinceId, + shippingCity: model.City, + trackingNumber: model.TrackingNumber, + loadNotShipped: model.LoadNotShipped, + createdFromUtc: startDateValue, + createdToUtc: endDateValue); + + //ensure that we at least one shipment selected + if (!shipments.Any()) + { + ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoShipmentsSelected")); + return RedirectToAction("ShipmentList"); + } + + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, "packagingslips.pdf"); + } + + [HttpPost] + public ActionResult PdfPackagingSlipSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipments = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + shipments.AddRange(_shipmentService.GetShipmentsByIds(ids)); + } + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + shipments = shipments.Where(HasAccessToShipment).ToList(); + } + + //ensure that we at least one shipment selected + if (!shipments.Any()) + { + ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoShipmentsSelected")); + return RedirectToAction("ShipmentList"); + } + + byte[] bytes; + using (var stream = new MemoryStream()) + { + _pdfService.PrintPackagingSlipsToPdf(stream, shipments, _orderSettings.GeneratePdfInvoiceInCustomerLanguage ? 0 : _workContext.WorkingLanguage.Id); + bytes = stream.ToArray(); + } + return File(bytes, MimeTypes.ApplicationPdf, "packagingslips.pdf"); + } + + [HttpPost] + public ActionResult SetAsShippedSelected(ICollection selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipments = new List(); + if (selectedIds != null) + { + shipments.AddRange(_shipmentService.GetShipmentsByIds(selectedIds.ToArray())); + } + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + shipments = shipments.Where(HasAccessToShipment).ToList(); + } + + foreach (var shipment in shipments) + { + try + { + _orderProcessingService.Ship(shipment, true); + } + catch + { + //ignore any exception + } + } + + return Json(new { Result = true }); + } + + [HttpPost] + public ActionResult SetAsDeliveredSelected(ICollection selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var shipments = new List(); + if (selectedIds != null) + { + shipments.AddRange(_shipmentService.GetShipmentsByIds(selectedIds.ToArray())); + } + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + shipments = shipments.Where(HasAccessToShipment).ToList(); + } + + foreach (var shipment in shipments) + { + try + { + _orderProcessingService.Deliver(shipment, true); + } + catch + { + //ignore any exception + } + } + + return Json(new { Result = true }); + } + + #endregion + + #region Order notes + + [HttpPost] + public ActionResult OrderNotesSelect(int orderId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + throw new ArgumentException("No order found with the specified id"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return Content(""); + + //order notes + var orderNoteModels = new List(); + foreach (var orderNote in order.OrderNotes + .OrderByDescending(on => on.CreatedOnUtc)) + { + var download = _downloadService.GetDownloadById(orderNote.DownloadId); + orderNoteModels.Add(new OrderModel.OrderNote + { + Id = orderNote.Id, + OrderId = orderNote.OrderId, + DownloadId = orderNote.DownloadId, + DownloadGuid = download != null ? download.DownloadGuid : Guid.Empty, + DisplayToCustomer = orderNote.DisplayToCustomer, + Note = orderNote.FormatOrderNoteText(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) + }); + } + + var gridModel = new DataSourceResult + { + Data = orderNoteModels, + Total = orderNoteModels.Count + }; + + return Json(gridModel); + } + + [ValidateInput(false)] + public ActionResult OrderNoteAdd(int orderId, int downloadId, bool displayToCustomer, string message) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + return Json(new { Result = false }, JsonRequestBehavior.AllowGet); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return Json(new { Result = false }, JsonRequestBehavior.AllowGet); + + var orderNote = new OrderNote + { + DisplayToCustomer = displayToCustomer, + Note = message, + DownloadId = downloadId, + CreatedOnUtc = DateTime.UtcNow, + }; + order.OrderNotes.Add(orderNote); + _orderService.UpdateOrder(order); + + //new order notification + if (displayToCustomer) + { + //email + _workflowMessageService.SendNewOrderNoteAddedCustomerNotification( + orderNote, _workContext.WorkingLanguage.Id); + + } + + return Json(new { Result = true }, JsonRequestBehavior.AllowGet); + } + + [HttpPost] + public ActionResult OrderNoteDelete(int id, int orderId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var order = _orderService.GetOrderById(orderId); + if (order == null) + throw new ArgumentException("No order found with the specified id"); + + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = orderId }); + + var orderNote = order.OrderNotes.FirstOrDefault(on => on.Id == id); + if (orderNote == null) + throw new ArgumentException("No order note found with the specified id"); + _orderService.DeleteOrderNote(orderNote); + + return new NullJsonResult(); + } + + #endregion + + #region Reports + + [NonAction] + protected DataSourceResult GetBestsellersBriefReportModel(int pageIndex, + int pageSize, int orderBy) + { + //a vendor should have access only to his products + int vendorId = 0; + if (_workContext.CurrentVendor != null) + vendorId = _workContext.CurrentVendor.Id; + + var items = _orderReportService.BestSellersReport( + vendorId : vendorId, + orderBy: orderBy, + pageIndex: pageIndex, + pageSize: pageSize, + showHidden: true); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + { + var m = new BestsellersReportLineModel + { + ProductId = x.ProductId, + TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), + TotalQuantity = x.TotalQuantity, + }; + var product = _productService.GetProductById(x.ProductId); + if (product != null) + m.ProductName = product.Name; + return m; + }), + Total = items.TotalCount + }; + return gridModel; + } + public ActionResult BestsellersBriefReportByQuantity() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + return PartialView(); + } + [HttpPost] + public ActionResult BestsellersBriefReportByQuantityList(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + var gridModel = GetBestsellersBriefReportModel(command.Page - 1, + command.PageSize, 1); + + return Json(gridModel); + } + public ActionResult BestsellersBriefReportByAmount() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + return PartialView(); + } + [HttpPost] + public ActionResult BestsellersBriefReportByAmountList(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + var gridModel = GetBestsellersBriefReportModel(command.Page - 1, + command.PageSize, 2); + + return Json(gridModel); + } + + + + public ActionResult BestsellersReport() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var model = new BestsellersReportModel(); + //vendor + model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; + + //stores + model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var s in _storeService.GetAllStores()) + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + + //order statuses + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //payment statuses + model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //categories + model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); + foreach (var c in categories) + model.AvailableCategories.Add(c); + + //manufacturers + model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); + foreach (var m in manufacturers) + model.AvailableManufacturers.Add(m); + + //billing countries + foreach (var c in _countryService.GetAllCountriesForBilling(showHidden: true)) + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); + model.AvailableCountries.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //vendors + model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); + foreach (var v in vendors) + model.AvailableVendors.Add(v); + + return View(model); + } + [HttpPost] + public ActionResult BestsellersReportList(DataSourceRequest command, BestsellersReportModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + model.VendorId = _workContext.CurrentVendor.Id; + } + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; + PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; + + var items = _orderReportService.BestSellersReport( + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + os: orderStatus, + ps: paymentStatus, + billingCountryId: model.BillingCountryId, + orderBy: 2, + vendorId: model.VendorId, + categoryId: model.CategoryId, + manufacturerId: model.ManufacturerId, + storeId: model.StoreId, + pageIndex: command.Page - 1, + pageSize: command.PageSize, + showHidden: true); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + { + var m = new BestsellersReportLineModel + { + ProductId = x.ProductId, + TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), + TotalQuantity = x.TotalQuantity, + }; + var product = _productService.GetProductById(x.ProductId); + if (product!= null) + m.ProductName = product.Name; + return m; + }), + Total = items.TotalCount + }; + + return Json(gridModel); + } + + + + public ActionResult NeverSoldReport() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + var model = new NeverSoldReportModel(); + + //a vendor should have access only to his products + model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; + + //categories + model.AvailableCategories.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var categories = SelectListHelper.GetCategoryList(_categoryService, _cacheManager, true); + foreach (var c in categories) + model.AvailableCategories.Add(c); + + //manufacturers + model.AvailableManufacturers.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var manufacturers = SelectListHelper.GetManufacturerList(_manufacturerService, _cacheManager, true); + foreach (var m in manufacturers) + model.AvailableManufacturers.Add(m); + + //stores + model.AvailableStores.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + foreach (var s in _storeService.GetAllStores()) + model.AvailableStores.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString() }); + + //vendors + model.AvailableVendors.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); + foreach (var v in vendors) + model.AvailableVendors.Add(v); + + + return View(model); + } + [HttpPost] + public ActionResult NeverSoldReportList(DataSourceRequest command, NeverSoldReportModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor should have access only to his products + if (_workContext.CurrentVendor != null) + { + model.SearchVendorId = _workContext.CurrentVendor.Id; + } + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var items = _orderReportService.ProductsNeverSold(vendorId: model.SearchVendorId, + storeId: model.SearchStoreId, + categoryId: model.SearchCategoryId, + manufacturerId: model.SearchManufacturerId, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + pageIndex : command.Page - 1, + pageSize: command.PageSize, + showHidden: true); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + new NeverSoldReportLineModel + { + ProductId = x.Id, + ProductName = x.Name, + }), + Total = items.TotalCount + }; + + return Json(gridModel); + } + + + [ChildActionOnly] + public ActionResult OrderAverageReport() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + return PartialView(); + } + [HttpPost] + public ActionResult OrderAverageReportList(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor doesn't have access to this report + if (_workContext.CurrentVendor != null) + return Content(""); + + var report = new List(); + report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Pending)); + report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Processing)); + report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Complete)); + report.Add(_orderReportService.OrderAverageReport(0, OrderStatus.Cancelled)); + var model = report.Select(x => new OrderAverageReportLineSummaryModel + { + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + SumTodayOrders = _priceFormatter.FormatPrice(x.SumTodayOrders, true, false), + SumThisWeekOrders = _priceFormatter.FormatPrice(x.SumThisWeekOrders, true, false), + SumThisMonthOrders = _priceFormatter.FormatPrice(x.SumThisMonthOrders, true, false), + SumThisYearOrders = _priceFormatter.FormatPrice(x.SumThisYearOrders, true, false), + SumAllTimeOrders = _priceFormatter.FormatPrice(x.SumAllTimeOrders, true, false), + }).ToList(); + + var gridModel = new DataSourceResult + { + Data = model, + Total = model.Count + }; + + return Json(gridModel); + } + + [ChildActionOnly] + public ActionResult OrderIncompleteReport() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + return PartialView(); + } + + [HttpPost] + public ActionResult OrderIncompleteReportList(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor doesn't have access to this report + if (_workContext.CurrentVendor != null) + return Content(""); + + var model = new List(); + //not paid + var orderStatuses = Enum.GetValues(typeof(OrderStatus)).Cast().Where(os => os != (int)OrderStatus.Cancelled).ToList(); + var paymentStatuses = new List() { (int)PaymentStatus.Pending }; + var psPending = _orderReportService.GetOrderAverageReportLine(psIds: paymentStatuses, osIds: orderStatuses); + model.Add(new OrderIncompleteReportLineModel + { + Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalUnpaidOrders"), + Count = psPending.CountOrders, + Total = _priceFormatter.FormatPrice(psPending.SumOrders, true, false), + ViewLink = Url.Action("List", "Order", new { + orderStatusIds = string.Join(",", orderStatuses), + paymentStatusIds = string.Join(",", paymentStatuses) }) + }); + //not shipped + var shippingStatuses = new List() { (int)ShippingStatus.NotYetShipped }; + var ssPending = _orderReportService.GetOrderAverageReportLine(osIds: orderStatuses, ssIds: shippingStatuses); + model.Add(new OrderIncompleteReportLineModel + { + Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalNotShippedOrders"), + Count = ssPending.CountOrders, + Total = _priceFormatter.FormatPrice(ssPending.SumOrders, true, false), + ViewLink = Url.Action("List", "Order", new { + orderStatusIds = string.Join(",", orderStatuses), + shippingStatusIds = string.Join(",", shippingStatuses) }) + }); + //pending + orderStatuses = new List() { (int)OrderStatus.Pending }; + var osPending = _orderReportService.GetOrderAverageReportLine(osIds: orderStatuses); + model.Add(new OrderIncompleteReportLineModel + { + Item = _localizationService.GetResource("Admin.SalesReport.Incomplete.TotalIncompleteOrders"), + Count = osPending.CountOrders, + Total = _priceFormatter.FormatPrice(osPending.SumOrders, true, false), + ViewLink = Url.Action("List", "Order", new { orderStatusIds = string.Join(",", orderStatuses) }) + }); + + var gridModel = new DataSourceResult + { + Data = model, + Total = model.Count + }; + + return Json(gridModel); + } + + public ActionResult CountryReport() + { + if (!_permissionService.Authorize(StandardPermissionProvider.OrderCountryReport)) + return AccessDeniedView(); + + var model = new CountryReportModel(); + + //order statuses + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //payment statuses + model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + return View(model); + } + + [HttpPost] + public ActionResult CountryReportList(DataSourceRequest command, CountryReportModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.OrderCountryReport)) + return Content(""); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; + PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; + + var items = _orderReportService.GetCountryReport( + os: orderStatus, + ps: paymentStatus, + startTimeUtc: startDateValue, + endTimeUtc: endDateValue); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + { + var country = _countryService.GetCountryById(x.CountryId.HasValue ? x.CountryId.Value : 0); + var m = new CountryReportLineModel + { + CountryName = country != null ? country.Name : "Unknown", + SumOrders = _priceFormatter.FormatPrice(x.SumOrders, true, false), + TotalOrders = x.TotalOrders, + }; + return m; + }), + Total = items.Count + }; + + return Json(gridModel); + } + + [ChildActionOnly] + public ActionResult OrderStatistics() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor doesn't have access to this report + if (_workContext.CurrentVendor != null) + return Content(""); + + return PartialView(); + } + + [AcceptVerbs(HttpVerbs.Get)] + public ActionResult LoadOrderStatistics(string period) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor doesn't have access to this report + if (_workContext.CurrentVendor != null) + return Content(""); + + var result = new List(); + + var nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); + var timeZone = _dateTimeHelper.CurrentTimeZone; + + var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture); + + switch (period) + { + case "year": + //year statistics + var yearAgoDt = nowDt.AddYears(-1).AddMonths(1); + var searchYearDateUser = new DateTime(yearAgoDt.Year, yearAgoDt.Month, 1); + if (!timeZone.IsInvalidTime(searchYearDateUser)) + { + for (int i = 0; i <= 12; i++) + { + result.Add(new + { + date = searchYearDateUser.Date.ToString("Y", culture), + value = _orderService.SearchOrders( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser.AddMonths(1), timeZone), + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchYearDateUser = searchYearDateUser.AddMonths(1); + } + } + break; + + case "month": + //month statistics + var monthAgoDt = nowDt.AddDays(-30); + var searchMonthDateUser = new DateTime(monthAgoDt.Year, monthAgoDt.Month, monthAgoDt.Day); + if (!timeZone.IsInvalidTime(searchMonthDateUser)) + { + for (int i = 0; i <= 30; i++) + { + result.Add(new + { + date = searchMonthDateUser.Date.ToString("M", culture), + value = _orderService.SearchOrders( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser.AddDays(1), timeZone), + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchMonthDateUser = searchMonthDateUser.AddDays(1); + } + } + break; + + case "week": + default: + //week statistics + var weekAgoDt = nowDt.AddDays(-7); + var searchWeekDateUser = new DateTime(weekAgoDt.Year, weekAgoDt.Month, weekAgoDt.Day); + if (!timeZone.IsInvalidTime(searchWeekDateUser)) + { + for (int i = 0; i <= 7; i++) + { + result.Add(new + { + date = searchWeekDateUser.Date.ToString("d dddd", culture), + value = _orderService.SearchOrders( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser.AddDays(1), timeZone), + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchWeekDateUser = searchWeekDateUser.AddDays(1); + } + } + break; + } + + return Json(result, JsonRequestBehavior.AllowGet); + } + + [ChildActionOnly] + public ActionResult LatestOrders() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + return PartialView(); + } + + #endregion + + #region Activity log + + [NonAction] + protected void LogEditOrder(int orderId) + { + _customerActivityService.InsertActivity("EditOrder", _localizationService.GetResource("ActivityLog.EditOrder"), orderId); + } + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml index 30e0eb02062..831b93c10e2 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml @@ -1,256 +1,274 @@ -@model IList - -@using System.Text -@using Nop.Web.Framework; -@using Nop.Core.Domain.Catalog; -@if (Model.Count > 0) -{ - //dynamic update support - var attributeChangeScriptsBuilder = new StringBuilder(); - var productId = Convert.ToInt32(ViewData["productId"]); - var attributesHaveConditions = Model.Any(a => a.HasCondition); - var attributeChangeHandlerFuncName = string.Format("attribute_change_handler_{0}", productId); - -
-
- @foreach (var attribute in Model) - { -
- @{ - string controlId = string.Format("product_attribute_{0}", attribute.Id); - string textPrompt = !string.IsNullOrEmpty(attribute.TextPrompt) ? attribute.TextPrompt : attribute.Name; - } -
- @if (attribute.IsRequired) - { - * - } -
- -
-
-
- @switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - { - - } - break; - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - { - foreach (var attributeValue in attribute.Values) - { -
- -
- } - } - break; - case AttributeControlType.Checkboxes: - case AttributeControlType.ReadonlyCheckboxes: - { - foreach (var attributeValue in attribute.Values) - { -
- -
- } - } - break; - case AttributeControlType.TextBox: - { - - } - break; - case AttributeControlType.MultilineTextbox: - { - - } - break; - case AttributeControlType.Datepicker: - { - @Html.DatePickerDropDowns(controlId + "_day", controlId + "_month", controlId + "_year", DateTime.Now.Year, DateTime.Now.Year + 1) - } - break; - case AttributeControlType.FileUpload: - { - //register CSS and JS - Html.AddCssFileParts("~/Administration/Scripts/fineuploader/fineuploader-4.2.2.min.css"); - Html.AddScriptParts("~/Administration/Scripts/fineuploader/jquery.fineuploader-4.2.2.min.js"); - - //ex. ['jpg', 'jpeg', 'png', 'gif'] or [] - var allowedFileExtensions = attribute.AllowedFileExtensions != null && attribute.AllowedFileExtensions.Any() - ? string.Join(", ", attribute.AllowedFileExtensions.Select(x => "'" + x.Trim() + "'").ToList()) - : null; - - - - @*fine uploader container*@ -
- @*fine uploader template (keep it synchronized to \Content\fineuploader\templates\default.html)*@ - - -
-
- -
-
- } - break; - } -
-
- } - - @*generate change event script*@ - @foreach (var attribute in Model) - { - string controlId = string.Format("product_attribute_{0}", attribute.Id); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - { - attributeChangeScriptsBuilder.AppendFormat("$('#{0}').change(function(){{{1}();}});\n", controlId, attributeChangeHandlerFuncName); - } - break; - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - { - foreach (var attributeValue in attribute.Values) - { - attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName); - } - } - break; - case AttributeControlType.Checkboxes: - case AttributeControlType.ReadonlyCheckboxes: - { - foreach (var attributeValue in attribute.Values) - { - attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName); - } - } - break; - default: - break; - } - } - - @*render scripts*@ - -
-
+@model IList + +@using System.Text +@using Nop.Web.Framework; +@using Nop.Core.Domain.Catalog; +@if (Model.Count > 0) +{ + //dynamic update support + var attributeChangeScriptsBuilder = new StringBuilder(); + var productId = Convert.ToInt32(ViewData["productId"]); + var attributesHaveConditions = Model.Any(a => a.HasCondition); + var attributeChangeHandlerFuncName = string.Format("attribute_change_handler_{0}", productId); + +
+
+ @foreach (var attribute in Model) + { +
+ @{ + string controlId = string.Format("product_attribute_{0}", attribute.Id); + string textPrompt = !string.IsNullOrEmpty(attribute.TextPrompt) ? attribute.TextPrompt : attribute.Name; + } +
+ @if (attribute.IsRequired) + { + * + } +
+ +
+
+
+ @switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + { + + } + break; + case AttributeControlType.RadioList: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + { + foreach (var attributeValue in attribute.Values) + { +
+ +
+ } + } + break; + case AttributeControlType.Checkboxes: + case AttributeControlType.ReadonlyCheckboxes: + { + foreach (var attributeValue in attribute.Values) + { +
+ +
+ } + } + break; + case AttributeControlType.TextBox: + { + + } + break; + case AttributeControlType.MultilineTextbox: + { + + } + break; + case AttributeControlType.Datepicker: + { + @Html.DatePickerDropDowns(controlId + "_day", controlId + "_month", controlId + "_year", DateTime.Now.Year, DateTime.Now.Year + 1) + } + break; + case AttributeControlType.FileUpload: + { + //register CSS and JS + Html.AddCssFileParts("~/Administration/Scripts/fineuploader/fineuploader-4.2.2.min.css"); + Html.AddScriptParts("~/Administration/Scripts/fineuploader/jquery.fineuploader-4.2.2.min.js"); + + //ex. ['jpg', 'jpeg', 'png', 'gif'] or [] + var allowedFileExtensions = attribute.AllowedFileExtensions != null && attribute.AllowedFileExtensions.Any() + ? string.Join(", ", attribute.AllowedFileExtensions.Select(x => "'" + x.Trim() + "'").ToList()) + : null; + + + + @*fine uploader container*@ +
+ @*fine uploader template (keep it synchronized to \Content\fineuploader\templates\default.html)*@ + + +
+
+ +
+
+ } + break; + } +
+
+ } + + @*generate change event script*@ + @foreach (var attribute in Model) + { + string controlId = string.Format("product_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + { + attributeChangeScriptsBuilder.AppendFormat("$('#{0}').change(function(){{{1}();}});\n", controlId, attributeChangeHandlerFuncName); + } + break; + case AttributeControlType.RadioList: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + { + foreach (var attributeValue in attribute.Values) + { + attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName); + } + } + break; + case AttributeControlType.Checkboxes: + case AttributeControlType.ReadonlyCheckboxes: + { + foreach (var attributeValue in attribute.Values) + { + attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName); + } + } + break; + default: + break; + } + } + + @*render scripts*@ + +
+
} diff --git a/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs b/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs index 0b2798115b4..bb1adcd2297 100644 --- a/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs @@ -1,1848 +1,1848 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Media; -using Nop.Core.Domain.Orders; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Customers; -using Nop.Services.Directory; -using Nop.Services.Discounts; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Media; -using Nop.Services.Messages; -using Nop.Services.Orders; -using Nop.Services.Security; -using Nop.Services.Seo; -using Nop.Services.Shipping.Date; -using Nop.Services.Tax; -using Nop.Web.Factories; -using Nop.Web.Framework.Controllers; -using Nop.Web.Framework.Mvc; -using Nop.Web.Framework.Security; -using Nop.Web.Framework.Security.Captcha; -using Nop.Web.Infrastructure.Cache; -using Nop.Web.Models.Media; -using Nop.Web.Models.ShoppingCart; - -namespace Nop.Web.Controllers -{ - public partial class ShoppingCartController : BasePublicController - { - #region Fields - - private readonly IShoppingCartModelFactory _shoppingCartModelFactory; - private readonly IProductService _productService; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IShoppingCartService _shoppingCartService; - private readonly IPictureService _pictureService; - private readonly ILocalizationService _localizationService; - private readonly IProductAttributeService _productAttributeService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly ITaxService _taxService; - private readonly ICurrencyService _currencyService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly IPriceFormatter _priceFormatter; - private readonly ICheckoutAttributeParser _checkoutAttributeParser; - private readonly IDiscountService _discountService; - private readonly ICustomerService _customerService; - private readonly IGiftCardService _giftCardService; - private readonly IDateRangeService _dateRangeService; - private readonly ICheckoutAttributeService _checkoutAttributeService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IPermissionService _permissionService; - private readonly IDownloadService _downloadService; - private readonly ICacheManager _cacheManager; - private readonly IWebHelper _webHelper; - private readonly ICustomerActivityService _customerActivityService; - private readonly IGenericAttributeService _genericAttributeService; - - private readonly MediaSettings _mediaSettings; - private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly OrderSettings _orderSettings; - private readonly CaptchaSettings _captchaSettings; - private readonly CustomerSettings _customerSettings; - - #endregion - - #region Ctor - - public ShoppingCartController(IShoppingCartModelFactory shoppingCartModelFactory, - IProductService productService, - IStoreContext storeContext, - IWorkContext workContext, - IShoppingCartService shoppingCartService, - IPictureService pictureService, - ILocalizationService localizationService, - IProductAttributeService productAttributeService, - IProductAttributeParser productAttributeParser, - ITaxService taxService, ICurrencyService currencyService, - IPriceCalculationService priceCalculationService, - IPriceFormatter priceFormatter, - ICheckoutAttributeParser checkoutAttributeParser, - IDiscountService discountService, - ICustomerService customerService, - IGiftCardService giftCardService, - IDateRangeService dateRangeService, - ICheckoutAttributeService checkoutAttributeService, - IWorkflowMessageService workflowMessageService, - IPermissionService permissionService, - IDownloadService downloadService, - ICacheManager cacheManager, - IWebHelper webHelper, - ICustomerActivityService customerActivityService, - IGenericAttributeService genericAttributeService, - MediaSettings mediaSettings, - ShoppingCartSettings shoppingCartSettings, - OrderSettings orderSettings, - CaptchaSettings captchaSettings, - CustomerSettings customerSettings) - { - this._shoppingCartModelFactory = shoppingCartModelFactory; - this._productService = productService; - this._workContext = workContext; - this._storeContext = storeContext; - this._shoppingCartService = shoppingCartService; - this._pictureService = pictureService; - this._localizationService = localizationService; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._taxService = taxService; - this._currencyService = currencyService; - this._priceCalculationService = priceCalculationService; - this._priceFormatter = priceFormatter; - this._checkoutAttributeParser = checkoutAttributeParser; - this._discountService = discountService; - this._customerService = customerService; - this._giftCardService = giftCardService; - this._dateRangeService = dateRangeService; - this._checkoutAttributeService = checkoutAttributeService; - this._workflowMessageService = workflowMessageService; - this._permissionService = permissionService; - this._downloadService = downloadService; - this._cacheManager = cacheManager; - this._webHelper = webHelper; - this._customerActivityService = customerActivityService; - this._genericAttributeService = genericAttributeService; - - this._mediaSettings = mediaSettings; - this._shoppingCartSettings = shoppingCartSettings; - this._orderSettings = orderSettings; - this._captchaSettings = captchaSettings; - this._customerSettings = customerSettings; - } - - #endregion - - #region Utilities - - [NonAction] - protected virtual void ParseAndSaveCheckoutAttributes(List cart, FormCollection form) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - if (form == null) - throw new ArgumentNullException("form"); - - string attributesXml = ""; - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id, !cart.RequiresShipping()); - foreach (var attribute in checkoutAttributes) - { - string controlId = string.Format("checkout_attribute_{0}", attribute.Id); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - int selectedAttributeId = int.Parse(ctrlAttributes); - if (selectedAttributeId > 0) - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.Checkboxes: - { - var cblAttributes = form[controlId]; - if (!String.IsNullOrEmpty(cblAttributes)) - { - foreach (var item in cblAttributes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - } - break; - case AttributeControlType.ReadonlyCheckboxes: - { - //load read-only (already server-side selected) values - var attributeValues = _checkoutAttributeService.GetCheckoutAttributeValues(attribute.Id); - foreach (var selectedAttributeId in attributeValues - .Where(v => v.IsPreSelected) - .Select(v => v.Id) - .ToList()) - { - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - string enteredText = ctrlAttributes.Trim(); - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, enteredText); - } - } - break; - case AttributeControlType.Datepicker: - { - var date = form[controlId + "_day"]; - var month = form[controlId + "_month"]; - var year = form[controlId + "_year"]; - DateTime? selectedDate = null; - try - { - selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(date)); - } - catch { } - if (selectedDate.HasValue) - { - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, selectedDate.Value.ToString("D")); - } - } - break; - case AttributeControlType.FileUpload: - { - Guid downloadGuid; - Guid.TryParse(form[controlId], out downloadGuid); - var download = _downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, - attribute, download.DownloadGuid.ToString()); - } - } - break; - default: - break; - } - } - - //validate conditional attributes (if specified) - foreach (var attribute in checkoutAttributes) - { - var conditionMet = _checkoutAttributeParser.IsConditionMet(attribute, attributesXml); - if (conditionMet.HasValue && !conditionMet.Value) - attributesXml = _checkoutAttributeParser.RemoveCheckoutAttribute(attributesXml, attribute); - } - - //save checkout attributes - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.CheckoutAttributes, attributesXml, _storeContext.CurrentStore.Id); - } - - /// - /// Parse product attributes on the product details page - /// - /// Product - /// Form - /// Parsed attributes - [NonAction] - protected virtual string ParseProductAttributes(Product product, FormCollection form) - { - string attributesXml = ""; - - #region Product attributes - - var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - foreach (var attribute in productAttributes) - { - string controlId = string.Format("product_attribute_{0}", attribute.Id); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - int quantity; - int selectedAttributeId = int.Parse(ctrlAttributes); - if (selectedAttributeId > 0) - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString(), - int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, selectedAttributeId)], out quantity) && quantity > 1 ? (int?)quantity : null); - } - } - break; - case AttributeControlType.Checkboxes: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - foreach (var item in ctrlAttributes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - int quantity; - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString(), - int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, item)], out quantity) && quantity > 1 ? (int?)quantity : null); - } - } - } - break; - case AttributeControlType.ReadonlyCheckboxes: - { - //load read-only (already server-side selected) values - var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); - foreach (var selectedAttributeId in attributeValues - .Where(v => v.IsPreSelected) - .Select(v => v.Id) - .ToList()) - { - int quantity; - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString(), - int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, selectedAttributeId)], out quantity) && quantity > 1 ? (int?)quantity : null); - } - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - string enteredText = ctrlAttributes.Trim(); - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, enteredText); - } - } - break; - case AttributeControlType.Datepicker: - { - var day = form[controlId + "_day"]; - var month = form[controlId + "_month"]; - var year = form[controlId + "_year"]; - DateTime? selectedDate = null; - try - { - selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day)); - } - catch { } - if (selectedDate.HasValue) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedDate.Value.ToString("D")); - } - } - break; - case AttributeControlType.FileUpload: - { - Guid downloadGuid; - Guid.TryParse(form[controlId], out downloadGuid); - var download = _downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, download.DownloadGuid.ToString()); - } - } - break; - default: - break; - } - } - //validate conditional attributes (if specified) - foreach (var attribute in productAttributes) - { - var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributesXml); - if (conditionMet.HasValue && !conditionMet.Value) - { - attributesXml = _productAttributeParser.RemoveProductAttribute(attributesXml, attribute); - } - } - - #endregion - - #region Gift cards - - if (product.IsGiftCard) - { - string recipientName = ""; - string recipientEmail = ""; - string senderName = ""; - string senderEmail = ""; - string giftCardMessage = ""; - foreach (string formKey in form.AllKeys) - { - if (formKey.Equals(string.Format("giftcard_{0}.RecipientName", product.Id), StringComparison.InvariantCultureIgnoreCase)) - { - recipientName = form[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard_{0}.RecipientEmail", product.Id), StringComparison.InvariantCultureIgnoreCase)) - { - recipientEmail = form[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard_{0}.SenderName", product.Id), StringComparison.InvariantCultureIgnoreCase)) - { - senderName = form[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard_{0}.SenderEmail", product.Id), StringComparison.InvariantCultureIgnoreCase)) - { - senderEmail = form[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard_{0}.Message", product.Id), StringComparison.InvariantCultureIgnoreCase)) - { - giftCardMessage = form[formKey]; - continue; - } - } - - attributesXml = _productAttributeParser.AddGiftCardAttribute(attributesXml, - recipientName, recipientEmail, senderName, senderEmail, giftCardMessage); - } - - #endregion - - return attributesXml; - } - - /// - /// Parse product rental dates on the product details page - /// - /// Product - /// Form - /// Start date - /// End date - [NonAction] - protected virtual void ParseRentalDates(Product product, FormCollection form, - out DateTime? startDate, out DateTime? endDate) - { - startDate = null; - endDate = null; - - string startControlId = string.Format("rental_start_date_{0}", product.Id); - string endControlId = string.Format("rental_end_date_{0}", product.Id); - var ctrlStartDate = form[startControlId]; - var ctrlEndDate = form[endControlId]; - try - { - //currenly we support only this format (as in the \Views\Product\_RentalInfo.cshtml file) - const string datePickerFormat = "MM/dd/yyyy"; - startDate = DateTime.ParseExact(ctrlStartDate, datePickerFormat, CultureInfo.InvariantCulture); - endDate = DateTime.ParseExact(ctrlEndDate, datePickerFormat, CultureInfo.InvariantCulture); - } - catch - { - } - } - - #endregion - - #region Shopping cart - - //add product to cart using AJAX - //currently we use this method on catalog pages (category/manufacturer/etc) - [HttpPost] - public ActionResult AddProductToCart_Catalog(int productId, int shoppingCartTypeId, - int quantity, bool forceredirection = false) - { - var cartType = (ShoppingCartType)shoppingCartTypeId; - - var product = _productService.GetProductById(productId); - if (product == null) - //no product found - return Json(new - { - success = false, - message = "No product found with the specified ID" - }); - - //we can add only simple products - if (product.ProductType != ProductType.SimpleProduct) - { - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - //products with "minimum order quantity" more than a specified qty - if (product.OrderMinimumQuantity > quantity) - { - //we cannot add to the cart such products from category pages - //it can confuse customers. That's why we redirect customers to the product details page - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - if (product.CustomerEntersPrice) - { - //cannot be added to the cart (requires a customer to enter price) - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - if (product.IsRental) - { - //rental products require start/end dates to be entered - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - var allowedQuantities = product.ParseAllowedQuantities(); - if (allowedQuantities.Length > 0) - { - //cannot be added to the cart (requires a customer to select a quantity from dropdownlist) - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - //allow a product to be added to the cart when all attributes are with "read-only checkboxes" type - var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - if (productAttributes.Any(pam => pam.AttributeControlType != AttributeControlType.ReadonlyCheckboxes)) - { - //product has some attributes. let a customer see them - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - //creating XML for "read-only checkboxes" attributes - var attXml = productAttributes.Aggregate(string.Empty, (attributesXml, attribute) => - { - var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); - foreach (var selectedAttributeId in attributeValues - .Where(v => v.IsPreSelected) - .Select(v => v.Id) - .ToList()) - { - attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - return attributesXml; - }); - - //get standard warnings without attribute validations - //first, try to find existing shopping cart item - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == cartType) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var shoppingCartItem = _shoppingCartService.FindShoppingCartItemInTheCart(cart, cartType, product); - //if we already have the same product in the cart, then use the total quantity to validate - var quantityToValidate = shoppingCartItem != null ? shoppingCartItem.Quantity + quantity : quantity; - var addToCartWarnings = _shoppingCartService - .GetShoppingCartItemWarnings(_workContext.CurrentCustomer, cartType, - product, _storeContext.CurrentStore.Id, string.Empty, - decimal.Zero, null, null, quantityToValidate, false, true, false, false, false); - if (addToCartWarnings.Any()) - { - //cannot be added to the cart - //let's display standard warnings - return Json(new - { - success = false, - message = addToCartWarnings.ToArray() - }); - } - - //now let's try adding product to the cart (now including product attribute validation, etc) - addToCartWarnings = _shoppingCartService.AddToCart(customer: _workContext.CurrentCustomer, - product: product, - shoppingCartType: cartType, - storeId: _storeContext.CurrentStore.Id, - attributesXml: attXml, - quantity: quantity); - if (addToCartWarnings.Any()) - { - //cannot be added to the cart - //but we do not display attribute and gift card warnings here. let's do it on the product details page - return Json(new - { - redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), - }); - } - - //added to the cart/wishlist - switch (cartType) - { - case ShoppingCartType.Wishlist: - { - //activity log - _customerActivityService.InsertActivity("PublicStore.AddToWishlist", _localizationService.GetResource("ActivityLog.PublicStore.AddToWishlist"), product.Name); - - if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct || forceredirection) - { - //redirect to the wishlist page - return Json(new - { - redirect = Url.RouteUrl("Wishlist"), - }); - } - - //display notification message and update appropriate blocks - var updatetopwishlistsectionhtml = string.Format(_localizationService.GetResource("Wishlist.HeaderQuantity"), - _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList() - .GetTotalProducts()); - return Json(new - { - success = true, - message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), - updatetopwishlistsectionhtml = updatetopwishlistsectionhtml, - }); - } - case ShoppingCartType.ShoppingCart: - default: - { - //activity log - _customerActivityService.InsertActivity("PublicStore.AddToShoppingCart", _localizationService.GetResource("ActivityLog.PublicStore.AddToShoppingCart"), product.Name); - - if (_shoppingCartSettings.DisplayCartAfterAddingProduct || forceredirection) - { - //redirect to the shopping cart page - return Json(new - { - redirect = Url.RouteUrl("ShoppingCart"), - }); - } - - //display notification message and update appropriate blocks - var updatetopcartsectionhtml = string.Format(_localizationService.GetResource("ShoppingCart.HeaderQuantity"), - _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList() - .GetTotalProducts()); - - var updateflyoutcartsectionhtml = _shoppingCartSettings.MiniShoppingCartEnabled - ? this.RenderPartialViewToString("FlyoutShoppingCart", _shoppingCartModelFactory.PrepareMiniShoppingCartModel()) - : ""; - - return Json(new - { - success = true, - message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), - updatetopcartsectionhtml = updatetopcartsectionhtml, - updateflyoutcartsectionhtml = updateflyoutcartsectionhtml - }); - } - } - } - - //add product to cart using AJAX - //currently we use this method on the product details pages - [HttpPost] - [ValidateInput(false)] - public ActionResult AddProductToCart_Details(int productId, int shoppingCartTypeId, FormCollection form) - { - var product = _productService.GetProductById(productId); - if (product == null) - { - return Json(new - { - redirect = Url.RouteUrl("HomePage"), - }); - } - - //we can add only simple products - if (product.ProductType != ProductType.SimpleProduct) - { - return Json(new - { - success = false, - message = "Only simple products could be added to the cart" - }); - } - - #region Update existing shopping cart item? - - int updatecartitemid = 0; - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("addtocart_{0}.UpdatedShoppingCartItemId", productId), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out updatecartitemid); - break; - } - ShoppingCartItem updatecartitem = null; - if (_shoppingCartSettings.AllowCartItemEditing && updatecartitemid > 0) - { - //search with the same cart type as specified - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(x => x.ShoppingCartTypeId == shoppingCartTypeId) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - updatecartitem = cart.FirstOrDefault(x => x.Id == updatecartitemid); - //not found? let's ignore it. in this case we'll add a new item - //if (updatecartitem == null) - //{ - // return Json(new - // { - // success = false, - // message = "No shopping cart item found to update" - // }); - //} - //is it this product? - if (updatecartitem != null && product.Id != updatecartitem.ProductId) - { - return Json(new - { - success = false, - message = "This product does not match a passed shopping cart item identifier" - }); - } - } - - #endregion - - #region Customer entered price - decimal customerEnteredPriceConverted = decimal.Zero; - if (product.CustomerEntersPrice) - { - foreach (string formKey in form.AllKeys) - { - if (formKey.Equals(string.Format("addtocart_{0}.CustomerEnteredPrice", productId), StringComparison.InvariantCultureIgnoreCase)) - { - decimal customerEnteredPrice; - if (decimal.TryParse(form[formKey], out customerEnteredPrice)) - customerEnteredPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(customerEnteredPrice, _workContext.WorkingCurrency); - break; - } - } - } - #endregion - - #region Quantity - - int quantity = 1; - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("addtocart_{0}.EnteredQuantity", productId), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out quantity); - break; - } - - #endregion - - //product and gift card attributes - string attributes = ParseProductAttributes(product, form); - - //rental attributes - DateTime? rentalStartDate = null; - DateTime? rentalEndDate = null; - if (product.IsRental) - { - ParseRentalDates(product, form, out rentalStartDate, out rentalEndDate); - } - - var cartType = updatecartitem == null ? (ShoppingCartType)shoppingCartTypeId : - //if the item to update is found, then we ignore the specified "shoppingCartTypeId" parameter - updatecartitem.ShoppingCartType; - - //save item - var addToCartWarnings = new List(); - if (updatecartitem == null) - { - //add to the cart - addToCartWarnings.AddRange(_shoppingCartService.AddToCart(_workContext.CurrentCustomer, - product, cartType, _storeContext.CurrentStore.Id, - attributes, customerEnteredPriceConverted, - rentalStartDate, rentalEndDate, quantity, true)); - } - else - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(x => x.ShoppingCartType == updatecartitem.ShoppingCartType) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var otherCartItemWithSameParameters = _shoppingCartService.FindShoppingCartItemInTheCart( - cart, updatecartitem.ShoppingCartType, product, attributes, customerEnteredPriceConverted, - rentalStartDate, rentalEndDate); - if (otherCartItemWithSameParameters != null && - otherCartItemWithSameParameters.Id == updatecartitem.Id) - { - //ensure it's some other shopping cart item - otherCartItemWithSameParameters = null; - } - //update existing item - addToCartWarnings.AddRange(_shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, - updatecartitem.Id, attributes, customerEnteredPriceConverted, - rentalStartDate, rentalEndDate, quantity, true)); - if (otherCartItemWithSameParameters != null && !addToCartWarnings.Any()) - { - //delete the same shopping cart item (the other one) - _shoppingCartService.DeleteShoppingCartItem(otherCartItemWithSameParameters); - } - } - - #region Return result - - if (addToCartWarnings.Any()) - { - //cannot be added to the cart/wishlist - //let's display warnings - return Json(new - { - success = false, - message = addToCartWarnings.ToArray() - }); - } - - //added to the cart/wishlist - switch (cartType) - { - case ShoppingCartType.Wishlist: - { - //activity log - _customerActivityService.InsertActivity("PublicStore.AddToWishlist", _localizationService.GetResource("ActivityLog.PublicStore.AddToWishlist"), product.Name); - - if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct) - { - //redirect to the wishlist page - return Json(new - { - redirect = Url.RouteUrl("Wishlist"), - }); - } - - //display notification message and update appropriate blocks - var updatetopwishlistsectionhtml = string.Format(_localizationService.GetResource("Wishlist.HeaderQuantity"), - _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList() - .GetTotalProducts()); - - return Json(new - { - success = true, - message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), - updatetopwishlistsectionhtml = updatetopwishlistsectionhtml, - }); - } - case ShoppingCartType.ShoppingCart: - default: - { - //activity log - _customerActivityService.InsertActivity("PublicStore.AddToShoppingCart", _localizationService.GetResource("ActivityLog.PublicStore.AddToShoppingCart"), product.Name); - - if (_shoppingCartSettings.DisplayCartAfterAddingProduct) - { - //redirect to the shopping cart page - return Json(new - { - redirect = Url.RouteUrl("ShoppingCart"), - }); - } - - //display notification message and update appropriate blocks - var updatetopcartsectionhtml = string.Format(_localizationService.GetResource("ShoppingCart.HeaderQuantity"), - _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList() - .GetTotalProducts()); - - var updateflyoutcartsectionhtml = _shoppingCartSettings.MiniShoppingCartEnabled - ? this.RenderPartialViewToString("FlyoutShoppingCart", _shoppingCartModelFactory.PrepareMiniShoppingCartModel()) - : ""; - - return Json(new - { - success = true, - message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), - updatetopcartsectionhtml = updatetopcartsectionhtml, - updateflyoutcartsectionhtml = updateflyoutcartsectionhtml - }); - } - } - - - #endregion - } - - //handle product attribute selection event. this way we return new price, overridden gtin/sku/mpn - //currently we use this method on the product details pages - [HttpPost] - [ValidateInput(false)] - public ActionResult ProductDetails_AttributeChange(int productId, bool validateAttributeConditions, - bool loadPicture, FormCollection form) - { - var product = _productService.GetProductById(productId); - if (product == null) - return new NullJsonResult(); - - string attributeXml = ParseProductAttributes(product, form); - - //rental attributes - DateTime? rentalStartDate = null; - DateTime? rentalEndDate = null; - if (product.IsRental) - { - ParseRentalDates(product, form, out rentalStartDate, out rentalEndDate); - } - - //sku, mpn, gtin - string sku = product.FormatSku(attributeXml, _productAttributeParser); - string mpn = product.FormatMpn(attributeXml, _productAttributeParser); - string gtin = product.FormatGtin(attributeXml, _productAttributeParser); - - //price - string price = ""; - if (_permissionService.Authorize(StandardPermissionProvider.DisplayPrices) && !product.CustomerEntersPrice) - { - //we do not calculate price of "customer enters price" option is enabled - List scDiscounts; - decimal discountAmount; - decimal finalPrice = _priceCalculationService.GetUnitPrice(product, - _workContext.CurrentCustomer, - ShoppingCartType.ShoppingCart, - 1, attributeXml, 0, - rentalStartDate, rentalEndDate, - true, out discountAmount, out scDiscounts); - decimal taxRate; - decimal finalPriceWithDiscountBase = _taxService.GetProductPrice(product, finalPrice, out taxRate); - decimal finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, _workContext.WorkingCurrency); - price = _priceFormatter.FormatPrice(finalPriceWithDiscount); - } - - //stock - var stockAvailability = product.FormatStockMessage(attributeXml, _localizationService, _productAttributeParser, _dateRangeService); - - //conditional attributes - var enabledAttributeMappingIds = new List(); - var disabledAttributeMappingIds = new List(); - if (validateAttributeConditions) - { - var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - foreach (var attribute in attributes) - { - var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributeXml); - if (conditionMet.HasValue) - { - if (conditionMet.Value) - enabledAttributeMappingIds.Add(attribute.Id); - else - disabledAttributeMappingIds.Add(attribute.Id); - } - } - } - - //picture. used when we want to override a default product picture when some attribute is selected - var pictureFullSizeUrl = ""; - var pictureDefaultSizeUrl = ""; - if (loadPicture) - { - //just load (return) the first found picture (in case if we have several distinct attributes with associated pictures) - //actually we're going to support pictures associated to attribute combinations (not attribute values) soon. it'll more flexible approach - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributeXml); - var attributeValueWithPicture = attributeValues.FirstOrDefault(x => x.PictureId > 0); - if (attributeValueWithPicture != null) - { - var productAttributePictureCacheKey = string.Format(ModelCacheEventConsumer.PRODUCTATTRIBUTE_PICTURE_MODEL_KEY, - attributeValueWithPicture.PictureId, - _webHelper.IsCurrentConnectionSecured(), - _storeContext.CurrentStore.Id); - var pictureModel = _cacheManager.Get(productAttributePictureCacheKey, () => - { - var valuePicture = _pictureService.GetPictureById(attributeValueWithPicture.PictureId); - if (valuePicture != null) - { - return new PictureModel - { - FullSizeImageUrl = _pictureService.GetPictureUrl(valuePicture), - ImageUrl = _pictureService.GetPictureUrl(valuePicture, _mediaSettings.ProductDetailsPictureSize) - }; - } - return new PictureModel(); - }); - pictureFullSizeUrl = pictureModel.FullSizeImageUrl; - pictureDefaultSizeUrl = pictureModel.ImageUrl; - } - - } - - return Json(new - { - gtin = gtin, - mpn = mpn, - sku = sku, - price = price, - stockAvailability = stockAvailability, - enabledattributemappingids = enabledAttributeMappingIds.ToArray(), - disabledattributemappingids = disabledAttributeMappingIds.ToArray(), - pictureFullSizeUrl = pictureFullSizeUrl, - pictureDefaultSizeUrl = pictureDefaultSizeUrl - }); - } - - [HttpPost] - [ValidateInput(false)] - public ActionResult CheckoutAttributeChange(FormCollection form) - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - ParseAndSaveCheckoutAttributes(cart, form); - var attributeXml = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, - _genericAttributeService, _storeContext.CurrentStore.Id); - - var enabledAttributeIds = new List(); - var disabledAttributeIds = new List(); - var attributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id, !cart.RequiresShipping()); - foreach (var attribute in attributes) - { - var conditionMet = _checkoutAttributeParser.IsConditionMet(attribute, attributeXml); - if (conditionMet.HasValue) - { - if (conditionMet.Value) - enabledAttributeIds.Add(attribute.Id); - else - disabledAttributeIds.Add(attribute.Id); - } - } - - return Json(new - { - enabledattributeids = enabledAttributeIds.ToArray(), - disabledattributeids = disabledAttributeIds.ToArray() - }); - } - - [HttpPost] - public ActionResult UploadFileProductAttribute(int attributeId) - { - var attribute = _productAttributeService.GetProductAttributeMappingById(attributeId); - if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) - { - return Json(new - { - success = false, - downloadGuid = Guid.Empty, - }, MimeTypes.TextPlain); - } - - //we process it distinct ways based on a browser - //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload - Stream stream = null; - var fileName = ""; - var contentType = ""; - if (String.IsNullOrEmpty(Request["qqfile"])) - { - // IE - HttpPostedFileBase httpPostedFile = Request.Files[0]; - if (httpPostedFile == null) - throw new ArgumentException("No file uploaded"); - stream = httpPostedFile.InputStream; - fileName = Path.GetFileName(httpPostedFile.FileName); - contentType = httpPostedFile.ContentType; - } - else - { - //Webkit, Mozilla - stream = Request.InputStream; - fileName = Request["qqfile"]; - } - - var fileBinary = new byte[stream.Length]; - stream.Read(fileBinary, 0, fileBinary.Length); - - var fileExtension = Path.GetExtension(fileName); - if (!String.IsNullOrEmpty(fileExtension)) - fileExtension = fileExtension.ToLowerInvariant(); - - if (attribute.ValidationFileMaximumSize.HasValue) - { - //compare in bytes - var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; - if (fileBinary.Length > maxFileSizeBytes) - { - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. - return Json(new - { - success = false, - message = string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), - downloadGuid = Guid.Empty, - }, MimeTypes.TextPlain); - } - } - - var download = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = false, - DownloadUrl = "", - DownloadBinary = fileBinary, - ContentType = contentType, - //we store filename without extension for downloads - Filename = Path.GetFileNameWithoutExtension(fileName), - Extension = fileExtension, - IsNew = true - }; - _downloadService.InsertDownload(download); - - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. - return Json(new - { - success = true, - message = _localizationService.GetResource("ShoppingCart.FileUploaded"), - downloadUrl = Url.Action("GetFileUpload", "Download", new { downloadId = download.DownloadGuid }), - downloadGuid = download.DownloadGuid, - }, MimeTypes.TextPlain); - } - - [HttpPost] - public ActionResult UploadFileCheckoutAttribute(int attributeId) - { - var attribute = _checkoutAttributeService.GetCheckoutAttributeById(attributeId); - if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) - { - return Json(new - { - success = false, - downloadGuid = Guid.Empty, - }, MimeTypes.TextPlain); - } - - //we process it distinct ways based on a browser - //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload - Stream stream = null; - var fileName = ""; - var contentType = ""; - if (String.IsNullOrEmpty(Request["qqfile"])) - { - // IE - HttpPostedFileBase httpPostedFile = Request.Files[0]; - if (httpPostedFile == null) - throw new ArgumentException("No file uploaded"); - stream = httpPostedFile.InputStream; - fileName = Path.GetFileName(httpPostedFile.FileName); - contentType = httpPostedFile.ContentType; - } - else - { - //Webkit, Mozilla - stream = Request.InputStream; - fileName = Request["qqfile"]; - } - - var fileBinary = new byte[stream.Length]; - stream.Read(fileBinary, 0, fileBinary.Length); - - var fileExtension = Path.GetExtension(fileName); - if (!String.IsNullOrEmpty(fileExtension)) - fileExtension = fileExtension.ToLowerInvariant(); - - if (attribute.ValidationFileMaximumSize.HasValue) - { - //compare in bytes - var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; - if (fileBinary.Length > maxFileSizeBytes) - { - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. - return Json(new - { - success = false, - message = string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), - downloadGuid = Guid.Empty, - }, MimeTypes.TextPlain); - } - } - - var download = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = false, - DownloadUrl = "", - DownloadBinary = fileBinary, - ContentType = contentType, - //we store filename without extension for downloads - Filename = Path.GetFileNameWithoutExtension(fileName), - Extension = fileExtension, - IsNew = true - }; - _downloadService.InsertDownload(download); - - //when returning JSON the mime-type must be set to text/plain - //otherwise some browsers will pop-up a "Save As" dialog. - return Json(new - { - success = true, - message = _localizationService.GetResource("ShoppingCart.FileUploaded"), - downloadUrl = Url.Action("GetFileUpload", "Download", new { downloadId = download.DownloadGuid }), - downloadGuid = download.DownloadGuid, - }, MimeTypes.TextPlain); - } - - [NopHttpsRequirement(SslRequirement.Yes)] - public ActionResult Cart() - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) - return RedirectToRoute("HomePage"); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new ShoppingCartModel(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - return View(model); - } - - [ChildActionOnly] - public ActionResult OrderSummary(bool? prepareAndDisplayOrderReviewData) - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new ShoppingCartModel(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart, - isEditable: false, - prepareEstimateShippingIfEnabled: false, - prepareAndDisplayOrderReviewData: prepareAndDisplayOrderReviewData.GetValueOrDefault()); - return PartialView(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired("updatecart")] - public ActionResult UpdateCart(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) - return RedirectToRoute("HomePage"); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - var allIdsToRemove = form["removefromcart"] != null ? form["removefromcart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => int.Parse(x)).ToList() : new List(); - - //current warnings - var innerWarnings = new Dictionary>(); - foreach (var sci in cart) - { - bool remove = allIdsToRemove.Contains(sci.Id); - if (remove) - _shoppingCartService.DeleteShoppingCartItem(sci, ensureOnlyActiveCheckoutAttributes: true); - else - { - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("itemquantity{0}", sci.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int newQuantity; - if (int.TryParse(form[formKey], out newQuantity)) - { - var currSciWarnings = _shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, - sci.Id, sci.AttributesXml, sci.CustomerEnteredPrice, - sci.RentalStartDateUtc, sci.RentalEndDateUtc, - newQuantity, true); - innerWarnings.Add(sci.Id, currSciWarnings); - } - break; - } - } - } - - //parse and save checkout attributes - ParseAndSaveCheckoutAttributes(cart, form); - - //updated cart - cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new ShoppingCartModel(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - //update current warnings - foreach (var kvp in innerWarnings) - { - //kvp = - var sciId = kvp.Key; - var warnings = kvp.Value; - //find model - var sciModel = model.Items.FirstOrDefault(x => x.Id == sciId); - if (sciModel != null) - foreach (var w in warnings) - if (!sciModel.Warnings.Contains(w)) - sciModel.Warnings.Add(w); - } - return View(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired("continueshopping")] - public ActionResult ContinueShopping() - { - var returnUrl = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.LastContinueShoppingPage, _storeContext.CurrentStore.Id); - if (!String.IsNullOrEmpty(returnUrl)) - { - return Redirect(returnUrl); - } - else - { - return RedirectToRoute("HomePage"); - } - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired("checkout")] - public ActionResult StartCheckout(FormCollection form) - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - //parse and save checkout attributes - ParseAndSaveCheckoutAttributes(cart, form); - - //validate attributes - var checkoutAttributes = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService, _storeContext.CurrentStore.Id); - var checkoutAttributeWarnings = _shoppingCartService.GetShoppingCartWarnings(cart, checkoutAttributes, true); - if (checkoutAttributeWarnings.Any()) - { - //something wrong, redisplay the page with warnings - var model = new ShoppingCartModel(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart, validateCheckoutAttributes: true); - return View(model); - } - - //everything is OK - if (_workContext.CurrentCustomer.IsGuest()) - { - bool downloadableProductsRequireRegistration = - _customerSettings.RequireRegistrationForDownloadableProducts && cart.Any(sci => sci.Product.IsDownload); - - if (!_orderSettings.AnonymousCheckoutAllowed - || downloadableProductsRequireRegistration) - return new HttpUnauthorizedResult(); - - return RedirectToRoute("LoginCheckoutAsGuest", new {returnUrl = Url.RouteUrl("ShoppingCart")}); - } - - return RedirectToRoute("Checkout"); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired("applydiscountcouponcode")] - public ActionResult ApplyDiscountCoupon(string discountcouponcode, FormCollection form) - { - //trim - if (discountcouponcode != null) - discountcouponcode = discountcouponcode.Trim(); - - //cart - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - //parse and save checkout attributes - ParseAndSaveCheckoutAttributes(cart, form); - - var model = new ShoppingCartModel(); - if (!String.IsNullOrWhiteSpace(discountcouponcode)) - { - //we find even hidden records here. this way we can display a user-friendly message if it's expired - var discounts = _discountService.GetAllDiscountsForCaching(couponCode: discountcouponcode, showHidden: true) - .Where(d => d.RequiresCouponCode) - .ToList(); - if (discounts.Any()) - { - string userError = ""; - bool anyValidDiscount = false; - foreach (var discount in discounts) - { - var validationResult = _discountService.ValidateDiscount(discount, _workContext.CurrentCustomer, new[] { discountcouponcode }); - if (validationResult.IsValid) - { - anyValidDiscount = true; - break; - } - else - { - if (!String.IsNullOrEmpty(validationResult.UserError)) - userError = validationResult.UserError; - } - } - - if (anyValidDiscount) - { - //valid - _workContext.CurrentCustomer.ApplyDiscountCouponCode(discountcouponcode); - model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.Applied"); - model.DiscountBox.IsApplied = true; - } - else - { - if (!String.IsNullOrEmpty(userError)) - { - //some user error - model.DiscountBox.Message = userError; - model.DiscountBox.IsApplied = false; - } - else - { - //general error text - model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); - model.DiscountBox.IsApplied = false; - } - } - } - else - { - //discount cannot be found - model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); - model.DiscountBox.IsApplied = false; - } - } - else - { - //empty coupon code - model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); - model.DiscountBox.IsApplied = false; - } - - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - return View(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired("applygiftcardcouponcode")] - public ActionResult ApplyGiftCard(string giftcardcouponcode, FormCollection form) - { - //trim - if (giftcardcouponcode != null) - giftcardcouponcode = giftcardcouponcode.Trim(); - - //cart - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - //parse and save checkout attributes - ParseAndSaveCheckoutAttributes(cart, form); - - var model = new ShoppingCartModel(); - if (!cart.IsRecurring()) - { - if (!String.IsNullOrWhiteSpace(giftcardcouponcode)) - { - var giftCard = _giftCardService.GetAllGiftCards(giftCardCouponCode: giftcardcouponcode).FirstOrDefault(); - bool isGiftCardValid = giftCard != null && giftCard.IsGiftCardValid(); - if (isGiftCardValid) - { - _workContext.CurrentCustomer.ApplyGiftCardCouponCode(giftcardcouponcode); - model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.Applied"); - model.GiftCardBox.IsApplied = true; - } - else - { - model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); - model.GiftCardBox.IsApplied = false; - } - } - else - { - model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); - model.GiftCardBox.IsApplied = false; - } - } - else - { - model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.DontWorkWithAutoshipProducts"); - model.GiftCardBox.IsApplied = false; - } - - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - return View(model); - } - - [ValidateInput(false)] - [PublicAntiForgery] - [HttpPost] - public ActionResult GetEstimateShipping(int? countryId, int? stateProvinceId, string zipPostalCode, FormCollection form) - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - //parse and save checkout attributes - ParseAndSaveCheckoutAttributes(cart, form); - - var model = _shoppingCartModelFactory.PrepareEstimateShippingResultModel(cart, countryId, stateProvinceId, zipPostalCode); - return PartialView("_EstimateShippingResult", model); - } - - [ChildActionOnly] - public ActionResult OrderTotals(bool isEditable) - { - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - var model = _shoppingCartModelFactory.PrepareOrderTotalsModel(cart, isEditable); - return PartialView(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired(FormValueRequirement.StartsWith, "removediscount-")] - public ActionResult RemoveDiscountCoupon(FormCollection form) - { - var model = new ShoppingCartModel(); - - //get discount identifier - int discountId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("removediscount-", StringComparison.InvariantCultureIgnoreCase)) - discountId = Convert.ToInt32(formValue.Substring("removediscount-".Length)); - var discount = _discountService.GetDiscountById(discountId); - if (discount != null) - _workContext.CurrentCustomer.RemoveDiscountCouponCode(discount.CouponCode); - - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - return View(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Cart")] - [FormValueRequired(FormValueRequirement.StartsWith, "removegiftcard-")] - public ActionResult RemoveGiftCardCode(FormCollection form) - { - var model = new ShoppingCartModel(); - - //get gift card identifier - int giftCardId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("removegiftcard-", StringComparison.InvariantCultureIgnoreCase)) - giftCardId = Convert.ToInt32(formValue.Substring("removegiftcard-".Length)); - var gc = _giftCardService.GetGiftCardById(giftCardId); - if (gc != null) - _workContext.CurrentCustomer.RemoveGiftCardCouponCode(gc.GiftCardCouponCode); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); - return View(model); - } - - [ChildActionOnly] - public ActionResult FlyoutShoppingCart() - { - if (!_shoppingCartSettings.MiniShoppingCartEnabled) - return Content(""); - - if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) - return Content(""); - - var model = _shoppingCartModelFactory.PrepareMiniShoppingCartModel(); - return PartialView(model); - } - - #endregion - - #region Wishlist - - [NopHttpsRequirement(SslRequirement.Yes)] - public ActionResult Wishlist(Guid? customerGuid) - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) - return RedirectToRoute("HomePage"); - - Customer customer = customerGuid.HasValue ? - _customerService.GetCustomerByGuid(customerGuid.Value) - : _workContext.CurrentCustomer; - if (customer == null) - return RedirectToRoute("HomePage"); - var cart = customer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new WishlistModel(); - model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart, !customerGuid.HasValue); - return View(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Wishlist")] - [FormValueRequired("updatecart")] - public ActionResult UpdateWishlist(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) - return RedirectToRoute("HomePage"); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - var allIdsToRemove = form["removefromcart"] != null - ? form["removefromcart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToList() - : new List(); - - //current warnings - var innerWarnings = new Dictionary>(); - foreach (var sci in cart) - { - bool remove = allIdsToRemove.Contains(sci.Id); - if (remove) - _shoppingCartService.DeleteShoppingCartItem(sci); - else - { - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("itemquantity{0}", sci.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int newQuantity; - if (int.TryParse(form[formKey], out newQuantity)) - { - var currSciWarnings = _shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, - sci.Id, sci.AttributesXml, sci.CustomerEnteredPrice, - sci.RentalStartDateUtc, sci.RentalEndDateUtc, - newQuantity, true); - innerWarnings.Add(sci.Id, currSciWarnings); - } - break; - } - } - } - - //updated wishlist - cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new WishlistModel(); - model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart); - //update current warnings - foreach (var kvp in innerWarnings) - { - //kvp = - var sciId = kvp.Key; - var warnings = kvp.Value; - //find model - var sciModel = model.Items.FirstOrDefault(x => x.Id == sciId); - if (sciModel != null) - foreach (var w in warnings) - if (!sciModel.Warnings.Contains(w)) - sciModel.Warnings.Add(w); - } - return View(model); - } - - [ValidateInput(false)] - [HttpPost, ActionName("Wishlist")] - [FormValueRequired("addtocartbutton")] - public ActionResult AddItemsToCartFromWishlist(Guid? customerGuid, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) - return RedirectToRoute("HomePage"); - - if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) - return RedirectToRoute("HomePage"); - - var pageCustomer = customerGuid.HasValue - ? _customerService.GetCustomerByGuid(customerGuid.Value) - : _workContext.CurrentCustomer; - if (pageCustomer == null) - return RedirectToRoute("HomePage"); - - var pageCart = pageCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - var allWarnings = new List(); - var numberOfAddedItems = 0; - var allIdsToAdd = form["addtocart"] != null - ? form["addtocart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToList() - : new List(); - foreach (var sci in pageCart) - { - if (allIdsToAdd.Contains(sci.Id)) - { - var warnings = _shoppingCartService.AddToCart(_workContext.CurrentCustomer, - sci.Product, ShoppingCartType.ShoppingCart, - _storeContext.CurrentStore.Id, - sci.AttributesXml, sci.CustomerEnteredPrice, - sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, true); - if (!warnings.Any()) - numberOfAddedItems++; - if (_shoppingCartSettings.MoveItemsFromWishlistToCart && //settings enabled - !customerGuid.HasValue && //own wishlist - !warnings.Any()) //no warnings ( already in the cart) - { - //let's remove the item from wishlist - _shoppingCartService.DeleteShoppingCartItem(sci); - } - allWarnings.AddRange(warnings); - } - } - - if (numberOfAddedItems > 0) - { - //redirect to the shopping cart page - - if (allWarnings.Any()) - { - ErrorNotification(_localizationService.GetResource("Wishlist.AddToCart.Error"), true); - } - - return RedirectToRoute("ShoppingCart"); - } - else - { - //no items added. redisplay the wishlist page - - if (allWarnings.Any()) - { - ErrorNotification(_localizationService.GetResource("Wishlist.AddToCart.Error"), false); - } - - var cart = pageCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - var model = new WishlistModel(); - model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart, !customerGuid.HasValue); - return View(model); - } - } - - [NopHttpsRequirement(SslRequirement.Yes)] - public ActionResult EmailWishlist() - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist) || !_shoppingCartSettings.EmailWishlistEnabled) - return RedirectToRoute("HomePage"); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - if (!cart.Any()) - return RedirectToRoute("HomePage"); - - var model = new WishlistEmailAFriendModel(); - model = _shoppingCartModelFactory.PrepareWishlistEmailAFriendModel(model, false); - return View(model); - } - - [HttpPost, ActionName("EmailWishlist")] - [PublicAntiForgery] - [FormValueRequired("send-email")] - [CaptchaValidator] - public ActionResult EmailWishlistSend(WishlistEmailAFriendModel model, bool captchaValid) - { - if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist) || !_shoppingCartSettings.EmailWishlistEnabled) - return RedirectToRoute("HomePage"); - - var cart = _workContext.CurrentCustomer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - if (!cart.Any()) - return RedirectToRoute("HomePage"); - - //validate CAPTCHA - if (_captchaSettings.Enabled && _captchaSettings.ShowOnEmailWishlistToFriendPage && !captchaValid) - { - ModelState.AddModelError("", _captchaSettings.GetWrongCaptchaMessage(_localizationService)); - } - - //check whether the current customer is guest and ia allowed to email wishlist - if (_workContext.CurrentCustomer.IsGuest() && !_shoppingCartSettings.AllowAnonymousUsersToEmailWishlist) - { - ModelState.AddModelError("", _localizationService.GetResource("Wishlist.EmailAFriend.OnlyRegisteredUsers")); - } - - if (ModelState.IsValid) - { - //email - _workflowMessageService.SendWishlistEmailAFriendMessage(_workContext.CurrentCustomer, - _workContext.WorkingLanguage.Id, model.YourEmailAddress, - model.FriendEmail, Core.Html.HtmlHelper.FormatText(model.PersonalMessage, false, true, false, false, false, false)); - - model.SuccessfullySent = true; - model.Result = _localizationService.GetResource("Wishlist.EmailAFriend.SuccessfullySent"); - - return View(model); - } - - //If we got this far, something failed, redisplay form - model = _shoppingCartModelFactory.PrepareWishlistEmailAFriendModel(model, true); - return View(model); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Media; +using Nop.Core.Domain.Orders; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Media; +using Nop.Services.Messages; +using Nop.Services.Orders; +using Nop.Services.Security; +using Nop.Services.Seo; +using Nop.Services.Shipping.Date; +using Nop.Services.Tax; +using Nop.Web.Factories; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Mvc; +using Nop.Web.Framework.Security; +using Nop.Web.Framework.Security.Captcha; +using Nop.Web.Infrastructure.Cache; +using Nop.Web.Models.Media; +using Nop.Web.Models.ShoppingCart; + +namespace Nop.Web.Controllers +{ + public partial class ShoppingCartController : BasePublicController + { + #region Fields + + private readonly IShoppingCartModelFactory _shoppingCartModelFactory; + private readonly IProductService _productService; + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IShoppingCartService _shoppingCartService; + private readonly IPictureService _pictureService; + private readonly ILocalizationService _localizationService; + private readonly IProductAttributeService _productAttributeService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly ITaxService _taxService; + private readonly ICurrencyService _currencyService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly IPriceFormatter _priceFormatter; + private readonly ICheckoutAttributeParser _checkoutAttributeParser; + private readonly IDiscountService _discountService; + private readonly ICustomerService _customerService; + private readonly IGiftCardService _giftCardService; + private readonly IDateRangeService _dateRangeService; + private readonly ICheckoutAttributeService _checkoutAttributeService; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly IPermissionService _permissionService; + private readonly IDownloadService _downloadService; + private readonly ICacheManager _cacheManager; + private readonly IWebHelper _webHelper; + private readonly ICustomerActivityService _customerActivityService; + private readonly IGenericAttributeService _genericAttributeService; + + private readonly MediaSettings _mediaSettings; + private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly OrderSettings _orderSettings; + private readonly CaptchaSettings _captchaSettings; + private readonly CustomerSettings _customerSettings; + + #endregion + + #region Ctor + + public ShoppingCartController(IShoppingCartModelFactory shoppingCartModelFactory, + IProductService productService, + IStoreContext storeContext, + IWorkContext workContext, + IShoppingCartService shoppingCartService, + IPictureService pictureService, + ILocalizationService localizationService, + IProductAttributeService productAttributeService, + IProductAttributeParser productAttributeParser, + ITaxService taxService, ICurrencyService currencyService, + IPriceCalculationService priceCalculationService, + IPriceFormatter priceFormatter, + ICheckoutAttributeParser checkoutAttributeParser, + IDiscountService discountService, + ICustomerService customerService, + IGiftCardService giftCardService, + IDateRangeService dateRangeService, + ICheckoutAttributeService checkoutAttributeService, + IWorkflowMessageService workflowMessageService, + IPermissionService permissionService, + IDownloadService downloadService, + ICacheManager cacheManager, + IWebHelper webHelper, + ICustomerActivityService customerActivityService, + IGenericAttributeService genericAttributeService, + MediaSettings mediaSettings, + ShoppingCartSettings shoppingCartSettings, + OrderSettings orderSettings, + CaptchaSettings captchaSettings, + CustomerSettings customerSettings) + { + this._shoppingCartModelFactory = shoppingCartModelFactory; + this._productService = productService; + this._workContext = workContext; + this._storeContext = storeContext; + this._shoppingCartService = shoppingCartService; + this._pictureService = pictureService; + this._localizationService = localizationService; + this._productAttributeService = productAttributeService; + this._productAttributeParser = productAttributeParser; + this._taxService = taxService; + this._currencyService = currencyService; + this._priceCalculationService = priceCalculationService; + this._priceFormatter = priceFormatter; + this._checkoutAttributeParser = checkoutAttributeParser; + this._discountService = discountService; + this._customerService = customerService; + this._giftCardService = giftCardService; + this._dateRangeService = dateRangeService; + this._checkoutAttributeService = checkoutAttributeService; + this._workflowMessageService = workflowMessageService; + this._permissionService = permissionService; + this._downloadService = downloadService; + this._cacheManager = cacheManager; + this._webHelper = webHelper; + this._customerActivityService = customerActivityService; + this._genericAttributeService = genericAttributeService; + + this._mediaSettings = mediaSettings; + this._shoppingCartSettings = shoppingCartSettings; + this._orderSettings = orderSettings; + this._captchaSettings = captchaSettings; + this._customerSettings = customerSettings; + } + + #endregion + + #region Utilities + + [NonAction] + protected virtual void ParseAndSaveCheckoutAttributes(List cart, FormCollection form) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + if (form == null) + throw new ArgumentNullException("form"); + + string attributesXml = ""; + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id, !cart.RequiresShipping()); + foreach (var attribute in checkoutAttributes) + { + string controlId = string.Format("checkout_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + int selectedAttributeId = int.Parse(ctrlAttributes); + if (selectedAttributeId > 0) + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.Checkboxes: + { + var cblAttributes = form[controlId]; + if (!String.IsNullOrEmpty(cblAttributes)) + { + foreach (var item in cblAttributes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + int selectedAttributeId = int.Parse(item); + if (selectedAttributeId > 0) + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + } + break; + case AttributeControlType.ReadonlyCheckboxes: + { + //load read-only (already server-side selected) values + var attributeValues = _checkoutAttributeService.GetCheckoutAttributeValues(attribute.Id); + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + string enteredText = ctrlAttributes.Trim(); + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, enteredText); + } + } + break; + case AttributeControlType.Datepicker: + { + var date = form[controlId + "_day"]; + var month = form[controlId + "_month"]; + var year = form[controlId + "_year"]; + DateTime? selectedDate = null; + try + { + selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(date)); + } + catch { } + if (selectedDate.HasValue) + { + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, selectedDate.Value.ToString("D")); + } + } + break; + case AttributeControlType.FileUpload: + { + Guid downloadGuid; + Guid.TryParse(form[controlId], out downloadGuid); + var download = _downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + attributesXml = _checkoutAttributeParser.AddCheckoutAttribute(attributesXml, + attribute, download.DownloadGuid.ToString()); + } + } + break; + default: + break; + } + } + + //validate conditional attributes (if specified) + foreach (var attribute in checkoutAttributes) + { + var conditionMet = _checkoutAttributeParser.IsConditionMet(attribute, attributesXml); + if (conditionMet.HasValue && !conditionMet.Value) + attributesXml = _checkoutAttributeParser.RemoveCheckoutAttribute(attributesXml, attribute); + } + + //save checkout attributes + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.CheckoutAttributes, attributesXml, _storeContext.CurrentStore.Id); + } + + /// + /// Parse product attributes on the product details page + /// + /// Product + /// Form + /// Parsed attributes + [NonAction] + protected virtual string ParseProductAttributes(Product product, FormCollection form) + { + string attributesXml = ""; + + #region Product attributes + + var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in productAttributes) + { + string controlId = string.Format("product_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + int quantity; + int selectedAttributeId = int.Parse(ctrlAttributes); + if (selectedAttributeId > 0) + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString(), + int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, selectedAttributeId)], out quantity) && quantity > 1 ? (int?)quantity : null); + } + } + break; + case AttributeControlType.Checkboxes: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + foreach (var item in ctrlAttributes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + int quantity; + int selectedAttributeId = int.Parse(item); + if (selectedAttributeId > 0) + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString(), + int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, item)], out quantity) && quantity > 1 ? (int?)quantity : null); + } + } + } + break; + case AttributeControlType.ReadonlyCheckboxes: + { + //load read-only (already server-side selected) values + var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + int quantity; + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString(), + int.TryParse(form[string.Format("product_attribute_{0}_{1}_qty", attribute.Id, selectedAttributeId)], out quantity) && quantity > 1 ? (int?)quantity : null); + } + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + string enteredText = ctrlAttributes.Trim(); + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, enteredText); + } + } + break; + case AttributeControlType.Datepicker: + { + var day = form[controlId + "_day"]; + var month = form[controlId + "_month"]; + var year = form[controlId + "_year"]; + DateTime? selectedDate = null; + try + { + selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day)); + } + catch { } + if (selectedDate.HasValue) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedDate.Value.ToString("D")); + } + } + break; + case AttributeControlType.FileUpload: + { + Guid downloadGuid; + Guid.TryParse(form[controlId], out downloadGuid); + var download = _downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, download.DownloadGuid.ToString()); + } + } + break; + default: + break; + } + } + //validate conditional attributes (if specified) + foreach (var attribute in productAttributes) + { + var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributesXml); + if (conditionMet.HasValue && !conditionMet.Value) + { + attributesXml = _productAttributeParser.RemoveProductAttribute(attributesXml, attribute); + } + } + + #endregion + + #region Gift cards + + if (product.IsGiftCard) + { + string recipientName = ""; + string recipientEmail = ""; + string senderName = ""; + string senderEmail = ""; + string giftCardMessage = ""; + foreach (string formKey in form.AllKeys) + { + if (formKey.Equals(string.Format("giftcard_{0}.RecipientName", product.Id), StringComparison.InvariantCultureIgnoreCase)) + { + recipientName = form[formKey]; + continue; + } + if (formKey.Equals(string.Format("giftcard_{0}.RecipientEmail", product.Id), StringComparison.InvariantCultureIgnoreCase)) + { + recipientEmail = form[formKey]; + continue; + } + if (formKey.Equals(string.Format("giftcard_{0}.SenderName", product.Id), StringComparison.InvariantCultureIgnoreCase)) + { + senderName = form[formKey]; + continue; + } + if (formKey.Equals(string.Format("giftcard_{0}.SenderEmail", product.Id), StringComparison.InvariantCultureIgnoreCase)) + { + senderEmail = form[formKey]; + continue; + } + if (formKey.Equals(string.Format("giftcard_{0}.Message", product.Id), StringComparison.InvariantCultureIgnoreCase)) + { + giftCardMessage = form[formKey]; + continue; + } + } + + attributesXml = _productAttributeParser.AddGiftCardAttribute(attributesXml, + recipientName, recipientEmail, senderName, senderEmail, giftCardMessage); + } + + #endregion + + return attributesXml; + } + + /// + /// Parse product rental dates on the product details page + /// + /// Product + /// Form + /// Start date + /// End date + [NonAction] + protected virtual void ParseRentalDates(Product product, FormCollection form, + out DateTime? startDate, out DateTime? endDate) + { + startDate = null; + endDate = null; + + string startControlId = string.Format("rental_start_date_{0}", product.Id); + string endControlId = string.Format("rental_end_date_{0}", product.Id); + var ctrlStartDate = form[startControlId]; + var ctrlEndDate = form[endControlId]; + try + { + //currenly we support only this format (as in the \Views\Product\_RentalInfo.cshtml file) + const string datePickerFormat = "MM/dd/yyyy"; + startDate = DateTime.ParseExact(ctrlStartDate, datePickerFormat, CultureInfo.InvariantCulture); + endDate = DateTime.ParseExact(ctrlEndDate, datePickerFormat, CultureInfo.InvariantCulture); + } + catch + { + } + } + + #endregion + + #region Shopping cart + + //add product to cart using AJAX + //currently we use this method on catalog pages (category/manufacturer/etc) + [HttpPost] + public ActionResult AddProductToCart_Catalog(int productId, int shoppingCartTypeId, + int quantity, bool forceredirection = false) + { + var cartType = (ShoppingCartType)shoppingCartTypeId; + + var product = _productService.GetProductById(productId); + if (product == null) + //no product found + return Json(new + { + success = false, + message = "No product found with the specified ID" + }); + + //we can add only simple products + if (product.ProductType != ProductType.SimpleProduct) + { + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + //products with "minimum order quantity" more than a specified qty + if (product.OrderMinimumQuantity > quantity) + { + //we cannot add to the cart such products from category pages + //it can confuse customers. That's why we redirect customers to the product details page + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + if (product.CustomerEntersPrice) + { + //cannot be added to the cart (requires a customer to enter price) + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + if (product.IsRental) + { + //rental products require start/end dates to be entered + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + var allowedQuantities = product.ParseAllowedQuantities(); + if (allowedQuantities.Length > 0) + { + //cannot be added to the cart (requires a customer to select a quantity from dropdownlist) + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + //allow a product to be added to the cart when all attributes are with "read-only checkboxes" type + var productAttributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + if (productAttributes.Any(pam => pam.AttributeControlType != AttributeControlType.ReadonlyCheckboxes)) + { + //product has some attributes. let a customer see them + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + //creating XML for "read-only checkboxes" attributes + var attXml = productAttributes.Aggregate(string.Empty, (attributesXml, attribute) => + { + var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + return attributesXml; + }); + + //get standard warnings without attribute validations + //first, try to find existing shopping cart item + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == cartType) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var shoppingCartItem = _shoppingCartService.FindShoppingCartItemInTheCart(cart, cartType, product); + //if we already have the same product in the cart, then use the total quantity to validate + var quantityToValidate = shoppingCartItem != null ? shoppingCartItem.Quantity + quantity : quantity; + var addToCartWarnings = _shoppingCartService + .GetShoppingCartItemWarnings(_workContext.CurrentCustomer, cartType, + product, _storeContext.CurrentStore.Id, string.Empty, + decimal.Zero, null, null, quantityToValidate, false, true, false, false, false); + if (addToCartWarnings.Any()) + { + //cannot be added to the cart + //let's display standard warnings + return Json(new + { + success = false, + message = addToCartWarnings.ToArray() + }); + } + + //now let's try adding product to the cart (now including product attribute validation, etc) + addToCartWarnings = _shoppingCartService.AddToCart(customer: _workContext.CurrentCustomer, + product: product, + shoppingCartType: cartType, + storeId: _storeContext.CurrentStore.Id, + attributesXml: attXml, + quantity: quantity); + if (addToCartWarnings.Any()) + { + //cannot be added to the cart + //but we do not display attribute and gift card warnings here. let's do it on the product details page + return Json(new + { + redirect = Url.RouteUrl("Product", new { SeName = product.GetSeName() }), + }); + } + + //added to the cart/wishlist + switch (cartType) + { + case ShoppingCartType.Wishlist: + { + //activity log + _customerActivityService.InsertActivity("PublicStore.AddToWishlist", _localizationService.GetResource("ActivityLog.PublicStore.AddToWishlist"), product.Name); + + if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct || forceredirection) + { + //redirect to the wishlist page + return Json(new + { + redirect = Url.RouteUrl("Wishlist"), + }); + } + + //display notification message and update appropriate blocks + var updatetopwishlistsectionhtml = string.Format(_localizationService.GetResource("Wishlist.HeaderQuantity"), + _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList() + .GetTotalProducts()); + return Json(new + { + success = true, + message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), + updatetopwishlistsectionhtml = updatetopwishlistsectionhtml, + }); + } + case ShoppingCartType.ShoppingCart: + default: + { + //activity log + _customerActivityService.InsertActivity("PublicStore.AddToShoppingCart", _localizationService.GetResource("ActivityLog.PublicStore.AddToShoppingCart"), product.Name); + + if (_shoppingCartSettings.DisplayCartAfterAddingProduct || forceredirection) + { + //redirect to the shopping cart page + return Json(new + { + redirect = Url.RouteUrl("ShoppingCart"), + }); + } + + //display notification message and update appropriate blocks + var updatetopcartsectionhtml = string.Format(_localizationService.GetResource("ShoppingCart.HeaderQuantity"), + _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList() + .GetTotalProducts()); + + var updateflyoutcartsectionhtml = _shoppingCartSettings.MiniShoppingCartEnabled + ? this.RenderPartialViewToString("FlyoutShoppingCart", _shoppingCartModelFactory.PrepareMiniShoppingCartModel()) + : ""; + + return Json(new + { + success = true, + message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), + updatetopcartsectionhtml = updatetopcartsectionhtml, + updateflyoutcartsectionhtml = updateflyoutcartsectionhtml + }); + } + } + } + + //add product to cart using AJAX + //currently we use this method on the product details pages + [HttpPost] + [ValidateInput(false)] + public ActionResult AddProductToCart_Details(int productId, int shoppingCartTypeId, FormCollection form) + { + var product = _productService.GetProductById(productId); + if (product == null) + { + return Json(new + { + redirect = Url.RouteUrl("HomePage"), + }); + } + + //we can add only simple products + if (product.ProductType != ProductType.SimpleProduct) + { + return Json(new + { + success = false, + message = "Only simple products could be added to the cart" + }); + } + + #region Update existing shopping cart item? + + int updatecartitemid = 0; + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("addtocart_{0}.UpdatedShoppingCartItemId", productId), StringComparison.InvariantCultureIgnoreCase)) + { + int.TryParse(form[formKey], out updatecartitemid); + break; + } + ShoppingCartItem updatecartitem = null; + if (_shoppingCartSettings.AllowCartItemEditing && updatecartitemid > 0) + { + //search with the same cart type as specified + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(x => x.ShoppingCartTypeId == shoppingCartTypeId) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + updatecartitem = cart.FirstOrDefault(x => x.Id == updatecartitemid); + //not found? let's ignore it. in this case we'll add a new item + //if (updatecartitem == null) + //{ + // return Json(new + // { + // success = false, + // message = "No shopping cart item found to update" + // }); + //} + //is it this product? + if (updatecartitem != null && product.Id != updatecartitem.ProductId) + { + return Json(new + { + success = false, + message = "This product does not match a passed shopping cart item identifier" + }); + } + } + + #endregion + + #region Customer entered price + decimal customerEnteredPriceConverted = decimal.Zero; + if (product.CustomerEntersPrice) + { + foreach (string formKey in form.AllKeys) + { + if (formKey.Equals(string.Format("addtocart_{0}.CustomerEnteredPrice", productId), StringComparison.InvariantCultureIgnoreCase)) + { + decimal customerEnteredPrice; + if (decimal.TryParse(form[formKey], out customerEnteredPrice)) + customerEnteredPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(customerEnteredPrice, _workContext.WorkingCurrency); + break; + } + } + } + #endregion + + #region Quantity + + int quantity = 1; + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("addtocart_{0}.EnteredQuantity", productId), StringComparison.InvariantCultureIgnoreCase)) + { + int.TryParse(form[formKey], out quantity); + break; + } + + #endregion + + //product and gift card attributes + string attributes = ParseProductAttributes(product, form); + + //rental attributes + DateTime? rentalStartDate = null; + DateTime? rentalEndDate = null; + if (product.IsRental) + { + ParseRentalDates(product, form, out rentalStartDate, out rentalEndDate); + } + + var cartType = updatecartitem == null ? (ShoppingCartType)shoppingCartTypeId : + //if the item to update is found, then we ignore the specified "shoppingCartTypeId" parameter + updatecartitem.ShoppingCartType; + + //save item + var addToCartWarnings = new List(); + if (updatecartitem == null) + { + //add to the cart + addToCartWarnings.AddRange(_shoppingCartService.AddToCart(_workContext.CurrentCustomer, + product, cartType, _storeContext.CurrentStore.Id, + attributes, customerEnteredPriceConverted, + rentalStartDate, rentalEndDate, quantity, true)); + } + else + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(x => x.ShoppingCartType == updatecartitem.ShoppingCartType) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var otherCartItemWithSameParameters = _shoppingCartService.FindShoppingCartItemInTheCart( + cart, updatecartitem.ShoppingCartType, product, attributes, customerEnteredPriceConverted, + rentalStartDate, rentalEndDate); + if (otherCartItemWithSameParameters != null && + otherCartItemWithSameParameters.Id == updatecartitem.Id) + { + //ensure it's some other shopping cart item + otherCartItemWithSameParameters = null; + } + //update existing item + addToCartWarnings.AddRange(_shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, + updatecartitem.Id, attributes, customerEnteredPriceConverted, + rentalStartDate, rentalEndDate, quantity, true)); + if (otherCartItemWithSameParameters != null && !addToCartWarnings.Any()) + { + //delete the same shopping cart item (the other one) + _shoppingCartService.DeleteShoppingCartItem(otherCartItemWithSameParameters); + } + } + + #region Return result + + if (addToCartWarnings.Any()) + { + //cannot be added to the cart/wishlist + //let's display warnings + return Json(new + { + success = false, + message = addToCartWarnings.ToArray() + }); + } + + //added to the cart/wishlist + switch (cartType) + { + case ShoppingCartType.Wishlist: + { + //activity log + _customerActivityService.InsertActivity("PublicStore.AddToWishlist", _localizationService.GetResource("ActivityLog.PublicStore.AddToWishlist"), product.Name); + + if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct) + { + //redirect to the wishlist page + return Json(new + { + redirect = Url.RouteUrl("Wishlist"), + }); + } + + //display notification message and update appropriate blocks + var updatetopwishlistsectionhtml = string.Format(_localizationService.GetResource("Wishlist.HeaderQuantity"), + _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList() + .GetTotalProducts()); + + return Json(new + { + success = true, + message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), + updatetopwishlistsectionhtml = updatetopwishlistsectionhtml, + }); + } + case ShoppingCartType.ShoppingCart: + default: + { + //activity log + _customerActivityService.InsertActivity("PublicStore.AddToShoppingCart", _localizationService.GetResource("ActivityLog.PublicStore.AddToShoppingCart"), product.Name); + + if (_shoppingCartSettings.DisplayCartAfterAddingProduct) + { + //redirect to the shopping cart page + return Json(new + { + redirect = Url.RouteUrl("ShoppingCart"), + }); + } + + //display notification message and update appropriate blocks + var updatetopcartsectionhtml = string.Format(_localizationService.GetResource("ShoppingCart.HeaderQuantity"), + _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList() + .GetTotalProducts()); + + var updateflyoutcartsectionhtml = _shoppingCartSettings.MiniShoppingCartEnabled + ? this.RenderPartialViewToString("FlyoutShoppingCart", _shoppingCartModelFactory.PrepareMiniShoppingCartModel()) + : ""; + + return Json(new + { + success = true, + message = string.Format(_localizationService.GetResource("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), + updatetopcartsectionhtml = updatetopcartsectionhtml, + updateflyoutcartsectionhtml = updateflyoutcartsectionhtml + }); + } + } + + + #endregion + } + + //handle product attribute selection event. this way we return new price, overridden gtin/sku/mpn + //currently we use this method on the product details pages + [HttpPost] + [ValidateInput(false)] + public ActionResult ProductDetails_AttributeChange(int productId, bool validateAttributeConditions, + bool loadPicture, FormCollection form) + { + var product = _productService.GetProductById(productId); + if (product == null) + return new NullJsonResult(); + + string attributeXml = ParseProductAttributes(product, form); + + //rental attributes + DateTime? rentalStartDate = null; + DateTime? rentalEndDate = null; + if (product.IsRental) + { + ParseRentalDates(product, form, out rentalStartDate, out rentalEndDate); + } + + //sku, mpn, gtin + string sku = product.FormatSku(attributeXml, _productAttributeParser); + string mpn = product.FormatMpn(attributeXml, _productAttributeParser); + string gtin = product.FormatGtin(attributeXml, _productAttributeParser); + + //price + string price = ""; + if (_permissionService.Authorize(StandardPermissionProvider.DisplayPrices) && !product.CustomerEntersPrice) + { + //we do not calculate price of "customer enters price" option is enabled + List scDiscounts; + decimal discountAmount; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributeXml, 0, + rentalStartDate, rentalEndDate, + true, out discountAmount, out scDiscounts); + decimal taxRate; + decimal finalPriceWithDiscountBase = _taxService.GetProductPrice(product, finalPrice, out taxRate); + decimal finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, _workContext.WorkingCurrency); + price = _priceFormatter.FormatPrice(finalPriceWithDiscount); + } + + //stock + var stockAvailability = product.FormatStockMessage(attributeXml, _localizationService, _productAttributeParser, _dateRangeService); + + //conditional attributes + var enabledAttributeMappingIds = new List(); + var disabledAttributeMappingIds = new List(); + if (validateAttributeConditions) + { + var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in attributes) + { + var conditionMet = _productAttributeParser.IsConditionMet(attribute, attributeXml); + if (conditionMet.HasValue) + { + if (conditionMet.Value) + enabledAttributeMappingIds.Add(attribute.Id); + else + disabledAttributeMappingIds.Add(attribute.Id); + } + } + } + + //picture. used when we want to override a default product picture when some attribute is selected + var pictureFullSizeUrl = ""; + var pictureDefaultSizeUrl = ""; + if (loadPicture) + { + //just load (return) the first found picture (in case if we have several distinct attributes with associated pictures) + //actually we're going to support pictures associated to attribute combinations (not attribute values) soon. it'll more flexible approach + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributeXml); + var attributeValueWithPicture = attributeValues.FirstOrDefault(x => x.PictureId > 0); + if (attributeValueWithPicture != null) + { + var productAttributePictureCacheKey = string.Format(ModelCacheEventConsumer.PRODUCTATTRIBUTE_PICTURE_MODEL_KEY, + attributeValueWithPicture.PictureId, + _webHelper.IsCurrentConnectionSecured(), + _storeContext.CurrentStore.Id); + var pictureModel = _cacheManager.Get(productAttributePictureCacheKey, () => + { + var valuePicture = _pictureService.GetPictureById(attributeValueWithPicture.PictureId); + if (valuePicture != null) + { + return new PictureModel + { + FullSizeImageUrl = _pictureService.GetPictureUrl(valuePicture), + ImageUrl = _pictureService.GetPictureUrl(valuePicture, _mediaSettings.ProductDetailsPictureSize) + }; + } + return new PictureModel(); + }); + pictureFullSizeUrl = pictureModel.FullSizeImageUrl; + pictureDefaultSizeUrl = pictureModel.ImageUrl; + } + + } + + return Json(new + { + gtin = gtin, + mpn = mpn, + sku = sku, + price = price, + stockAvailability = stockAvailability, + enabledattributemappingids = enabledAttributeMappingIds.ToArray(), + disabledattributemappingids = disabledAttributeMappingIds.ToArray(), + pictureFullSizeUrl = pictureFullSizeUrl, + pictureDefaultSizeUrl = pictureDefaultSizeUrl + }); + } + + [HttpPost] + [ValidateInput(false)] + public ActionResult CheckoutAttributeChange(FormCollection form) + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + ParseAndSaveCheckoutAttributes(cart, form); + var attributeXml = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, + _genericAttributeService, _storeContext.CurrentStore.Id); + + var enabledAttributeIds = new List(); + var disabledAttributeIds = new List(); + var attributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id, !cart.RequiresShipping()); + foreach (var attribute in attributes) + { + var conditionMet = _checkoutAttributeParser.IsConditionMet(attribute, attributeXml); + if (conditionMet.HasValue) + { + if (conditionMet.Value) + enabledAttributeIds.Add(attribute.Id); + else + disabledAttributeIds.Add(attribute.Id); + } + } + + return Json(new + { + enabledattributeids = enabledAttributeIds.ToArray(), + disabledattributeids = disabledAttributeIds.ToArray() + }); + } + + [HttpPost] + public ActionResult UploadFileProductAttribute(int attributeId) + { + var attribute = _productAttributeService.GetProductAttributeMappingById(attributeId); + if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) + { + return Json(new + { + success = false, + downloadGuid = Guid.Empty, + }, MimeTypes.TextPlain); + } + + //we process it distinct ways based on a browser + //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload + Stream stream = null; + var fileName = ""; + var contentType = ""; + if (String.IsNullOrEmpty(Request["qqfile"])) + { + // IE + HttpPostedFileBase httpPostedFile = Request.Files[0]; + if (httpPostedFile == null) + throw new ArgumentException("No file uploaded"); + stream = httpPostedFile.InputStream; + fileName = Path.GetFileName(httpPostedFile.FileName); + contentType = httpPostedFile.ContentType; + } + else + { + //Webkit, Mozilla + stream = Request.InputStream; + fileName = Request["qqfile"]; + } + + var fileBinary = new byte[stream.Length]; + stream.Read(fileBinary, 0, fileBinary.Length); + + var fileExtension = Path.GetExtension(fileName); + if (!String.IsNullOrEmpty(fileExtension)) + fileExtension = fileExtension.ToLowerInvariant(); + + if (attribute.ValidationFileMaximumSize.HasValue) + { + //compare in bytes + var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; + if (fileBinary.Length > maxFileSizeBytes) + { + //when returning JSON the mime-type must be set to text/plain + //otherwise some browsers will pop-up a "Save As" dialog. + return Json(new + { + success = false, + message = string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), + downloadGuid = Guid.Empty, + }, MimeTypes.TextPlain); + } + } + + var download = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = false, + DownloadUrl = "", + DownloadBinary = fileBinary, + ContentType = contentType, + //we store filename without extension for downloads + Filename = Path.GetFileNameWithoutExtension(fileName), + Extension = fileExtension, + IsNew = true + }; + _downloadService.InsertDownload(download); + + //when returning JSON the mime-type must be set to text/plain + //otherwise some browsers will pop-up a "Save As" dialog. + return Json(new + { + success = true, + message = _localizationService.GetResource("ShoppingCart.FileUploaded"), + downloadUrl = Url.Action("GetFileUpload", "Download", new { downloadId = download.DownloadGuid }), + downloadGuid = download.DownloadGuid, + }, MimeTypes.TextPlain); + } + + [HttpPost] + public ActionResult UploadFileCheckoutAttribute(int attributeId) + { + var attribute = _checkoutAttributeService.GetCheckoutAttributeById(attributeId); + if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) + { + return Json(new + { + success = false, + downloadGuid = Guid.Empty, + }, MimeTypes.TextPlain); + } + + //we process it distinct ways based on a browser + //find more info here http://stackoverflow.com/questions/4884920/mvc3-valums-ajax-file-upload + Stream stream = null; + var fileName = ""; + var contentType = ""; + if (String.IsNullOrEmpty(Request["qqfile"])) + { + // IE + HttpPostedFileBase httpPostedFile = Request.Files[0]; + if (httpPostedFile == null) + throw new ArgumentException("No file uploaded"); + stream = httpPostedFile.InputStream; + fileName = Path.GetFileName(httpPostedFile.FileName); + contentType = httpPostedFile.ContentType; + } + else + { + //Webkit, Mozilla + stream = Request.InputStream; + fileName = Request["qqfile"]; + } + + var fileBinary = new byte[stream.Length]; + stream.Read(fileBinary, 0, fileBinary.Length); + + var fileExtension = Path.GetExtension(fileName); + if (!String.IsNullOrEmpty(fileExtension)) + fileExtension = fileExtension.ToLowerInvariant(); + + if (attribute.ValidationFileMaximumSize.HasValue) + { + //compare in bytes + var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; + if (fileBinary.Length > maxFileSizeBytes) + { + //when returning JSON the mime-type must be set to text/plain + //otherwise some browsers will pop-up a "Save As" dialog. + return Json(new + { + success = false, + message = string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), + downloadGuid = Guid.Empty, + }, MimeTypes.TextPlain); + } + } + + var download = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = false, + DownloadUrl = "", + DownloadBinary = fileBinary, + ContentType = contentType, + //we store filename without extension for downloads + Filename = Path.GetFileNameWithoutExtension(fileName), + Extension = fileExtension, + IsNew = true + }; + _downloadService.InsertDownload(download); + + //when returning JSON the mime-type must be set to text/plain + //otherwise some browsers will pop-up a "Save As" dialog. + return Json(new + { + success = true, + message = _localizationService.GetResource("ShoppingCart.FileUploaded"), + downloadUrl = Url.Action("GetFileUpload", "Download", new { downloadId = download.DownloadGuid }), + downloadGuid = download.DownloadGuid, + }, MimeTypes.TextPlain); + } + + [NopHttpsRequirement(SslRequirement.Yes)] + public ActionResult Cart() + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) + return RedirectToRoute("HomePage"); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new ShoppingCartModel(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + return View(model); + } + + [ChildActionOnly] + public ActionResult OrderSummary(bool? prepareAndDisplayOrderReviewData) + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new ShoppingCartModel(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart, + isEditable: false, + prepareEstimateShippingIfEnabled: false, + prepareAndDisplayOrderReviewData: prepareAndDisplayOrderReviewData.GetValueOrDefault()); + return PartialView(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired("updatecart")] + public ActionResult UpdateCart(FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) + return RedirectToRoute("HomePage"); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + var allIdsToRemove = form["removefromcart"] != null ? form["removefromcart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => int.Parse(x)).ToList() : new List(); + + //current warnings + var innerWarnings = new Dictionary>(); + foreach (var sci in cart) + { + bool remove = allIdsToRemove.Contains(sci.Id); + if (remove) + _shoppingCartService.DeleteShoppingCartItem(sci, ensureOnlyActiveCheckoutAttributes: true); + else + { + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("itemquantity{0}", sci.Id), StringComparison.InvariantCultureIgnoreCase)) + { + int newQuantity; + if (int.TryParse(form[formKey], out newQuantity)) + { + var currSciWarnings = _shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, + sci.Id, sci.AttributesXml, sci.CustomerEnteredPrice, + sci.RentalStartDateUtc, sci.RentalEndDateUtc, + newQuantity, true); + innerWarnings.Add(sci.Id, currSciWarnings); + } + break; + } + } + } + + //parse and save checkout attributes + ParseAndSaveCheckoutAttributes(cart, form); + + //updated cart + cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new ShoppingCartModel(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + //update current warnings + foreach (var kvp in innerWarnings) + { + //kvp = + var sciId = kvp.Key; + var warnings = kvp.Value; + //find model + var sciModel = model.Items.FirstOrDefault(x => x.Id == sciId); + if (sciModel != null) + foreach (var w in warnings) + if (!sciModel.Warnings.Contains(w)) + sciModel.Warnings.Add(w); + } + return View(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired("continueshopping")] + public ActionResult ContinueShopping() + { + var returnUrl = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.LastContinueShoppingPage, _storeContext.CurrentStore.Id); + if (!String.IsNullOrEmpty(returnUrl)) + { + return Redirect(returnUrl); + } + else + { + return RedirectToRoute("HomePage"); + } + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired("checkout")] + public ActionResult StartCheckout(FormCollection form) + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + //parse and save checkout attributes + ParseAndSaveCheckoutAttributes(cart, form); + + //validate attributes + var checkoutAttributes = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService, _storeContext.CurrentStore.Id); + var checkoutAttributeWarnings = _shoppingCartService.GetShoppingCartWarnings(cart, checkoutAttributes, true); + if (checkoutAttributeWarnings.Any()) + { + //something wrong, redisplay the page with warnings + var model = new ShoppingCartModel(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart, validateCheckoutAttributes: true); + return View(model); + } + + //everything is OK + if (_workContext.CurrentCustomer.IsGuest()) + { + bool downloadableProductsRequireRegistration = + _customerSettings.RequireRegistrationForDownloadableProducts && cart.Any(sci => sci.Product.IsDownload); + + if (!_orderSettings.AnonymousCheckoutAllowed + || downloadableProductsRequireRegistration) + return new HttpUnauthorizedResult(); + + return RedirectToRoute("LoginCheckoutAsGuest", new {returnUrl = Url.RouteUrl("ShoppingCart")}); + } + + return RedirectToRoute("Checkout"); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired("applydiscountcouponcode")] + public ActionResult ApplyDiscountCoupon(string discountcouponcode, FormCollection form) + { + //trim + if (discountcouponcode != null) + discountcouponcode = discountcouponcode.Trim(); + + //cart + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + //parse and save checkout attributes + ParseAndSaveCheckoutAttributes(cart, form); + + var model = new ShoppingCartModel(); + if (!String.IsNullOrWhiteSpace(discountcouponcode)) + { + //we find even hidden records here. this way we can display a user-friendly message if it's expired + var discounts = _discountService.GetAllDiscountsForCaching(couponCode: discountcouponcode, showHidden: true) + .Where(d => d.RequiresCouponCode) + .ToList(); + if (discounts.Any()) + { + string userError = ""; + bool anyValidDiscount = false; + foreach (var discount in discounts) + { + var validationResult = _discountService.ValidateDiscount(discount, _workContext.CurrentCustomer, new[] { discountcouponcode }); + if (validationResult.IsValid) + { + anyValidDiscount = true; + break; + } + else + { + if (!String.IsNullOrEmpty(validationResult.UserError)) + userError = validationResult.UserError; + } + } + + if (anyValidDiscount) + { + //valid + _workContext.CurrentCustomer.ApplyDiscountCouponCode(discountcouponcode); + model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.Applied"); + model.DiscountBox.IsApplied = true; + } + else + { + if (!String.IsNullOrEmpty(userError)) + { + //some user error + model.DiscountBox.Message = userError; + model.DiscountBox.IsApplied = false; + } + else + { + //general error text + model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); + model.DiscountBox.IsApplied = false; + } + } + } + else + { + //discount cannot be found + model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); + model.DiscountBox.IsApplied = false; + } + } + else + { + //empty coupon code + model.DiscountBox.Message = _localizationService.GetResource("ShoppingCart.DiscountCouponCode.WrongDiscount"); + model.DiscountBox.IsApplied = false; + } + + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + return View(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired("applygiftcardcouponcode")] + public ActionResult ApplyGiftCard(string giftcardcouponcode, FormCollection form) + { + //trim + if (giftcardcouponcode != null) + giftcardcouponcode = giftcardcouponcode.Trim(); + + //cart + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + //parse and save checkout attributes + ParseAndSaveCheckoutAttributes(cart, form); + + var model = new ShoppingCartModel(); + if (!cart.IsRecurring()) + { + if (!String.IsNullOrWhiteSpace(giftcardcouponcode)) + { + var giftCard = _giftCardService.GetAllGiftCards(giftCardCouponCode: giftcardcouponcode).FirstOrDefault(); + bool isGiftCardValid = giftCard != null && giftCard.IsGiftCardValid(); + if (isGiftCardValid) + { + _workContext.CurrentCustomer.ApplyGiftCardCouponCode(giftcardcouponcode); + model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.Applied"); + model.GiftCardBox.IsApplied = true; + } + else + { + model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); + model.GiftCardBox.IsApplied = false; + } + } + else + { + model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); + model.GiftCardBox.IsApplied = false; + } + } + else + { + model.GiftCardBox.Message = _localizationService.GetResource("ShoppingCart.GiftCardCouponCode.DontWorkWithAutoshipProducts"); + model.GiftCardBox.IsApplied = false; + } + + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + return View(model); + } + + [ValidateInput(false)] + [PublicAntiForgery] + [HttpPost] + public ActionResult GetEstimateShipping(int? countryId, int? stateProvinceId, string zipPostalCode, FormCollection form) + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + //parse and save checkout attributes + ParseAndSaveCheckoutAttributes(cart, form); + + var model = _shoppingCartModelFactory.PrepareEstimateShippingResultModel(cart, countryId, stateProvinceId, zipPostalCode); + return PartialView("_EstimateShippingResult", model); + } + + [ChildActionOnly] + public ActionResult OrderTotals(bool isEditable) + { + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + var model = _shoppingCartModelFactory.PrepareOrderTotalsModel(cart, isEditable); + return PartialView(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired(FormValueRequirement.StartsWith, "removediscount-")] + public ActionResult RemoveDiscountCoupon(FormCollection form) + { + var model = new ShoppingCartModel(); + + //get discount identifier + int discountId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("removediscount-", StringComparison.InvariantCultureIgnoreCase)) + discountId = Convert.ToInt32(formValue.Substring("removediscount-".Length)); + var discount = _discountService.GetDiscountById(discountId); + if (discount != null) + _workContext.CurrentCustomer.RemoveDiscountCouponCode(discount.CouponCode); + + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + return View(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Cart")] + [FormValueRequired(FormValueRequirement.StartsWith, "removegiftcard-")] + public ActionResult RemoveGiftCardCode(FormCollection form) + { + var model = new ShoppingCartModel(); + + //get gift card identifier + int giftCardId = 0; + foreach (var formValue in form.AllKeys) + if (formValue.StartsWith("removegiftcard-", StringComparison.InvariantCultureIgnoreCase)) + giftCardId = Convert.ToInt32(formValue.Substring("removegiftcard-".Length)); + var gc = _giftCardService.GetGiftCardById(giftCardId); + if (gc != null) + _workContext.CurrentCustomer.RemoveGiftCardCouponCode(gc.GiftCardCouponCode); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + model = _shoppingCartModelFactory.PrepareShoppingCartModel(model, cart); + return View(model); + } + + [ChildActionOnly] + public ActionResult FlyoutShoppingCart() + { + if (!_shoppingCartSettings.MiniShoppingCartEnabled) + return Content(""); + + if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) + return Content(""); + + var model = _shoppingCartModelFactory.PrepareMiniShoppingCartModel(); + return PartialView(model); + } + + #endregion + + #region Wishlist + + [NopHttpsRequirement(SslRequirement.Yes)] + public ActionResult Wishlist(Guid? customerGuid) + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) + return RedirectToRoute("HomePage"); + + Customer customer = customerGuid.HasValue ? + _customerService.GetCustomerByGuid(customerGuid.Value) + : _workContext.CurrentCustomer; + if (customer == null) + return RedirectToRoute("HomePage"); + var cart = customer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new WishlistModel(); + model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart, !customerGuid.HasValue); + return View(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Wishlist")] + [FormValueRequired("updatecart")] + public ActionResult UpdateWishlist(FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) + return RedirectToRoute("HomePage"); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + var allIdsToRemove = form["removefromcart"] != null + ? form["removefromcart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToList() + : new List(); + + //current warnings + var innerWarnings = new Dictionary>(); + foreach (var sci in cart) + { + bool remove = allIdsToRemove.Contains(sci.Id); + if (remove) + _shoppingCartService.DeleteShoppingCartItem(sci); + else + { + foreach (string formKey in form.AllKeys) + if (formKey.Equals(string.Format("itemquantity{0}", sci.Id), StringComparison.InvariantCultureIgnoreCase)) + { + int newQuantity; + if (int.TryParse(form[formKey], out newQuantity)) + { + var currSciWarnings = _shoppingCartService.UpdateShoppingCartItem(_workContext.CurrentCustomer, + sci.Id, sci.AttributesXml, sci.CustomerEnteredPrice, + sci.RentalStartDateUtc, sci.RentalEndDateUtc, + newQuantity, true); + innerWarnings.Add(sci.Id, currSciWarnings); + } + break; + } + } + } + + //updated wishlist + cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new WishlistModel(); + model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart); + //update current warnings + foreach (var kvp in innerWarnings) + { + //kvp = + var sciId = kvp.Key; + var warnings = kvp.Value; + //find model + var sciModel = model.Items.FirstOrDefault(x => x.Id == sciId); + if (sciModel != null) + foreach (var w in warnings) + if (!sciModel.Warnings.Contains(w)) + sciModel.Warnings.Add(w); + } + return View(model); + } + + [ValidateInput(false)] + [HttpPost, ActionName("Wishlist")] + [FormValueRequired("addtocartbutton")] + public ActionResult AddItemsToCartFromWishlist(Guid? customerGuid, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart)) + return RedirectToRoute("HomePage"); + + if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist)) + return RedirectToRoute("HomePage"); + + var pageCustomer = customerGuid.HasValue + ? _customerService.GetCustomerByGuid(customerGuid.Value) + : _workContext.CurrentCustomer; + if (pageCustomer == null) + return RedirectToRoute("HomePage"); + + var pageCart = pageCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + var allWarnings = new List(); + var numberOfAddedItems = 0; + var allIdsToAdd = form["addtocart"] != null + ? form["addtocart"].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToList() + : new List(); + foreach (var sci in pageCart) + { + if (allIdsToAdd.Contains(sci.Id)) + { + var warnings = _shoppingCartService.AddToCart(_workContext.CurrentCustomer, + sci.Product, ShoppingCartType.ShoppingCart, + _storeContext.CurrentStore.Id, + sci.AttributesXml, sci.CustomerEnteredPrice, + sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, true); + if (!warnings.Any()) + numberOfAddedItems++; + if (_shoppingCartSettings.MoveItemsFromWishlistToCart && //settings enabled + !customerGuid.HasValue && //own wishlist + !warnings.Any()) //no warnings ( already in the cart) + { + //let's remove the item from wishlist + _shoppingCartService.DeleteShoppingCartItem(sci); + } + allWarnings.AddRange(warnings); + } + } + + if (numberOfAddedItems > 0) + { + //redirect to the shopping cart page + + if (allWarnings.Any()) + { + ErrorNotification(_localizationService.GetResource("Wishlist.AddToCart.Error"), true); + } + + return RedirectToRoute("ShoppingCart"); + } + else + { + //no items added. redisplay the wishlist page + + if (allWarnings.Any()) + { + ErrorNotification(_localizationService.GetResource("Wishlist.AddToCart.Error"), false); + } + + var cart = pageCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + var model = new WishlistModel(); + model = _shoppingCartModelFactory.PrepareWishlistModel(model, cart, !customerGuid.HasValue); + return View(model); + } + } + + [NopHttpsRequirement(SslRequirement.Yes)] + public ActionResult EmailWishlist() + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist) || !_shoppingCartSettings.EmailWishlistEnabled) + return RedirectToRoute("HomePage"); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + if (!cart.Any()) + return RedirectToRoute("HomePage"); + + var model = new WishlistEmailAFriendModel(); + model = _shoppingCartModelFactory.PrepareWishlistEmailAFriendModel(model, false); + return View(model); + } + + [HttpPost, ActionName("EmailWishlist")] + [PublicAntiForgery] + [FormValueRequired("send-email")] + [CaptchaValidator] + public ActionResult EmailWishlistSend(WishlistEmailAFriendModel model, bool captchaValid) + { + if (!_permissionService.Authorize(StandardPermissionProvider.EnableWishlist) || !_shoppingCartSettings.EmailWishlistEnabled) + return RedirectToRoute("HomePage"); + + var cart = _workContext.CurrentCustomer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.Wishlist) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + if (!cart.Any()) + return RedirectToRoute("HomePage"); + + //validate CAPTCHA + if (_captchaSettings.Enabled && _captchaSettings.ShowOnEmailWishlistToFriendPage && !captchaValid) + { + ModelState.AddModelError("", _captchaSettings.GetWrongCaptchaMessage(_localizationService)); + } + + //check whether the current customer is guest and ia allowed to email wishlist + if (_workContext.CurrentCustomer.IsGuest() && !_shoppingCartSettings.AllowAnonymousUsersToEmailWishlist) + { + ModelState.AddModelError("", _localizationService.GetResource("Wishlist.EmailAFriend.OnlyRegisteredUsers")); + } + + if (ModelState.IsValid) + { + //email + _workflowMessageService.SendWishlistEmailAFriendMessage(_workContext.CurrentCustomer, + _workContext.WorkingLanguage.Id, model.YourEmailAddress, + model.FriendEmail, Core.Html.HtmlHelper.FormatText(model.PersonalMessage, false, true, false, false, false, false)); + + model.SuccessfullySent = true; + model.Result = _localizationService.GetResource("Wishlist.EmailAFriend.SuccessfullySent"); + + return View(model); + } + + //If we got this far, something failed, redisplay form + model = _shoppingCartModelFactory.PrepareWishlistEmailAFriendModel(model, true); + return View(model); + } + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs index 468a52a947a..41c67093831 100644 --- a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs @@ -387,7 +387,7 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte ProductSeName = sci.Product.GetSeName(), Quantity = sci.Quantity, AttributeInfo = _productAttributeFormatter.FormatAttributes(sci.Product, sci.AttributesXml), - VatRate = sci.VatRate ?? decimal.Zero + VatRate = sci.VatRate == null ? "" : sci.VatRate.Value.ToString("G29") }; //allow editing? @@ -438,9 +438,11 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte } //unit prices + decimal itemSubtotal; + decimal taxRate; decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate); - cartItemModel.VatRate = taxRate; + cartItemModel.VatRate = taxRate.ToString("G29"); if (sci.Product.CallForPrice) { @@ -458,6 +460,7 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte if (sci.Product.CallForPrice) { cartItemModel.SubTotal = _localizationService.GetResource("Products.CallForPrice"); + itemSubtotal = decimal.Zero; } else { @@ -468,6 +471,7 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte //decimal taxRate; decimal shoppingCartItemSubTotalWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetSubTotal(sci, true, out shoppingCartItemDiscountBase, out scDiscounts, out maximumDiscountQty), out taxRate); decimal shoppingCartItemSubTotalWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartItemSubTotalWithDiscountBase, _workContext.WorkingCurrency); + itemSubtotal = shoppingCartItemSubTotalWithDiscount; cartItemModel.SubTotal = _priceFormatter.FormatPrice(shoppingCartItemSubTotalWithDiscount); cartItemModel.MaximumDiscountedQty = maximumDiscountQty; @@ -482,6 +486,13 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte } } } + //attribute tax info + if (!String.IsNullOrEmpty(sci.AttributesXml)) + { + cartItemModel.AttributeInfo = _productAttributeFormatter.FormatAttributes(sci.Product, sci.AttributesXml, _workContext.CurrentCustomer, subTotal: itemSubtotal); + if (sci.AttributesXml.Contains("(); - Warnings = new List(); - EstimateShipping = new EstimateShippingModel(); - DiscountBox = new DiscountBoxModel(); - GiftCardBox = new GiftCardBoxModel(); - CheckoutAttributes = new List(); - OrderReviewData = new OrderReviewDataModel(); - - ButtonPaymentMethodActionNames = new List(); - ButtonPaymentMethodControllerNames = new List(); - ButtonPaymentMethodRouteValues = new List(); - } - - public bool OnePageCheckoutEnabled { get; set; } - - public bool ShowSku { get; set; } - public bool ShowProductImages { get; set; } - public bool IsEditable { get; set; } - public IList Items { get; set; } - - public string CheckoutAttributeInfo { get; set; } - public IList CheckoutAttributes { get; set; } - - public IList Warnings { get; set; } - public string MinOrderSubtotalWarning { get; set; } - public bool DisplayTaxShippingInfo { get; set; } - public bool TermsOfServiceOnShoppingCartPage { get; set; } - public bool TermsOfServiceOnOrderConfirmPage { get; set; } - public EstimateShippingModel EstimateShipping { get; set; } - public DiscountBoxModel DiscountBox { get; set; } - public GiftCardBoxModel GiftCardBox { get; set; } - public OrderReviewDataModel OrderReviewData { get; set; } - - public IList ButtonPaymentMethodActionNames { get; set; } - public IList ButtonPaymentMethodControllerNames { get; set; } - public IList ButtonPaymentMethodRouteValues { get; set; } - - public bool HideCheckoutButton { get; set; } - - #region Nested Classes - - public partial class ShoppingCartItemModel : BaseNopEntityModel - { - public ShoppingCartItemModel() - { - Picture = new PictureModel(); - AllowedQuantities = new List(); - Warnings = new List(); - } - public string Sku { get; set; } - - public PictureModel Picture {get;set;} - - public int ProductId { get; set; } - - public string ProductName { get; set; } - - public string ProductSeName { get; set; } - - public string UnitPrice { get; set; } - - public string SubTotal { get; set; } - - public string Discount { get; set; } - public int? MaximumDiscountedQty { get; set; } - - public int Quantity { get; set; } - public List AllowedQuantities { get; set; } - - public string AttributeInfo { get; set; } - - public string RecurringInfo { get; set; } - - public string RentalInfo { get; set; } - - public bool AllowItemEditing { get; set; } - - public bool DisableRemoval { get; set; } - - public IList Warnings { get; set; } - public decimal VatRate { get; set; } - - } - - public partial class CheckoutAttributeModel : BaseNopEntityModel - { - public CheckoutAttributeModel() - { - AllowedFileExtensions = new List(); - Values = new List(); - } - - public string Name { get; set; } - - public string DefaultValue { get; set; } - - public string TextPrompt { get; set; } - - public bool IsRequired { get; set; } - - /// - /// Selected day value for datepicker - /// - public int? SelectedDay { get; set; } - /// - /// Selected month value for datepicker - /// - public int? SelectedMonth { get; set; } - /// - /// Selected year value for datepicker - /// - public int? SelectedYear { get; set; } - - /// - /// Allowed file extensions for customer uploaded files - /// - public IList AllowedFileExtensions { get; set; } - - public AttributeControlType AttributeControlType { get; set; } - - public IList Values { get; set; } - } - - public partial class CheckoutAttributeValueModel : BaseNopEntityModel - { - public string Name { get; set; } - - public string ColorSquaresRgb { get; set; } - - public string PriceAdjustment { get; set; } - - public bool IsPreSelected { get; set; } - } - - public partial class DiscountBoxModel: BaseNopModel - { - public DiscountBoxModel() - { - AppliedDiscountsWithCodes = new List(); - } - - public List AppliedDiscountsWithCodes { get; set; } - public bool Display { get; set; } - public string Message { get; set; } - public bool IsApplied { get; set; } - - public class DiscountInfoModel : BaseNopEntityModel - { - public string CouponCode { get; set; } - } - } - - public partial class GiftCardBoxModel : BaseNopModel - { - public bool Display { get; set; } - public string Message { get; set; } - public bool IsApplied { get; set; } - } - - public partial class OrderReviewDataModel : BaseNopModel - { - public OrderReviewDataModel() - { - this.BillingAddress = new AddressModel(); - this.ShippingAddress = new AddressModel(); - this.PickupAddress = new AddressModel(); - this.CustomValues= new Dictionary(); - } - public bool Display { get; set; } - - public AddressModel BillingAddress { get; set; } - - public bool IsShippable { get; set; } - public AddressModel ShippingAddress { get; set; } - public bool SelectedPickUpInStore { get; set; } - public AddressModel PickupAddress { get; set; } - public string ShippingMethod { get; set; } - - public string PaymentMethod { get; set; } - - public Dictionary CustomValues { get; set; } - } - #endregion - } +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Routing; +using Nop.Core.Domain.Catalog; +using Nop.Web.Framework.Mvc; +using Nop.Web.Models.Common; +using Nop.Web.Models.Media; + +namespace Nop.Web.Models.ShoppingCart +{ + public partial class ShoppingCartModel : BaseNopModel + { + public ShoppingCartModel() + { + Items = new List(); + Warnings = new List(); + EstimateShipping = new EstimateShippingModel(); + DiscountBox = new DiscountBoxModel(); + GiftCardBox = new GiftCardBoxModel(); + CheckoutAttributes = new List(); + OrderReviewData = new OrderReviewDataModel(); + + ButtonPaymentMethodActionNames = new List(); + ButtonPaymentMethodControllerNames = new List(); + ButtonPaymentMethodRouteValues = new List(); + } + + public bool OnePageCheckoutEnabled { get; set; } + + public bool ShowSku { get; set; } + public bool ShowProductImages { get; set; } + public bool IsEditable { get; set; } + public IList Items { get; set; } + + public string CheckoutAttributeInfo { get; set; } + public IList CheckoutAttributes { get; set; } + + public IList Warnings { get; set; } + public string MinOrderSubtotalWarning { get; set; } + public bool DisplayTaxShippingInfo { get; set; } + public bool TermsOfServiceOnShoppingCartPage { get; set; } + public bool TermsOfServiceOnOrderConfirmPage { get; set; } + public EstimateShippingModel EstimateShipping { get; set; } + public DiscountBoxModel DiscountBox { get; set; } + public GiftCardBoxModel GiftCardBox { get; set; } + public OrderReviewDataModel OrderReviewData { get; set; } + + public IList ButtonPaymentMethodActionNames { get; set; } + public IList ButtonPaymentMethodControllerNames { get; set; } + public IList ButtonPaymentMethodRouteValues { get; set; } + + public bool HideCheckoutButton { get; set; } + + #region Nested Classes + + public partial class ShoppingCartItemModel : BaseNopEntityModel + { + public ShoppingCartItemModel() + { + Picture = new PictureModel(); + AllowedQuantities = new List(); + Warnings = new List(); + } + public string Sku { get; set; } + + public PictureModel Picture {get;set;} + + public int ProductId { get; set; } + + public string ProductName { get; set; } + + public string ProductSeName { get; set; } + + public string UnitPrice { get; set; } + + public string SubTotal { get; set; } + + public string Discount { get; set; } + public int? MaximumDiscountedQty { get; set; } + + public int Quantity { get; set; } + public List AllowedQuantities { get; set; } + + public string AttributeInfo { get; set; } + + public string RecurringInfo { get; set; } + + public string RentalInfo { get; set; } + + public bool AllowItemEditing { get; set; } + + public bool DisableRemoval { get; set; } + + public IList Warnings { get; set; } + public string VatRate { get; set; } + + } + + public partial class CheckoutAttributeModel : BaseNopEntityModel + { + public CheckoutAttributeModel() + { + AllowedFileExtensions = new List(); + Values = new List(); + } + + public string Name { get; set; } + + public string DefaultValue { get; set; } + + public string TextPrompt { get; set; } + + public bool IsRequired { get; set; } + + /// + /// Selected day value for datepicker + /// + public int? SelectedDay { get; set; } + /// + /// Selected month value for datepicker + /// + public int? SelectedMonth { get; set; } + /// + /// Selected year value for datepicker + /// + public int? SelectedYear { get; set; } + + /// + /// Allowed file extensions for customer uploaded files + /// + public IList AllowedFileExtensions { get; set; } + + public AttributeControlType AttributeControlType { get; set; } + + public IList Values { get; set; } + } + + public partial class CheckoutAttributeValueModel : BaseNopEntityModel + { + public string Name { get; set; } + + public string ColorSquaresRgb { get; set; } + + public string PriceAdjustment { get; set; } + + public bool IsPreSelected { get; set; } + } + + public partial class DiscountBoxModel: BaseNopModel + { + public DiscountBoxModel() + { + AppliedDiscountsWithCodes = new List(); + } + + public List AppliedDiscountsWithCodes { get; set; } + public bool Display { get; set; } + public string Message { get; set; } + public bool IsApplied { get; set; } + + public class DiscountInfoModel : BaseNopEntityModel + { + public string CouponCode { get; set; } + } + } + + public partial class GiftCardBoxModel : BaseNopModel + { + public bool Display { get; set; } + public string Message { get; set; } + public bool IsApplied { get; set; } + } + + public partial class OrderReviewDataModel : BaseNopModel + { + public OrderReviewDataModel() + { + this.BillingAddress = new AddressModel(); + this.ShippingAddress = new AddressModel(); + this.PickupAddress = new AddressModel(); + this.CustomValues= new Dictionary(); + } + public bool Display { get; set; } + + public AddressModel BillingAddress { get; set; } + + public bool IsShippable { get; set; } + public AddressModel ShippingAddress { get; set; } + public bool SelectedPickUpInStore { get; set; } + public AddressModel PickupAddress { get; set; } + public string ShippingMethod { get; set; } + + public string PaymentMethod { get; set; } + + public Dictionary CustomValues { get; set; } + } + #endregion + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Views/Order/Details.cshtml b/src/Presentation/Nop.Web/Views/Order/Details.cshtml index 4916cc90cd7..b5d7d22a0b4 100644 --- a/src/Presentation/Nop.Web/Views/Order/Details.cshtml +++ b/src/Presentation/Nop.Web/Views/Order/Details.cshtml @@ -1,772 +1,772 @@ -@model OrderDetailsModel -@using Nop.Web.Models.Order -@{ - if (!Model.PrintMode) - { - Layout = "~/Views/Shared/_ColumnsOne.cshtml"; - } - else - { - Layout = "~/Views/Shared/_Print.cshtml"; - } - //title - Html.AddTitleParts(T("PageTitle.OrderDetails").Text); - //page class - Html.AppendPageCssClassParts("html-order-details-page"); -} -@if (Model.PrintMode) -{ - -} -
- @if (!Model.PrintMode) - { -
-

@T("Order.OrderInformation")

- @T("Order.Print") - @if (!Model.PdfInvoiceDisabled) - { - @T("Order.GetPDFInvoice") - } -
- } -
- @Html.Widget("orderdetails_page_top", Model.Id) -
-
- @T("Order.Order#")@Model.Id -
-
    -
  • - @T("Order.OrderDate"): @Model.CreatedOn.ToString("D") -
  • -
  • - @T("Order.OrderStatus"): @Model.OrderStatus -
  • -
  • - @T("Order.OrderTotal"): @Model.OrderTotal -
  • -
- @Html.Widget("orderdetails_page_overview", Model.Id) -
-
-
-
- @T("Order.BillingAddress") -
-
-
    -
  • - @Model.BillingAddress.FirstName @Model.BillingAddress.LastName -
  • - - @if (Model.BillingAddress.PhoneEnabled) - { -
  • - @T("Order.Phone"): @Model.BillingAddress.PhoneNumber -
  • - } - @if (Model.BillingAddress.FaxEnabled) - { -
  • - @T("Order.Fax"): @Model.BillingAddress.FaxNumber -
  • - } - @if (Model.BillingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.BillingAddress.Company)) - { -
  • - @Model.BillingAddress.Company -
  • - } - @if (Model.BillingAddress.StreetAddressEnabled) - { -
  • - @Model.BillingAddress.Address1 -
  • - } - @if (Model.BillingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.BillingAddress.Address2)) - { -
  • - @Model.BillingAddress.Address2 -
  • - } - @if (Model.BillingAddress.CityEnabled || - Model.BillingAddress.StateProvinceEnabled || - Model.BillingAddress.ZipPostalCodeEnabled) - { -
  • - @if (Model.BillingAddress.CityEnabled) - { - @Model.BillingAddress.City - } - @if (Model.BillingAddress.CityEnabled && (Model.BillingAddress.StateProvinceEnabled || Model.BillingAddress.ZipPostalCodeEnabled)) - { - , - } - @if (Model.BillingAddress.StateProvinceEnabled) - { - @Model.BillingAddress.StateProvinceName - } - @if (Model.BillingAddress.ZipPostalCodeEnabled) - { - @Model.BillingAddress.ZipPostalCode - } -
  • - } - @if (Model.BillingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.BillingAddress.CountryName)) - { -
  • - @Model.BillingAddress.CountryName -
  • - } - @if (!String.IsNullOrEmpty(Model.VatNumber)) - { -
  • - - @T("Order.VATNumber") - - - @Model.VatNumber - -
  • - } - @if (!String.IsNullOrEmpty(Model.BillingAddress.FormattedCustomAddressAttributes)) - { -
  • - @Html.Raw(Model.BillingAddress.FormattedCustomAddressAttributes) -
  • - } - @if (Model.CustomValues != null) - { - foreach (var item in Model.CustomValues) - { -
  • - - @item.Key: - - - @(item.Value != null ? item.Value.ToString() : "") - -
  • - } - } -
- @if (!String.IsNullOrEmpty(Model.PaymentMethod)) - { -
-
    -
  • - @T("Order.Payment") -
  • -
  • - - @T("Order.Payment.Method"): - - - @Model.PaymentMethod - -
  • - @if (!Model.PrintMode) - { -
  • - - @T("Order.Payment.Status"): - - - @Model.PaymentMethodStatus - -
  • - } - @if (!Model.PrintMode && Model.CanRePostProcessPayment) - { - @*Complete payment (for redirection payment methods)*@ -
  • - @using (Html.BeginRouteForm("OrderDetails", FormMethod.Post)) - { - @Html.AntiForgeryToken() - -

    - @T("Order.RetryPayment.Hint") -

    - } -
  • - } -
-
- } -
-
- @if (Model.IsShippable) - { -
-
- @(Model.PickUpInStore ? T("Order.PickupAddress") : T("Order.ShippingAddress")) -
-
-
    - @if (!Model.PickUpInStore) - { -
  • - @Model.ShippingAddress.FirstName @Model.ShippingAddress.LastName -
  • - - if (Model.ShippingAddress.PhoneEnabled) - { -
  • - @T("Order.Phone"): @Model.ShippingAddress.PhoneNumber -
  • - } - if (Model.ShippingAddress.FaxEnabled) - { -
  • - @T("Order.Fax"): @Model.ShippingAddress.FaxNumber -
  • - } - if (Model.ShippingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.Company)) - { -
  • - @Model.ShippingAddress.Company -
  • - } - if (Model.ShippingAddress.StreetAddressEnabled) - { -
  • - @Model.ShippingAddress.Address1 -
  • - } - if (Model.ShippingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.ShippingAddress.Address2)) - { -
  • - @Model.ShippingAddress.Address2 -
  • - } - if (Model.ShippingAddress.CityEnabled || - Model.ShippingAddress.StateProvinceEnabled || - Model.ShippingAddress.ZipPostalCodeEnabled) - { -
  • - @if (Model.ShippingAddress.CityEnabled) - { - @Model.ShippingAddress.City - } - @if (Model.ShippingAddress.CityEnabled && (Model.ShippingAddress.StateProvinceEnabled || Model.ShippingAddress.ZipPostalCodeEnabled)) - { - , - } - @if (Model.ShippingAddress.StateProvinceEnabled) - { - @Model.ShippingAddress.StateProvinceName - } - @if (Model.ShippingAddress.ZipPostalCodeEnabled) - { - @Model.ShippingAddress.ZipPostalCode - } -
  • - } - if (Model.ShippingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.CountryName)) - { -
  • - @Model.ShippingAddress.CountryName -
  • - } - if (!String.IsNullOrEmpty(Model.ShippingAddress.FormattedCustomAddressAttributes)) - { -
  • - @Html.Raw(Model.ShippingAddress.FormattedCustomAddressAttributes) -
  • - } - } - else - { - if (!string.IsNullOrEmpty(Model.PickupAddress.Address1)) - { -
  • - @Model.PickupAddress.Address1 -
  • - } - if (!string.IsNullOrEmpty(Model.PickupAddress.City) || !string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) - { -
  • - @if (!string.IsNullOrEmpty(Model.PickupAddress.City)) - { - @Model.PickupAddress.City - } - @if (!string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) - { - , - @Model.PickupAddress.ZipPostalCode - } -
  • - } - if (!string.IsNullOrEmpty(Model.PickupAddress.CountryName)) - { -
  • - @Model.PickupAddress.CountryName -
  • - } - } -
-
-
    -
  • - @T("Order.Shipping") -
  • -
  • - - @T("Order.Shipping.Name"): - - - @Model.ShippingMethod - -
  • - @if (!Model.PrintMode) - { -
  • - - @T("Order.Shipping.Status"): - - - @Model.ShippingStatus - -
  • - } -
-
-
-
- } -
- @if (!Model.PrintMode && Model.Shipments.Count > 0) - { -
-
- @T("Order.Shipments") -
-
- - - - - - - - - - - - - - - - - - - @foreach (var item in Model.Shipments) - { - - - - - - - - } - -
- @T("Order.Shipments.ID") - - @T("Order.Shipments.TrackingNumber") - - @T("Order.Shipments.ShippedDate") - - @T("Order.Shipments.DeliveryDate") - - @T("Order.Shipments.ViewDetails") -
- - @item.Id.ToString() - - - @item.TrackingNumber - - - @if (item.ShippedDate.HasValue) - { - @item.ShippedDate.Value.ToString("D") - } - else - { - @T("Order.Shipments.ShippedDate.NotYet") - } - - - @if (item.DeliveryDate.HasValue) - { - @item.DeliveryDate.Value.ToString("D") - } - else - { - @T("Order.Shipments.DeliveryDate.NotYet") - } - - @T("Order.Shipments.ViewDetails") -
-
-
- } - @if (Model.Items.Count > 0) - { - if (!Model.PrintMode && Model.OrderNotes.Count > 0) - { -
-
- @T("Order.Notes") -
-
- - - - - - - - - - - - - @foreach (var item in Model.OrderNotes) - { - - - - - } - -
- @T("Order.Notes.CreatedOn") - - @T("Order.Notes.Note") -
- @item.CreatedOn.ToString() - - @Html.Raw(item.Note) - @if (item.HasDownload) - { -

- @T("Order.Notes.Download") -

- } -
-
-
- } - @Html.Widget("orderdetails_page_beforeproducts", Model.Id) -
-
- @T("Order.Product(s)") -
-
- - - @if (Model.ShowSku) - { - - } - - - - - - - - - @if (Model.ShowSku) - { - - } - - - - - - - - - @foreach (var item in Model.Items) - { - - @if (Model.ShowSku) - { - - } - - - - - - - } - -
- @T("Order.Product(s).SKU") - - @T("Order.Product(s).Name") - - @T("Order.Product(s).Price") - - @T("Order.Product(s).Quantity") - - @T("Order.TaxRateLine.VatRate") - - @T("Order.Product(s).Total") -
- - @item.Sku - - @if (!Model.PrintMode) - { - @item.ProductName - } - else - { - @item.ProductName - } - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
- @Html.Raw(item.AttributeInfo) -
- } - @if (!String.IsNullOrEmpty(item.RentalInfo)) - { -
- @Html.Raw(item.RentalInfo) -
- } - @if (item.DownloadId > 0) - { - - } - @if (item.LicenseId > 0) - { - - } - @Html.Widget("orderdetails_product_line", item.Id) -
- - @item.UnitPrice - - - @item.Quantity - - - @item.VatRate.ToString("G29") - - - @item.SubTotal -
-
- @if (Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) - { - var inclTax = Model.PricesIncludeTax; - //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here - //of course, you can modify appropriate locales to include VAT info there -
- @T(inclTax ? "Order.TaxShipping.InclTax" : "Order.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) -
- } -
- @Html.Widget("orderdetails_page_afterproducts", Model.Id) -
- @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) - { -
- @Html.Raw(Model.CheckoutAttributeInfo) -
- } -
-
-
- - - - - - - @if (!string.IsNullOrEmpty(Model.OrderSubTotalDiscount)) - { - - - - - } - @if (Model.IsShippable) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFee)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.OrderAmount)) - { - - - - - } - @if (Model.DisplayTax) - { - - - - - } - @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) - { - - - - } - @if (Model.GiftCards.Count > 0) - { - foreach (var gc in Model.GiftCards) - { - - - - - } - } - @if (Model.RedeemedRewardPoints > 0) - { - - - - - } - - - - - -
- - - @Model.OrderSubtotal -
- - - @Model.OrderSubTotalDiscount -
- - - @Model.OrderShipping -
- - - @Model.PaymentMethodAdditionalFee -
- - - @Model.OrderTotalDiscount -
- - - @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount) -
- - - @Model.Tax -
-
    -
  • - @T("Order.TaxRateLine.VatRate") - @if (Model.includingTax) - { - @T("Order.TaxRateLine.AmountIncl") - } - else - { - @T("Order.TaxRateLine.Amount") - } - @if (Model.includingTax) - { - @T("Order.TaxRateLine.DiscountAmountIncl") - } - else - { - @T("Order.TaxRateLine.DiscountAmount") - } - @T("Order.TaxRateLine.BaseAmount") - @T("Order.TaxRateLine.VatAmount") -
  • - @foreach (var taxRate in Model.TaxRates) - { -
  • - @taxRate.Rate - @taxRate.Amount - @taxRate.DiscountAmount - @taxRate.BaseAmount - @taxRate.VatAmount -
  • - } -
-
- - - @gc.Amount -
- - - @Model.RedeemedRewardPointsAmount -
- - - @Model.OrderTotal -
-
- @if (!Model.PrintMode) - { -
- @if (Model.IsReOrderAllowed) - { - - } - @if (Model.IsReturnRequestAllowed) - { - - } -
- } -
- } - @Html.Widget("orderdetails_page_bottom", Model.Id) -
-
+@model OrderDetailsModel +@using Nop.Web.Models.Order +@{ + if (!Model.PrintMode) + { + Layout = "~/Views/Shared/_ColumnsOne.cshtml"; + } + else + { + Layout = "~/Views/Shared/_Print.cshtml"; + } + //title + Html.AddTitleParts(T("PageTitle.OrderDetails").Text); + //page class + Html.AppendPageCssClassParts("html-order-details-page"); +} +@if (Model.PrintMode) +{ + +} +
+ @if (!Model.PrintMode) + { +
+

@T("Order.OrderInformation")

+ @T("Order.Print") + @if (!Model.PdfInvoiceDisabled) + { + @T("Order.GetPDFInvoice") + } +
+ } +
+ @Html.Widget("orderdetails_page_top", Model.Id) +
+
+ @T("Order.Order#")@Model.Id +
+
    +
  • + @T("Order.OrderDate"): @Model.CreatedOn.ToString("D") +
  • +
  • + @T("Order.OrderStatus"): @Model.OrderStatus +
  • +
  • + @T("Order.OrderTotal"): @Model.OrderTotal +
  • +
+ @Html.Widget("orderdetails_page_overview", Model.Id) +
+
+
+
+ @T("Order.BillingAddress") +
+
+
    +
  • + @Model.BillingAddress.FirstName @Model.BillingAddress.LastName +
  • + + @if (Model.BillingAddress.PhoneEnabled) + { +
  • + @T("Order.Phone"): @Model.BillingAddress.PhoneNumber +
  • + } + @if (Model.BillingAddress.FaxEnabled) + { +
  • + @T("Order.Fax"): @Model.BillingAddress.FaxNumber +
  • + } + @if (Model.BillingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.BillingAddress.Company)) + { +
  • + @Model.BillingAddress.Company +
  • + } + @if (Model.BillingAddress.StreetAddressEnabled) + { +
  • + @Model.BillingAddress.Address1 +
  • + } + @if (Model.BillingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.BillingAddress.Address2)) + { +
  • + @Model.BillingAddress.Address2 +
  • + } + @if (Model.BillingAddress.CityEnabled || + Model.BillingAddress.StateProvinceEnabled || + Model.BillingAddress.ZipPostalCodeEnabled) + { +
  • + @if (Model.BillingAddress.CityEnabled) + { + @Model.BillingAddress.City + } + @if (Model.BillingAddress.CityEnabled && (Model.BillingAddress.StateProvinceEnabled || Model.BillingAddress.ZipPostalCodeEnabled)) + { + , + } + @if (Model.BillingAddress.StateProvinceEnabled) + { + @Model.BillingAddress.StateProvinceName + } + @if (Model.BillingAddress.ZipPostalCodeEnabled) + { + @Model.BillingAddress.ZipPostalCode + } +
  • + } + @if (Model.BillingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.BillingAddress.CountryName)) + { +
  • + @Model.BillingAddress.CountryName +
  • + } + @if (!String.IsNullOrEmpty(Model.VatNumber)) + { +
  • + + @T("Order.VATNumber") + + + @Model.VatNumber + +
  • + } + @if (!String.IsNullOrEmpty(Model.BillingAddress.FormattedCustomAddressAttributes)) + { +
  • + @Html.Raw(Model.BillingAddress.FormattedCustomAddressAttributes) +
  • + } + @if (Model.CustomValues != null) + { + foreach (var item in Model.CustomValues) + { +
  • + + @item.Key: + + + @(item.Value != null ? item.Value.ToString() : "") + +
  • + } + } +
+ @if (!String.IsNullOrEmpty(Model.PaymentMethod)) + { +
+
    +
  • + @T("Order.Payment") +
  • +
  • + + @T("Order.Payment.Method"): + + + @Model.PaymentMethod + +
  • + @if (!Model.PrintMode) + { +
  • + + @T("Order.Payment.Status"): + + + @Model.PaymentMethodStatus + +
  • + } + @if (!Model.PrintMode && Model.CanRePostProcessPayment) + { + @*Complete payment (for redirection payment methods)*@ +
  • + @using (Html.BeginRouteForm("OrderDetails", FormMethod.Post)) + { + @Html.AntiForgeryToken() + +

    + @T("Order.RetryPayment.Hint") +

    + } +
  • + } +
+
+ } +
+
+ @if (Model.IsShippable) + { +
+
+ @(Model.PickUpInStore ? T("Order.PickupAddress") : T("Order.ShippingAddress")) +
+
+
    + @if (!Model.PickUpInStore) + { +
  • + @Model.ShippingAddress.FirstName @Model.ShippingAddress.LastName +
  • + + if (Model.ShippingAddress.PhoneEnabled) + { +
  • + @T("Order.Phone"): @Model.ShippingAddress.PhoneNumber +
  • + } + if (Model.ShippingAddress.FaxEnabled) + { +
  • + @T("Order.Fax"): @Model.ShippingAddress.FaxNumber +
  • + } + if (Model.ShippingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.Company)) + { +
  • + @Model.ShippingAddress.Company +
  • + } + if (Model.ShippingAddress.StreetAddressEnabled) + { +
  • + @Model.ShippingAddress.Address1 +
  • + } + if (Model.ShippingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.ShippingAddress.Address2)) + { +
  • + @Model.ShippingAddress.Address2 +
  • + } + if (Model.ShippingAddress.CityEnabled || + Model.ShippingAddress.StateProvinceEnabled || + Model.ShippingAddress.ZipPostalCodeEnabled) + { +
  • + @if (Model.ShippingAddress.CityEnabled) + { + @Model.ShippingAddress.City + } + @if (Model.ShippingAddress.CityEnabled && (Model.ShippingAddress.StateProvinceEnabled || Model.ShippingAddress.ZipPostalCodeEnabled)) + { + , + } + @if (Model.ShippingAddress.StateProvinceEnabled) + { + @Model.ShippingAddress.StateProvinceName + } + @if (Model.ShippingAddress.ZipPostalCodeEnabled) + { + @Model.ShippingAddress.ZipPostalCode + } +
  • + } + if (Model.ShippingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.CountryName)) + { +
  • + @Model.ShippingAddress.CountryName +
  • + } + if (!String.IsNullOrEmpty(Model.ShippingAddress.FormattedCustomAddressAttributes)) + { +
  • + @Html.Raw(Model.ShippingAddress.FormattedCustomAddressAttributes) +
  • + } + } + else + { + if (!string.IsNullOrEmpty(Model.PickupAddress.Address1)) + { +
  • + @Model.PickupAddress.Address1 +
  • + } + if (!string.IsNullOrEmpty(Model.PickupAddress.City) || !string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) + { +
  • + @if (!string.IsNullOrEmpty(Model.PickupAddress.City)) + { + @Model.PickupAddress.City + } + @if (!string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) + { + , + @Model.PickupAddress.ZipPostalCode + } +
  • + } + if (!string.IsNullOrEmpty(Model.PickupAddress.CountryName)) + { +
  • + @Model.PickupAddress.CountryName +
  • + } + } +
+
+
    +
  • + @T("Order.Shipping") +
  • +
  • + + @T("Order.Shipping.Name"): + + + @Model.ShippingMethod + +
  • + @if (!Model.PrintMode) + { +
  • + + @T("Order.Shipping.Status"): + + + @Model.ShippingStatus + +
  • + } +
+
+
+
+ } +
+ @if (!Model.PrintMode && Model.Shipments.Count > 0) + { +
+
+ @T("Order.Shipments") +
+
+ + + + + + + + + + + + + + + + + + + @foreach (var item in Model.Shipments) + { + + + + + + + + } + +
+ @T("Order.Shipments.ID") + + @T("Order.Shipments.TrackingNumber") + + @T("Order.Shipments.ShippedDate") + + @T("Order.Shipments.DeliveryDate") + + @T("Order.Shipments.ViewDetails") +
+ + @item.Id.ToString() + + + @item.TrackingNumber + + + @if (item.ShippedDate.HasValue) + { + @item.ShippedDate.Value.ToString("D") + } + else + { + @T("Order.Shipments.ShippedDate.NotYet") + } + + + @if (item.DeliveryDate.HasValue) + { + @item.DeliveryDate.Value.ToString("D") + } + else + { + @T("Order.Shipments.DeliveryDate.NotYet") + } + + @T("Order.Shipments.ViewDetails") +
+
+
+ } + @if (Model.Items.Count > 0) + { + if (!Model.PrintMode && Model.OrderNotes.Count > 0) + { +
+
+ @T("Order.Notes") +
+
+ + + + + + + + + + + + + @foreach (var item in Model.OrderNotes) + { + + + + + } + +
+ @T("Order.Notes.CreatedOn") + + @T("Order.Notes.Note") +
+ @item.CreatedOn.ToString() + + @Html.Raw(item.Note) + @if (item.HasDownload) + { +

+ @T("Order.Notes.Download") +

+ } +
+
+
+ } + @Html.Widget("orderdetails_page_beforeproducts", Model.Id) +
+
+ @T("Order.Product(s)") +
+
+ + + @if (Model.ShowSku) + { + + } + + + + + + + + + @if (Model.ShowSku) + { + + } + + + + + + + + + @foreach (var item in Model.Items) + { + + @if (Model.ShowSku) + { + + } + + + + + + + } + +
+ @T("Order.Product(s).SKU") + + @T("Order.Product(s).Name") + + @T("Order.Product(s).Price") + + @T("Order.Product(s).Quantity") + + @T("Order.TaxRateLine.VatRate") + + @T("Order.Product(s).Total") +
+ + @item.Sku + + @if (!Model.PrintMode) + { + @item.ProductName + } + else + { + @item.ProductName + } + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
+ @Html.Raw(item.AttributeInfo) +
+ } + @if (!String.IsNullOrEmpty(item.RentalInfo)) + { +
+ @Html.Raw(item.RentalInfo) +
+ } + @if (item.DownloadId > 0) + { + + } + @if (item.LicenseId > 0) + { + + } + @Html.Widget("orderdetails_product_line", item.Id) +
+ + @item.UnitPrice + + + @item.Quantity + + + @item.VatRate + + + @item.SubTotal +
+
+ @if (Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) + { + var inclTax = Model.PricesIncludeTax; + //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here + //of course, you can modify appropriate locales to include VAT info there +
+ @T(inclTax ? "Order.TaxShipping.InclTax" : "Order.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) +
+ } +
+ @Html.Widget("orderdetails_page_afterproducts", Model.Id) +
+ @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) + { +
+ @Html.Raw(Model.CheckoutAttributeInfo) +
+ } +
+
+
+ + + + + + + @if (!string.IsNullOrEmpty(Model.OrderSubTotalDiscount)) + { + + + + + } + @if (Model.IsShippable) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFee)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.OrderAmount)) + { + + + + + } + @if (Model.DisplayTax) + { + + + + + } + @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) + { + + + + } + @if (Model.GiftCards.Count > 0) + { + foreach (var gc in Model.GiftCards) + { + + + + + } + } + @if (Model.RedeemedRewardPoints > 0) + { + + + + + } + + + + + +
+ + + @Model.OrderSubtotal +
+ + + @Model.OrderSubTotalDiscount +
+ + + @Model.OrderShipping +
+ + + @Model.PaymentMethodAdditionalFee +
+ + + @Model.OrderTotalDiscount +
+ + + @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount) +
+ + + @Model.Tax +
+
    +
  • + @T("Order.TaxRateLine.VatRate") + @if (Model.includingTax) + { + @T("Order.TaxRateLine.AmountIncl") + } + else + { + @T("Order.TaxRateLine.Amount") + } + @if (Model.includingTax) + { + @T("Order.TaxRateLine.DiscountAmountIncl") + } + else + { + @T("Order.TaxRateLine.DiscountAmount") + } + @T("Order.TaxRateLine.BaseAmount") + @T("Order.TaxRateLine.VatAmount") +
  • + @foreach (var taxRate in Model.TaxRates) + { +
  • + @taxRate.Rate + @taxRate.Amount + @taxRate.DiscountAmount + @taxRate.BaseAmount + @taxRate.VatAmount +
  • + } +
+
+ + + @gc.Amount +
+ + + @Model.RedeemedRewardPointsAmount +
+ + + @Model.OrderTotal +
+
+ @if (!Model.PrintMode) + { +
+ @if (Model.IsReOrderAllowed) + { + + } + @if (Model.IsReturnRequestAllowed) + { + + } +
+ } +
+ } + @Html.Widget("orderdetails_page_bottom", Model.Id) +
+
diff --git a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml index 35fe69a838d..fc801ffd88b 100644 --- a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -1,330 +1,331 @@ -@model ShoppingCartModel -@using Nop.Core -@using Nop.Core.Domain.Tax -@using Nop.Core.Infrastructure -@using Nop.Web.Models.ShoppingCart; -@{ - var webHelper = EngineContext.Current.Resolve(); -} -
- @Html.Widget("order_summary_content_before") - @Html.Partial("_OrderReviewData", Model.OrderReviewData) - @if (Model.Items.Count > 0) - { - if (Model.Warnings.Count > 0) - { -
-
    - @foreach (var warning in Model.Warnings) - { -
  • @warning
  • - } -
-
- } - @*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ - using (Html.BeginRouteForm("ShoppingCart", FormMethod.Post, new { enctype = "multipart/form-data", id = "shopping-cart-form" })) - { - @Html.AntiForgeryToken() -
- - - @if (Model.IsEditable) - { - - } - @if (Model.ShowSku) - { - - } - @if (Model.ShowProductImages) - { - - } - - - - - - - - - @if (Model.IsEditable) - { - - } - @if (Model.ShowSku) - { - - } - @if (Model.ShowProductImages) - { - - } - - - - - - - - - @foreach (var item in Model.Items) - { - - @if (Model.IsEditable) - { - - } - @if (Model.ShowSku) - { - - } - @if (Model.ShowProductImages) - { - - } - - - - - - - } - -
- @T("ShoppingCart.Remove") - - @T("ShoppingCart.SKU") - - @T("ShoppingCart.Image") - - @T("ShoppingCart.Product(s)") - - @T("ShoppingCart.UnitPrice") - - @T("ShoppingCart.Quantity") - - @T("ShoppingCart.VatRate") - - @T("ShoppingCart.ItemTotal") -
- @if (item.DisableRemoval) - { -   - } - else - { - - - } - - - @item.Sku - - @item.Picture.AlternateText - - @item.ProductName - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
- @Html.Raw(item.AttributeInfo) -
- } - @if (!String.IsNullOrEmpty(item.RecurringInfo)) - { -
- @Html.Raw(item.RecurringInfo) -
- } - @if (!String.IsNullOrEmpty(item.RentalInfo)) - { -
- @Html.Raw(item.RentalInfo) -
- } - @if (Model.IsEditable && item.AllowItemEditing) - { - var editCartItemUrl = Url.RouteUrl("Product", new {SeName = item.ProductSeName}); - editCartItemUrl = webHelper.ModifyQueryString(editCartItemUrl, "updatecartitemid=" + item.Id, null); - - } - @if (item.Warnings.Count > 0) - { -
-
    - @foreach (var warning in item.Warnings) - { -
  • @warning
  • - } -
-
- } -
- - @item.UnitPrice - - - @if (Model.IsEditable) - { - if (item.AllowedQuantities.Count > 0) - { - - } - else - { - - } - - } - else - { - @item.Quantity - } - - - @item.VatRate.ToString("G29") - - - @item.SubTotal - @if (!String.IsNullOrEmpty(item.Discount)) - { -
- @T("ShoppingCart.ItemYouSave", item.Discount) -
- if (item.MaximumDiscountedQty.HasValue) - { -
- @T("ShoppingCart.MaximumDiscountedQty", item.MaximumDiscountedQty.Value) -
- } - } -
-
- if (Model.IsEditable && Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) - { - var inclTax = EngineContext.Current.Resolve().TaxDisplayType == TaxDisplayType.IncludingTax; - //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here - //of course, you can modify appropriate locales to include VAT info there -
- @T(inclTax ? "ShoppingCart.TaxShipping.InclTax" : "ShoppingCart.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) -
- } -
- @if (Model.IsEditable) - { -
- - -
- } - @if (Model.IsEditable) - { - @Html.Partial("_CheckoutAttributes", Model.CheckoutAttributes, new ViewDataDictionary()) - } - @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) - { -
- @Html.Raw(Model.CheckoutAttributeInfo) -
- } -
- - if (Model.IsEditable) - { - @Html.Action("CrossSellProducts", "Product") - } - } - } - else - { -
- @T("ShoppingCart.CartIsEmpty") -
- } - @Html.Widget("order_summary_content_after") +@model ShoppingCartModel +@using Nop.Core +@using Nop.Core.Domain.Tax +@using Nop.Core.Infrastructure +@using Nop.Web.Models.ShoppingCart; +@{ + var webHelper = EngineContext.Current.Resolve(); +} +
+ @Html.Widget("order_summary_content_before") + @Html.Partial("_OrderReviewData", Model.OrderReviewData) + @if (Model.Items.Count > 0) + { + if (Model.Warnings.Count > 0) + { +
+
    + @foreach (var warning in Model.Warnings) + { +
  • @warning
  • + } +
+
+ } + @*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ + using (Html.BeginRouteForm("ShoppingCart", FormMethod.Post, new { enctype = "multipart/form-data", id = "shopping-cart-form" })) + { + @Html.AntiForgeryToken() +
+ + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + + + @foreach (var item in Model.Items) + { + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + } + +
+ @T("ShoppingCart.Remove") + + @T("ShoppingCart.SKU") + + @T("ShoppingCart.Image") + + @T("ShoppingCart.Product(s)") + + @T("ShoppingCart.UnitPrice") + + @T("ShoppingCart.Quantity") + + @T("ShoppingCart.VatRate") + + @T("ShoppingCart.ItemTotal") +
+ @if (item.DisableRemoval) + { +   + } + else + { + + + } + + + @item.Sku + + @item.Picture.AlternateText + + @item.ProductName + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
+ @Html.Raw(item.AttributeInfo) +
+ } + @if (!String.IsNullOrEmpty(item.RecurringInfo)) + { +
+ @Html.Raw(item.RecurringInfo) +
+ } + @if (!String.IsNullOrEmpty(item.RentalInfo)) + { +
+ @Html.Raw(item.RentalInfo) +
+ } + @if (Model.IsEditable && item.AllowItemEditing) + { + var editCartItemUrl = Url.RouteUrl("Product", new {SeName = item.ProductSeName}); + editCartItemUrl = webHelper.ModifyQueryString(editCartItemUrl, "updatecartitemid=" + item.Id, null); + + } + @if (item.Warnings.Count > 0) + { +
+
    + @foreach (var warning in item.Warnings) + { +
  • @warning
  • + } +
+
+ } +
+ + @item.UnitPrice + + + @if (Model.IsEditable) + { + if (item.AllowedQuantities.Count > 0) + { + + } + else + { + + } + + } + else + { + @item.Quantity + } + + + @*@item.VatRate.ToString("G29")*@ + @item.VatRate + + + @item.SubTotal + @if (!String.IsNullOrEmpty(item.Discount)) + { +
+ @T("ShoppingCart.ItemYouSave", item.Discount) +
+ if (item.MaximumDiscountedQty.HasValue) + { +
+ @T("ShoppingCart.MaximumDiscountedQty", item.MaximumDiscountedQty.Value) +
+ } + } +
+
+ if (Model.IsEditable && Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) + { + var inclTax = EngineContext.Current.Resolve().TaxDisplayType == TaxDisplayType.IncludingTax; + //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here + //of course, you can modify appropriate locales to include VAT info there +
+ @T(inclTax ? "ShoppingCart.TaxShipping.InclTax" : "ShoppingCart.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) +
+ } +
+ @if (Model.IsEditable) + { +
+ + +
+ } + @if (Model.IsEditable) + { + @Html.Partial("_CheckoutAttributes", Model.CheckoutAttributes, new ViewDataDictionary()) + } + @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) + { +
+ @Html.Raw(Model.CheckoutAttributeInfo) +
+ } +
+ + if (Model.IsEditable) + { + @Html.Action("CrossSellProducts", "Product") + } + } + } + else + { +
+ @T("ShoppingCart.CartIsEmpty") +
+ } + @Html.Widget("order_summary_content_after")
\ No newline at end of file diff --git a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 37024d579c2..e249b851a35 100644 --- a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -1,1410 +1,1410 @@ -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Data; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Stores; -using Nop.Core.Domain.Tax; -using Nop.Core.Plugins; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Directory; -using Nop.Services.Discounts; -using Nop.Services.Events; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Orders; -using Nop.Services.Payments; -using Nop.Services.Shipping; -using Nop.Services.Tax; -using Nop.Tests; -using NUnit.Framework; -using Rhino.Mocks; - -namespace Nop.Services.Tests.Orders -{ - [TestFixture] - public class OrderTotalCalculationServiceTests : ServiceTest - { - private IWorkContext _workContext; - private IStoreContext _storeContext; - private ITaxService _taxService; - private IShippingService _shippingService; - private IPaymentService _paymentService; - private ICheckoutAttributeParser _checkoutAttributeParser; - private IDiscountService _discountService; - private IGiftCardService _giftCardService; - private IGenericAttributeService _genericAttributeService; - private TaxSettings _taxSettings; - private RewardPointsSettings _rewardPointsSettings; - private ICategoryService _categoryService; - private IManufacturerService _manufacturerService; - private IProductAttributeParser _productAttributeParser; - private IPriceCalculationService _priceCalcService; - private IOrderTotalCalculationService _orderTotalCalcService; - private IAddressService _addressService; - private ShippingSettings _shippingSettings; - private ILocalizationService _localizationService; - private ILogger _logger; - private IRepository _shippingMethodRepository; - private IRepository _warehouseRepository; - private ShoppingCartSettings _shoppingCartSettings; - private CatalogSettings _catalogSettings; - private IEventPublisher _eventPublisher; - private Store _store; - private IProductService _productService; - private IGeoLookupService _geoLookupService; - private ICountryService _countryService; - private CustomerSettings _customerSettings; - private AddressSettings _addressSettings; - private IRewardPointService _rewardPointService; - - [SetUp] - public new void SetUp() - { - _workContext = MockRepository.GenerateMock(); - - _store = new Store { Id = 1 }; - _storeContext = MockRepository.GenerateMock(); - _storeContext.Expect(x => x.CurrentStore).Return(_store); - - _productService = MockRepository.GenerateMock(); - - var pluginFinder = new PluginFinder(); - var cacheManager = new NopNullCache(); - - _discountService = MockRepository.GenerateMock(); - _categoryService = MockRepository.GenerateMock(); - _manufacturerService = MockRepository.GenerateMock(); - _productAttributeParser = MockRepository.GenerateMock(); - - _shoppingCartSettings = new ShoppingCartSettings(); - _catalogSettings = new CatalogSettings(); - - _priceCalcService = new PriceCalculationService(_workContext, _storeContext, - _discountService, _categoryService, - _manufacturerService, _productAttributeParser, - _productService, cacheManager, - _shoppingCartSettings, _catalogSettings); - - _eventPublisher = MockRepository.GenerateMock(); - _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - - _localizationService = MockRepository.GenerateMock(); - - //shipping - _shippingSettings = new ShippingSettings(); - _shippingSettings.ActiveShippingRateComputationMethodSystemNames = new List(); - _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add("FixedRateTestShippingRateComputationMethod"); - _shippingMethodRepository = MockRepository.GenerateMock>(); - _warehouseRepository = MockRepository.GenerateMock>(); - _logger = new NullLogger(); - _shippingService = new ShippingService(_shippingMethodRepository, - _warehouseRepository, - _logger, - _productService, - _productAttributeParser, - _checkoutAttributeParser, - _genericAttributeService, - _localizationService, - _addressService, - _shippingSettings, - pluginFinder, - _storeContext, - _eventPublisher, - _shoppingCartSettings, - cacheManager); - - - _paymentService = MockRepository.GenerateMock(); - _checkoutAttributeParser = MockRepository.GenerateMock(); - _giftCardService = MockRepository.GenerateMock(); - _genericAttributeService = MockRepository.GenerateMock(); - - _eventPublisher = MockRepository.GenerateMock(); - _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - - _geoLookupService = MockRepository.GenerateMock(); - _countryService = MockRepository.GenerateMock(); - _customerSettings = new CustomerSettings(); - _addressSettings = new AddressSettings(); - - //tax - _taxSettings = new TaxSettings(); - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - _taxSettings.DefaultTaxAddressId = 10; - _addressService = MockRepository.GenerateMock(); - _addressService.Expect(x => x.GetAddressById(_taxSettings.DefaultTaxAddressId)).Return(new Address { Id = _taxSettings.DefaultTaxAddressId }); - _taxService = new TaxService(_addressService, _workContext, _taxSettings, - pluginFinder, _geoLookupService, _countryService, _logger, _customerSettings, _addressSettings); - _rewardPointService = MockRepository.GenerateMock(); - - _rewardPointsSettings = new RewardPointsSettings(); - - _orderTotalCalcService = new OrderTotalCalculationService(_workContext, _storeContext, - _priceCalcService, _taxService, _shippingService, _paymentService, - _checkoutAttributeParser, _discountService, _giftCardService, _genericAttributeService, - _rewardPointService, _taxSettings, _rewardPointsSettings, - _shippingSettings, _shoppingCartSettings, _catalogSettings); - } - - [Test] - public void Can_get_shopping_cart_subTotal_excluding_tax() - { - //customer - var customer = new Customer(); - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 12.34M, - CustomerEntersPrice = false, - Published = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 21.57M, - CustomerEntersPrice = false, - Published = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - decimal subTotalWithoutDiscount; - decimal subTotalWithDiscount; - - TaxSummary taxSummary; //MF 22.11.16 - //10% - default tax rate - _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, - out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); - var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 - discountAmount.ShouldEqual(0); - appliedDiscounts.Count.ShouldEqual(0); - subTotalWithoutDiscount.ShouldEqual(89.39); - subTotalWithDiscount.ShouldEqual(89.39); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(8.939); - } - - [Test] - public void Can_get_shopping_cart_subTotal_including_tax() - { - //customer - var customer = new Customer(); - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 12.34M, - CustomerEntersPrice = false, - Published = true, - }; - var sci1 = new ShoppingCartItem - { - Product= product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 21.57M, - CustomerEntersPrice = false, - Published = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - decimal subTotalWithoutDiscount; - decimal subTotalWithDiscount; - - TaxSummary taxSummary; //MF 22.11.16 - _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, - out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); - var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 - - discountAmount.ShouldEqual(0); - appliedDiscounts.Count.ShouldEqual(0); - subTotalWithoutDiscount.ShouldEqual(98.329); - subTotalWithDiscount.ShouldEqual(98.329); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(8.939); - } - - [Test] - public void Can_get_shopping_cart_subTotal_discount_excluding_tax() - { - //customer - var customer = new Customer(); - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 12.34M, - CustomerEntersPrice = false, - Published = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 21.57M, - CustomerEntersPrice = false, - Published = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - //discounts - var discount1 = new DiscountForCaching - { - Id = 1, - Name = "Discount 1", - DiscountType = DiscountType.AssignedToOrderSubTotal, - DiscountAmount = 3, - DiscountLimitation = DiscountLimitationType.Unlimited, - }; - _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal)).Return(new List { discount1 }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - decimal subTotalWithoutDiscount; - decimal subTotalWithDiscount; - TaxSummary taxSummary; //MF 22.11.16 - //10% - default tax rate - _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, - out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); - var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 - discountAmount.ShouldEqual(3); - appliedDiscounts.Count.ShouldEqual(1); - appliedDiscounts.First().Name.ShouldEqual("Discount 1"); - subTotalWithoutDiscount.ShouldEqual(89.39); - subTotalWithDiscount.ShouldEqual(86.39); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(8.639); - } - - [Test] - public void Can_get_shopping_cart_subTotal_discount_including_tax() - { - //customer - var customer = new Customer(); - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 12.34M, - CustomerEntersPrice = false, - Published = true, - }; - var sci1 = new ShoppingCartItem - { - Product= product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 21.57M, - CustomerEntersPrice = false, - Published = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - //discounts - var discount1 = new DiscountForCaching - { - Id = 1, - Name = "Discount 1", - DiscountType = DiscountType.AssignedToOrderSubTotal, - DiscountAmount = 3, - DiscountLimitation = DiscountLimitationType.Unlimited, - }; - _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal)).Return(new List { discount1 }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - decimal subTotalWithoutDiscount; - decimal subTotalWithDiscount; - TaxSummary taxSummary;//MF 22.11.16 - _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, - out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); - var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 - - //The comparison test failed before, because of a very tiny number difference. - //discountAmount.ShouldEqual(3.3); - (System.Math.Round(discountAmount, 10) == 3.3M).ShouldBeTrue(); - appliedDiscounts.Count.ShouldEqual(1); - appliedDiscounts.First().Name.ShouldEqual("Discount 1"); - subTotalWithoutDiscount.ShouldEqual(98.329); - subTotalWithDiscount.ShouldEqual(95.029); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(8.639); - } - - - - [Test] - public void Can_get_shoppingCartItem_additional_shippingCharge() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product= new Product - { - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - AdditionalShippingCharge = 5.5M, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 6.5M, - IsShipEnabled = true, - } - }; - - //sci3 is not shippable - var sci3 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 5, - Product = new Product - { - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 7.5M, - IsShipEnabled = false, - } - }; - - var cart = new List { sci1, sci2, sci3 }; - _orderTotalCalcService.GetShoppingCartAdditionalShippingCharge(cart).ShouldEqual(42.5M); - } - - [Test] - public void Shipping_should_be_free_when_all_shoppingCartItems_are_marked_as_freeShipping() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - IsFreeShipping = true, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - IsFreeShipping = true, - IsShipEnabled = true, - } - }; - var cart = new List { sci1, sci2 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(true); - } - - [Test] - public void Shipping_should_not_be_free_when_some_of_shoppingCartItems_are_not_marked_as_freeShipping() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - IsFreeShipping = true, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - IsFreeShipping = false, - IsShipEnabled = true, - } - }; - var cart = new List { sci1, sci2 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(false); - } - - [Test] - public void Shipping_should_be_free_when_customer_is_in_role_with_free_shipping() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - IsFreeShipping = false, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - IsFreeShipping = false, - IsShipEnabled = true, - } - }; - var cart = new List { sci1, sci2 }; - var customer = new Customer(); - var customerRole1 = new CustomerRole - { - Active = true, - FreeShipping = true, - }; - var customerRole2 = new CustomerRole - { - Active = true, - FreeShipping = false, - }; - customer.CustomerRoles.Add(customerRole1); - customer.CustomerRoles.Add(customerRole2); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(true); - } - - [Test] - public void Can_get_shipping_total_with_fixed_shipping_rate_excluding_tax() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Id = 1, - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - AdditionalShippingCharge = 5.5M, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Id = 2, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 6.5M, - IsShipEnabled = true, - } - }; - - //sci3 is not shippable - var sci3 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 5, - Product = new Product - { - Id = 3, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 7.5M, - IsShipEnabled = false, - } - }; - - var cart = new List { sci1, sci2, sci3 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - decimal taxRate; - List appliedDiscounts; - var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, false, out taxRate, out appliedDiscounts); - shipping.ShouldNotBeNull(); - //10 - default fixed shipping rate, 42.5 - additional shipping change - shipping.ShouldEqual(52.5); - appliedDiscounts.Count.ShouldEqual(0); - //10 - default fixed tax rate - taxRate.ShouldEqual(10); - } - - [Test] - public void Can_get_shipping_total_with_fixed_shipping_rate_including_tax() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Id = 1, - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - AdditionalShippingCharge = 5.5M, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Id = 2, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 6.5M, - IsShipEnabled = true, - } - }; - - //sci3 is not shippable - var sci3 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 5, - Product = new Product - { - Id = 3, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 7.5M, - IsShipEnabled = false, - } - }; - - var cart = new List { sci1, sci2, sci3 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - decimal taxRate; - List appliedDiscounts; - var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, true, out taxRate, out appliedDiscounts); - shipping.ShouldNotBeNull(); - //10 - default fixed shipping rate, 42.5 - additional shipping change - shipping.ShouldEqual(57.75); - appliedDiscounts.Count.ShouldEqual(0); - //10 - default fixed tax rate - taxRate.ShouldEqual(10); - } - - [Test] - public void Can_get_shipping_total_discount_excluding_tax() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Id = 1, - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - AdditionalShippingCharge = 5.5M, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Id = 2, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 6.5M, - IsShipEnabled = true, - } - }; - - //sci3 is not shippable - var sci3 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 5, - Product = new Product - { - Id = 3, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 7.5M, - IsShipEnabled = false, - } - }; - - var cart = new List { sci1, sci2, sci3 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - //discounts - var discount1 = new DiscountForCaching - { - Id = 1, - Name = "Discount 1", - DiscountType = DiscountType.AssignedToShipping, - DiscountAmount = 3, - DiscountLimitation = DiscountLimitationType.Unlimited, - }; - _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToShipping)).Return(new List { discount1 }); - - - decimal taxRate; - List appliedDiscounts; - var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, false, out taxRate, out appliedDiscounts); - appliedDiscounts.Count.ShouldEqual(1); - appliedDiscounts.First().Name.ShouldEqual("Discount 1"); - shipping.ShouldNotBeNull(); - //10 - default fixed shipping rate, 42.5 - additional shipping change, -3 - discount - shipping.ShouldEqual(49.5); - //10 - default fixed tax rate - taxRate.ShouldEqual(10); - } - - [Test] - public void Can_get_shipping_total_discount_including_tax() - { - var sci1 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 3, - Product = new Product - { - Id = 1, - Weight = 1.5M, - Height = 2.5M, - Length = 3.5M, - Width = 4.5M, - AdditionalShippingCharge = 5.5M, - IsShipEnabled = true, - } - }; - var sci2 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 4, - Product = new Product - { - Id = 2, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 6.5M, - IsShipEnabled = true, - } - }; - - //sci3 is not shippable - var sci3 = new ShoppingCartItem - { - AttributesXml = "", - Quantity = 5, - Product = new Product - { - Id = 3, - Weight = 11.5M, - Height = 12.5M, - Length = 13.5M, - Width = 14.5M, - AdditionalShippingCharge = 7.5M, - IsShipEnabled = false, - } - }; - - var cart = new List { sci1, sci2, sci3 }; - var customer = new Customer(); - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - //discounts - var discount1 = new DiscountForCaching - { - Id = 1, - Name = "Discount 1", - DiscountType = DiscountType.AssignedToShipping, - DiscountAmount = 3, - DiscountLimitation = DiscountLimitationType.Unlimited, - }; - _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToShipping)).Return(new List { discount1 }); - - - decimal taxRate; - List appliedDiscounts; - var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, true, out taxRate, out appliedDiscounts); - appliedDiscounts.Count.ShouldEqual(1); - appliedDiscounts.First().Name.ShouldEqual("Discount 1"); - shipping.ShouldNotBeNull(); - //10 - default fixed shipping rate, 42.5 - additional shipping change, -3 - discount - shipping.ShouldEqual(54.45); - //10 - default fixed tax rate - taxRate.ShouldEqual(10); - } - - [Test] - public void Can_get_tax_total() - { - //customer - var customer = new Customer - { - Id = 10, - }; - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 10M, - Published = true, - IsShipEnabled = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 12M, - Published = true, - IsShipEnabled = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - - - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List - { - new GenericAttribute - { - StoreId = _store.Id, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.SelectedPaymentMethod, - KeyGroup = "Customer", - Value = "test1" - } - }); - _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - //56 - items, 10 - shipping (fixed), 20 - payment fee - - //1. shipping is taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - TaxSummary taxSummary; //22.11.16 - _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(8.6); - var taxRates = taxSummary.GenerateOldTaxrateDict(); - taxRates.ShouldNotBeNull(); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(8.6); - - //2. shipping is taxable, payment fee is not taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; - _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(6.6); - taxRates = taxSummary.GenerateOldTaxrateDict(); - taxRates.ShouldNotBeNull(); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(6.6); - - //3. shipping is not taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = false; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(7.6); - taxRates = taxSummary.GenerateOldTaxrateDict(); - taxRates.ShouldNotBeNull(); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(7.6); - - //3. shipping is not taxable, payment fee is not taxable - _taxSettings.ShippingIsTaxable = false; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; - _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(5.6); - taxRates = taxSummary.GenerateOldTaxrateDict(); - taxRates.ShouldNotBeNull(); - taxRates.Count.ShouldEqual(1); - taxRates.ContainsKey(10).ShouldBeTrue(); - taxRates[10].ShouldEqual(5.6); - } - - [Test] - public void Can_get_shopping_cart_total_without_shipping_required() - { - //customer - var customer = new Customer - { - Id = 10, - }; - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 10M, - Published = true, - IsShipEnabled = false, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 12M, - Published = true, - IsShipEnabled = false, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - - - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List - { - new GenericAttribute - { - StoreId = _store.Id, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.SelectedPaymentMethod, - KeyGroup = "Customer", - Value = "test1" - } - }); - _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); - - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - List appliedGiftCards; - List subTotalAppliedDiscounts; - List shippingAppliedDiscounts; - TaxSummary taxSummary; - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - - - //shipping is taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - - //56 - items, 20 - payment fee, 7.6 - tax - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, - out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) - .ShouldEqual(83.6M); - } - - [Test] - public void Can_get_shopping_cart_total_with_shipping_required() - { - //customer - var customer = new Customer - { - Id = 10, - }; - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 10M, - Published = true, - IsShipEnabled = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 12M, - Published = true, - IsShipEnabled = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List - { - new GenericAttribute - { - StoreId = _store.Id, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.SelectedPaymentMethod, - KeyGroup = "Customer", - Value = "test1" - } - }); - _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); - - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - List appliedDiscounts; - List appliedGiftCards; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - List subTotalAppliedDiscounts; - List shippingAppliedDiscounts; - TaxSummary taxSummary; - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - - - //shipping is taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - - //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, - out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) - .ShouldEqual(94.6M); - } - - /*TODO temporary disabled - [Test] - public void Can_get_shopping_cart_total_with_applied_reward_points() - { - //customer - var customer = new Customer - { - Id = 10, - }; - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 10M, - Published = true, - IsShipEnabled = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 12M, - Published = true, - IsShipEnabled = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - - - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List - { - new GenericAttribute - { - StoreId = _store.Id, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.SelectedPaymentMethod, - KeyGroup = "Customer", - Value = "test1" - }, - new GenericAttribute - { - StoreId = 1, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, - KeyGroup = "Customer", - Value = true.ToString() - } - }); - _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); - - - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - decimal discountAmount; - Discount appliedDiscount; - List appliedGiftCards; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - - - //shipping is taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - - //reward points - _rewardPointsSettings.Enabled = true; - _rewardPointsSettings.ExchangeRate = 2; //1 reward point = 2 - - customer.AddRewardPointsHistoryEntry(15, 0); //15*2=30 - - //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax, -30 (reward points) - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscount, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) - .ShouldEqual(64.6M); - }*/ - - [Test] - public void Can_get_shopping_cart_total_discount() - { - //customer - var customer = new Customer - { - Id = 10, - }; - - //shopping cart - var product1 = new Product - { - Id = 1, - Name = "Product name 1", - Price = 10M, - Published = true, - IsShipEnabled = true, - }; - var sci1 = new ShoppingCartItem - { - Product = product1, - ProductId = product1.Id, - Quantity = 2, - }; - var product2 = new Product - { - Id = 2, - Name = "Product name 2", - Price = 12M, - Published = true, - IsShipEnabled = true, - }; - var sci2 = new ShoppingCartItem - { - Product = product2, - ProductId = product2.Id, - Quantity = 3 - }; - - var cart = new List { sci1, sci2 }; - cart.ForEach(sci => sci.Customer = customer); - cart.ForEach(sci => sci.CustomerId = customer.Id); - - //discounts - var discount1 = new DiscountForCaching - { - Id = 1, - Name = "Discount 1", - DiscountType = DiscountType.AssignedToOrderTotal, - DiscountAmount = 3, - DiscountLimitation = DiscountLimitationType.Unlimited, - }; - _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderTotal)).Return(new List { discount1 }); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); - _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); - - - _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) - .Return(new List - { - new GenericAttribute - { - StoreId = _store.Id, - EntityId = customer.Id, - Key = SystemCustomerAttributeNames.SelectedPaymentMethod, - KeyGroup = "Customer", - Value = "test1" - } - }); - _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); - - - decimal discountAmount; - List appliedDiscounts; - List appliedGiftCards; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - List subTotalAppliedDiscounts; - List shippingAppliedDiscounts; - TaxSummary taxSummary; - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - - //shipping is taxable, payment fee is taxable - _taxSettings.ShippingIsTaxable = true; - _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - - //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax, [-3] - discount - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, - out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) - .ShouldEqual(91.6M); - discountAmount.ShouldEqual(3); - appliedDiscounts.Count.ShouldEqual(1); - appliedDiscounts.First().Name.ShouldEqual("Discount 1"); - } - - [Test] - public void Can_convert_reward_points_to_amount() - { - _rewardPointsSettings.Enabled = true; - _rewardPointsSettings.ExchangeRate = 15M; - - _orderTotalCalcService.ConvertRewardPointsToAmount(100).ShouldEqual(1500); - } - - [Test] - public void Can_convert_amount_to_reward_points() - { - _rewardPointsSettings.Enabled = true; - _rewardPointsSettings.ExchangeRate = 15M; - - //we calculate ceiling for reward points - _orderTotalCalcService.ConvertAmountToRewardPoints(100).ShouldEqual(7); - } - - [Test] - public void Can_check_minimum_reward_points_to_use_requirement() - { - _rewardPointsSettings.Enabled = true; - _rewardPointsSettings.MinimumRewardPointsToUse = 0; - - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(0).ShouldEqual(true); - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(1).ShouldEqual(true); - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(10).ShouldEqual(true); - - - _rewardPointsSettings.MinimumRewardPointsToUse = 2; - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(0).ShouldEqual(false); - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(1).ShouldEqual(false); - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(2).ShouldEqual(true); - _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(10).ShouldEqual(true); - } - } -} +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Data; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Stores; +using Nop.Core.Domain.Tax; +using Nop.Core.Plugins; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.Events; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Shipping; +using Nop.Services.Tax; +using Nop.Tests; +using NUnit.Framework; +using Rhino.Mocks; + +namespace Nop.Services.Tests.Orders +{ + [TestFixture] + public class OrderTotalCalculationServiceTests : ServiceTest + { + private IWorkContext _workContext; + private IStoreContext _storeContext; + private ITaxService _taxService; + private IShippingService _shippingService; + private IPaymentService _paymentService; + private ICheckoutAttributeParser _checkoutAttributeParser; + private IDiscountService _discountService; + private IGiftCardService _giftCardService; + private IGenericAttributeService _genericAttributeService; + private TaxSettings _taxSettings; + private RewardPointsSettings _rewardPointsSettings; + private ICategoryService _categoryService; + private IManufacturerService _manufacturerService; + private IProductAttributeParser _productAttributeParser; + private IPriceCalculationService _priceCalcService; + private IOrderTotalCalculationService _orderTotalCalcService; + private IAddressService _addressService; + private ShippingSettings _shippingSettings; + private ILocalizationService _localizationService; + private ILogger _logger; + private IRepository _shippingMethodRepository; + private IRepository _warehouseRepository; + private ShoppingCartSettings _shoppingCartSettings; + private CatalogSettings _catalogSettings; + private IEventPublisher _eventPublisher; + private Store _store; + private IProductService _productService; + private IGeoLookupService _geoLookupService; + private ICountryService _countryService; + private CustomerSettings _customerSettings; + private AddressSettings _addressSettings; + private IRewardPointService _rewardPointService; + + [SetUp] + public new void SetUp() + { + _workContext = MockRepository.GenerateMock(); + + _store = new Store { Id = 1 }; + _storeContext = MockRepository.GenerateMock(); + _storeContext.Expect(x => x.CurrentStore).Return(_store); + + _productService = MockRepository.GenerateMock(); + + var pluginFinder = new PluginFinder(); + var cacheManager = new NopNullCache(); + + _discountService = MockRepository.GenerateMock(); + _categoryService = MockRepository.GenerateMock(); + _manufacturerService = MockRepository.GenerateMock(); + _productAttributeParser = MockRepository.GenerateMock(); + + _shoppingCartSettings = new ShoppingCartSettings(); + _catalogSettings = new CatalogSettings(); + + _priceCalcService = new PriceCalculationService(_workContext, _storeContext, + _discountService, _categoryService, + _manufacturerService, _productAttributeParser, + _productService, cacheManager, + _shoppingCartSettings, _catalogSettings, _taxService); + + _eventPublisher = MockRepository.GenerateMock(); + _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); + + _localizationService = MockRepository.GenerateMock(); + + //shipping + _shippingSettings = new ShippingSettings(); + _shippingSettings.ActiveShippingRateComputationMethodSystemNames = new List(); + _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add("FixedRateTestShippingRateComputationMethod"); + _shippingMethodRepository = MockRepository.GenerateMock>(); + _warehouseRepository = MockRepository.GenerateMock>(); + _logger = new NullLogger(); + _shippingService = new ShippingService(_shippingMethodRepository, + _warehouseRepository, + _logger, + _productService, + _productAttributeParser, + _checkoutAttributeParser, + _genericAttributeService, + _localizationService, + _addressService, + _shippingSettings, + pluginFinder, + _storeContext, + _eventPublisher, + _shoppingCartSettings, + cacheManager); + + + _paymentService = MockRepository.GenerateMock(); + _checkoutAttributeParser = MockRepository.GenerateMock(); + _giftCardService = MockRepository.GenerateMock(); + _genericAttributeService = MockRepository.GenerateMock(); + + _eventPublisher = MockRepository.GenerateMock(); + _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); + + _geoLookupService = MockRepository.GenerateMock(); + _countryService = MockRepository.GenerateMock(); + _customerSettings = new CustomerSettings(); + _addressSettings = new AddressSettings(); + + //tax + _taxSettings = new TaxSettings(); + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + _taxSettings.DefaultTaxAddressId = 10; + _addressService = MockRepository.GenerateMock(); + _addressService.Expect(x => x.GetAddressById(_taxSettings.DefaultTaxAddressId)).Return(new Address { Id = _taxSettings.DefaultTaxAddressId }); + _taxService = new TaxService(_addressService, _workContext, _taxSettings, + pluginFinder, _geoLookupService, _countryService, _logger, _customerSettings, _addressSettings); + _rewardPointService = MockRepository.GenerateMock(); + + _rewardPointsSettings = new RewardPointsSettings(); + + _orderTotalCalcService = new OrderTotalCalculationService(_workContext, _storeContext, + _priceCalcService, _taxService, _shippingService, _paymentService, + _checkoutAttributeParser, _discountService, _giftCardService, _genericAttributeService, + _rewardPointService, _taxSettings, _rewardPointsSettings, + _shippingSettings, _shoppingCartSettings, _catalogSettings); + } + + [Test] + public void Can_get_shopping_cart_subTotal_excluding_tax() + { + //customer + var customer = new Customer(); + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 12.34M, + CustomerEntersPrice = false, + Published = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 21.57M, + CustomerEntersPrice = false, + Published = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + + TaxSummary taxSummary; //MF 22.11.16 + //10% - default tax rate + _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, + out discountAmount, out appliedDiscounts, + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 + discountAmount.ShouldEqual(0); + appliedDiscounts.Count.ShouldEqual(0); + subTotalWithoutDiscount.ShouldEqual(89.39); + subTotalWithDiscount.ShouldEqual(89.39); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(8.939); + } + + [Test] + public void Can_get_shopping_cart_subTotal_including_tax() + { + //customer + var customer = new Customer(); + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 12.34M, + CustomerEntersPrice = false, + Published = true, + }; + var sci1 = new ShoppingCartItem + { + Product= product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 21.57M, + CustomerEntersPrice = false, + Published = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + + TaxSummary taxSummary; //MF 22.11.16 + _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, + out discountAmount, out appliedDiscounts, + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 + + discountAmount.ShouldEqual(0); + appliedDiscounts.Count.ShouldEqual(0); + subTotalWithoutDiscount.ShouldEqual(98.329); + subTotalWithDiscount.ShouldEqual(98.329); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(8.939); + } + + [Test] + public void Can_get_shopping_cart_subTotal_discount_excluding_tax() + { + //customer + var customer = new Customer(); + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 12.34M, + CustomerEntersPrice = false, + Published = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 21.57M, + CustomerEntersPrice = false, + Published = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + //discounts + var discount1 = new DiscountForCaching + { + Id = 1, + Name = "Discount 1", + DiscountType = DiscountType.AssignedToOrderSubTotal, + DiscountAmount = 3, + DiscountLimitation = DiscountLimitationType.Unlimited, + }; + _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal)).Return(new List { discount1 }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + TaxSummary taxSummary; //MF 22.11.16 + //10% - default tax rate + _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, + out discountAmount, out appliedDiscounts, + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 + discountAmount.ShouldEqual(3); + appliedDiscounts.Count.ShouldEqual(1); + appliedDiscounts.First().Name.ShouldEqual("Discount 1"); + subTotalWithoutDiscount.ShouldEqual(89.39); + subTotalWithDiscount.ShouldEqual(86.39); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(8.639); + } + + [Test] + public void Can_get_shopping_cart_subTotal_discount_including_tax() + { + //customer + var customer = new Customer(); + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 12.34M, + CustomerEntersPrice = false, + Published = true, + }; + var sci1 = new ShoppingCartItem + { + Product= product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 21.57M, + CustomerEntersPrice = false, + Published = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + //discounts + var discount1 = new DiscountForCaching + { + Id = 1, + Name = "Discount 1", + DiscountType = DiscountType.AssignedToOrderSubTotal, + DiscountAmount = 3, + DiscountLimitation = DiscountLimitationType.Unlimited, + }; + _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal)).Return(new List { discount1 }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + TaxSummary taxSummary;//MF 22.11.16 + _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, + out discountAmount, out appliedDiscounts, + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 + + //The comparison test failed before, because of a very tiny number difference. + //discountAmount.ShouldEqual(3.3); + (System.Math.Round(discountAmount, 10) == 3.3M).ShouldBeTrue(); + appliedDiscounts.Count.ShouldEqual(1); + appliedDiscounts.First().Name.ShouldEqual("Discount 1"); + subTotalWithoutDiscount.ShouldEqual(98.329); + subTotalWithDiscount.ShouldEqual(95.029); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(8.639); + } + + + + [Test] + public void Can_get_shoppingCartItem_additional_shippingCharge() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product= new Product + { + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + AdditionalShippingCharge = 5.5M, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 6.5M, + IsShipEnabled = true, + } + }; + + //sci3 is not shippable + var sci3 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 5, + Product = new Product + { + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 7.5M, + IsShipEnabled = false, + } + }; + + var cart = new List { sci1, sci2, sci3 }; + _orderTotalCalcService.GetShoppingCartAdditionalShippingCharge(cart).ShouldEqual(42.5M); + } + + [Test] + public void Shipping_should_be_free_when_all_shoppingCartItems_are_marked_as_freeShipping() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + IsFreeShipping = true, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + IsFreeShipping = true, + IsShipEnabled = true, + } + }; + var cart = new List { sci1, sci2 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(true); + } + + [Test] + public void Shipping_should_not_be_free_when_some_of_shoppingCartItems_are_not_marked_as_freeShipping() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + IsFreeShipping = true, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + IsFreeShipping = false, + IsShipEnabled = true, + } + }; + var cart = new List { sci1, sci2 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(false); + } + + [Test] + public void Shipping_should_be_free_when_customer_is_in_role_with_free_shipping() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + IsFreeShipping = false, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + IsFreeShipping = false, + IsShipEnabled = true, + } + }; + var cart = new List { sci1, sci2 }; + var customer = new Customer(); + var customerRole1 = new CustomerRole + { + Active = true, + FreeShipping = true, + }; + var customerRole2 = new CustomerRole + { + Active = true, + FreeShipping = false, + }; + customer.CustomerRoles.Add(customerRole1); + customer.CustomerRoles.Add(customerRole2); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _orderTotalCalcService.IsFreeShipping(cart).ShouldEqual(true); + } + + [Test] + public void Can_get_shipping_total_with_fixed_shipping_rate_excluding_tax() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Id = 1, + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + AdditionalShippingCharge = 5.5M, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Id = 2, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 6.5M, + IsShipEnabled = true, + } + }; + + //sci3 is not shippable + var sci3 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 5, + Product = new Product + { + Id = 3, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 7.5M, + IsShipEnabled = false, + } + }; + + var cart = new List { sci1, sci2, sci3 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + decimal taxRate; + List appliedDiscounts; + var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, false, out taxRate, out appliedDiscounts); + shipping.ShouldNotBeNull(); + //10 - default fixed shipping rate, 42.5 - additional shipping change + shipping.ShouldEqual(52.5); + appliedDiscounts.Count.ShouldEqual(0); + //10 - default fixed tax rate + taxRate.ShouldEqual(10); + } + + [Test] + public void Can_get_shipping_total_with_fixed_shipping_rate_including_tax() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Id = 1, + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + AdditionalShippingCharge = 5.5M, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Id = 2, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 6.5M, + IsShipEnabled = true, + } + }; + + //sci3 is not shippable + var sci3 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 5, + Product = new Product + { + Id = 3, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 7.5M, + IsShipEnabled = false, + } + }; + + var cart = new List { sci1, sci2, sci3 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + decimal taxRate; + List appliedDiscounts; + var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, true, out taxRate, out appliedDiscounts); + shipping.ShouldNotBeNull(); + //10 - default fixed shipping rate, 42.5 - additional shipping change + shipping.ShouldEqual(57.75); + appliedDiscounts.Count.ShouldEqual(0); + //10 - default fixed tax rate + taxRate.ShouldEqual(10); + } + + [Test] + public void Can_get_shipping_total_discount_excluding_tax() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Id = 1, + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + AdditionalShippingCharge = 5.5M, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Id = 2, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 6.5M, + IsShipEnabled = true, + } + }; + + //sci3 is not shippable + var sci3 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 5, + Product = new Product + { + Id = 3, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 7.5M, + IsShipEnabled = false, + } + }; + + var cart = new List { sci1, sci2, sci3 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + //discounts + var discount1 = new DiscountForCaching + { + Id = 1, + Name = "Discount 1", + DiscountType = DiscountType.AssignedToShipping, + DiscountAmount = 3, + DiscountLimitation = DiscountLimitationType.Unlimited, + }; + _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToShipping)).Return(new List { discount1 }); + + + decimal taxRate; + List appliedDiscounts; + var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, false, out taxRate, out appliedDiscounts); + appliedDiscounts.Count.ShouldEqual(1); + appliedDiscounts.First().Name.ShouldEqual("Discount 1"); + shipping.ShouldNotBeNull(); + //10 - default fixed shipping rate, 42.5 - additional shipping change, -3 - discount + shipping.ShouldEqual(49.5); + //10 - default fixed tax rate + taxRate.ShouldEqual(10); + } + + [Test] + public void Can_get_shipping_total_discount_including_tax() + { + var sci1 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 3, + Product = new Product + { + Id = 1, + Weight = 1.5M, + Height = 2.5M, + Length = 3.5M, + Width = 4.5M, + AdditionalShippingCharge = 5.5M, + IsShipEnabled = true, + } + }; + var sci2 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 4, + Product = new Product + { + Id = 2, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 6.5M, + IsShipEnabled = true, + } + }; + + //sci3 is not shippable + var sci3 = new ShoppingCartItem + { + AttributesXml = "", + Quantity = 5, + Product = new Product + { + Id = 3, + Weight = 11.5M, + Height = 12.5M, + Length = 13.5M, + Width = 14.5M, + AdditionalShippingCharge = 7.5M, + IsShipEnabled = false, + } + }; + + var cart = new List { sci1, sci2, sci3 }; + var customer = new Customer(); + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + //discounts + var discount1 = new DiscountForCaching + { + Id = 1, + Name = "Discount 1", + DiscountType = DiscountType.AssignedToShipping, + DiscountAmount = 3, + DiscountLimitation = DiscountLimitationType.Unlimited, + }; + _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToShipping)).Return(new List { discount1 }); + + + decimal taxRate; + List appliedDiscounts; + var shipping = _orderTotalCalcService.GetShoppingCartShippingTotal(cart, true, out taxRate, out appliedDiscounts); + appliedDiscounts.Count.ShouldEqual(1); + appliedDiscounts.First().Name.ShouldEqual("Discount 1"); + shipping.ShouldNotBeNull(); + //10 - default fixed shipping rate, 42.5 - additional shipping change, -3 - discount + shipping.ShouldEqual(54.45); + //10 - default fixed tax rate + taxRate.ShouldEqual(10); + } + + [Test] + public void Can_get_tax_total() + { + //customer + var customer = new Customer + { + Id = 10, + }; + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 10M, + Published = true, + IsShipEnabled = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 12M, + Published = true, + IsShipEnabled = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + + + _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) + .Return(new List + { + new GenericAttribute + { + StoreId = _store.Id, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.SelectedPaymentMethod, + KeyGroup = "Customer", + Value = "test1" + } + }); + _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + //56 - items, 10 - shipping (fixed), 20 - payment fee + + //1. shipping is taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + TaxSummary taxSummary; //22.11.16 + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(8.6); + var taxRates = taxSummary.GenerateOldTaxrateDict(); + taxRates.ShouldNotBeNull(); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(8.6); + + //2. shipping is taxable, payment fee is not taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(6.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); + taxRates.ShouldNotBeNull(); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(6.6); + + //3. shipping is not taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = false; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(7.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); + taxRates.ShouldNotBeNull(); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(7.6); + + //3. shipping is not taxable, payment fee is not taxable + _taxSettings.ShippingIsTaxable = false; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(5.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); + taxRates.ShouldNotBeNull(); + taxRates.Count.ShouldEqual(1); + taxRates.ContainsKey(10).ShouldBeTrue(); + taxRates[10].ShouldEqual(5.6); + } + + [Test] + public void Can_get_shopping_cart_total_without_shipping_required() + { + //customer + var customer = new Customer + { + Id = 10, + }; + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 10M, + Published = true, + IsShipEnabled = false, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 12M, + Published = true, + IsShipEnabled = false, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + + + _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) + .Return(new List + { + new GenericAttribute + { + StoreId = _store.Id, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.SelectedPaymentMethod, + KeyGroup = "Customer", + Value = "test1" + } + }); + _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); + + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + List appliedGiftCards; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + + + //shipping is taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + + //56 - items, 20 - payment fee, 7.6 - tax + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) + .ShouldEqual(83.6M); + } + + [Test] + public void Can_get_shopping_cart_total_with_shipping_required() + { + //customer + var customer = new Customer + { + Id = 10, + }; + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 10M, + Published = true, + IsShipEnabled = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 12M, + Published = true, + IsShipEnabled = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) + .Return(new List + { + new GenericAttribute + { + StoreId = _store.Id, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.SelectedPaymentMethod, + KeyGroup = "Customer", + Value = "test1" + } + }); + _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); + + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + List appliedDiscounts; + List appliedGiftCards; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + + + //shipping is taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + + //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) + .ShouldEqual(94.6M); + } + + /*TODO temporary disabled + [Test] + public void Can_get_shopping_cart_total_with_applied_reward_points() + { + //customer + var customer = new Customer + { + Id = 10, + }; + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 10M, + Published = true, + IsShipEnabled = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 12M, + Published = true, + IsShipEnabled = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + + + _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) + .Return(new List + { + new GenericAttribute + { + StoreId = _store.Id, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.SelectedPaymentMethod, + KeyGroup = "Customer", + Value = "test1" + }, + new GenericAttribute + { + StoreId = 1, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, + KeyGroup = "Customer", + Value = true.ToString() + } + }); + _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); + + + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + decimal discountAmount; + Discount appliedDiscount; + List appliedGiftCards; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + + + //shipping is taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + + //reward points + _rewardPointsSettings.Enabled = true; + _rewardPointsSettings.ExchangeRate = 2; //1 reward point = 2 + + customer.AddRewardPointsHistoryEntry(15, 0); //15*2=30 + + //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax, -30 (reward points) + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscount, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) + .ShouldEqual(64.6M); + }*/ + + [Test] + public void Can_get_shopping_cart_total_discount() + { + //customer + var customer = new Customer + { + Id = 10, + }; + + //shopping cart + var product1 = new Product + { + Id = 1, + Name = "Product name 1", + Price = 10M, + Published = true, + IsShipEnabled = true, + }; + var sci1 = new ShoppingCartItem + { + Product = product1, + ProductId = product1.Id, + Quantity = 2, + }; + var product2 = new Product + { + Id = 2, + Name = "Product name 2", + Price = 12M, + Published = true, + IsShipEnabled = true, + }; + var sci2 = new ShoppingCartItem + { + Product = product2, + ProductId = product2.Id, + Quantity = 3 + }; + + var cart = new List { sci1, sci2 }; + cart.ForEach(sci => sci.Customer = customer); + cart.ForEach(sci => sci.CustomerId = customer.Id); + + //discounts + var discount1 = new DiscountForCaching + { + Id = 1, + Name = "Discount 1", + DiscountType = DiscountType.AssignedToOrderTotal, + DiscountAmount = 3, + DiscountLimitation = DiscountLimitationType.Unlimited, + }; + _discountService.Expect(ds => ds.ValidateDiscount(discount1, customer)).Return(new DiscountValidationResult() { IsValid = true }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToOrderTotal)).Return(new List { discount1 }); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)).Return(new List()); + _discountService.Expect(ds => ds.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)).Return(new List()); + + + _genericAttributeService.Expect(x => x.GetAttributesForEntity(customer.Id, "Customer")) + .Return(new List + { + new GenericAttribute + { + StoreId = _store.Id, + EntityId = customer.Id, + Key = SystemCustomerAttributeNames.SelectedPaymentMethod, + KeyGroup = "Customer", + Value = "test1" + } + }); + _paymentService.Expect(ps => ps.GetAdditionalHandlingFee(cart, "test1")).Return(20); + + + decimal discountAmount; + List appliedDiscounts; + List appliedGiftCards; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + + //shipping is taxable, payment fee is taxable + _taxSettings.ShippingIsTaxable = true; + _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; + + //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax, [-3] - discount + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) + .ShouldEqual(91.6M); + discountAmount.ShouldEqual(3); + appliedDiscounts.Count.ShouldEqual(1); + appliedDiscounts.First().Name.ShouldEqual("Discount 1"); + } + + [Test] + public void Can_convert_reward_points_to_amount() + { + _rewardPointsSettings.Enabled = true; + _rewardPointsSettings.ExchangeRate = 15M; + + _orderTotalCalcService.ConvertRewardPointsToAmount(100).ShouldEqual(1500); + } + + [Test] + public void Can_convert_amount_to_reward_points() + { + _rewardPointsSettings.Enabled = true; + _rewardPointsSettings.ExchangeRate = 15M; + + //we calculate ceiling for reward points + _orderTotalCalcService.ConvertAmountToRewardPoints(100).ShouldEqual(7); + } + + [Test] + public void Can_check_minimum_reward_points_to_use_requirement() + { + _rewardPointsSettings.Enabled = true; + _rewardPointsSettings.MinimumRewardPointsToUse = 0; + + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(0).ShouldEqual(true); + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(1).ShouldEqual(true); + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(10).ShouldEqual(true); + + + _rewardPointsSettings.MinimumRewardPointsToUse = 2; + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(0).ShouldEqual(false); + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(1).ShouldEqual(false); + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(2).ShouldEqual(true); + _orderTotalCalcService.CheckMinimumRewardPointsToUseRequirement(10).ShouldEqual(true); + } + } +}