diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Examples/Notifications/OrderAfterGenerateXmlSubscriber.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Examples/Notifications/OrderAfterGenerateXmlSubscriber.cs index 19af2f5..45d53a5 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Examples/Notifications/OrderAfterGenerateXmlSubscriber.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Examples/Notifications/OrderAfterGenerateXmlSubscriber.cs @@ -25,7 +25,7 @@ public override void OnNotify(string notification, NotificationArgs args) if (myArgs?.Document != null) { var settings = SettingsManager.GetSettingsByShop(myArgs.Order.ShopId); - if (settings != null && !settings.ErpControlsShipping) + if (settings != null && settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping) { var order = myArgs.Order; var shipping = Services.Shippings.GetShipping(order.ShippingMethodId); diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs index b38da14..d34d157 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs @@ -77,5 +77,24 @@ internal static class OrderConfiguration public const string DefaultShippingItemType = "ItemCharge"; } + + /// + /// Provides string constants that define the available modes for controlling shipping calculation and data + /// exchange between Dynamicweb and an ERP system. + /// + /// Use these constants to specify how shipping fees and information are managed in + /// integrations between Dynamicweb and ERP systems. Each mode determines whether shipping is calculated by + /// Dynamicweb, by the ERP, or based on selections made in Dynamicweb and processed by the ERP. + public static class ShippingControlMode + { + /// Dynamicweb calculates and sends the shipping fee to the ERP. + public const string DynamicwebControlsShipping = "DynamicwebControlsShipping"; + + /// ERP controls shipping entirely; no shipping data is sent from Dynamicweb. + public const string ErpControlsShipping = "ErpControlsShipping"; + + /// The customer selects a shipping method in Dynamicweb; the ERP calculates the freight cost based on the selected method's identity fields. + public const string ErpCalculatesBasedOnDwSelection = "ErpCalculatesBasedOnDwSelection"; + } } } \ No newline at end of file diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs index f89c013..f6003e2 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs @@ -266,9 +266,16 @@ public interface ISettings string ErpShippingItemKey { get; set; } /// - /// Gets or sets if ERP controls shipping calculations + /// Gets or sets the shipping control mode. /// - /// true if [ERP controls shipping calculations]; otherwise, false. + string ShippingControlMode { get; set; } + + /// + /// Gets or sets whether the ERP controls shipping. Use instead. + /// Setting false switches to ; + /// setting true restores . + /// + [System.Obsolete("Use ShippingControlMode instead.")] bool ErpControlsShipping { get; set; } /// diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs index 46743a7..b540e36 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs @@ -40,7 +40,7 @@ public Settings() WebServiceConnectionStatusGlobalTagName = "Global:LiveIntegration.IsWebServiceConnectionAvailable"; ErpControlsDiscount = true; - ErpControlsShipping = true; + ShippingControlMode = Constants.ShippingControlMode.ErpControlsShipping; SetOrderlineFixed = false; @@ -263,10 +263,33 @@ public string InstanceName public bool ErpControlsDiscount { get; set; } /// - /// Gets or sets if ERP controls shipping + /// Gets or sets the shipping control mode. /// - /// true if [ERP controls shipping]; otherwise, false. - public bool ErpControlsShipping { get; set; } + public string ShippingControlMode + { + get => _shippingControlMode; + set => _shippingControlMode = NormalizeShippingControlMode(value); + } + private string _shippingControlMode; + + private static string NormalizeShippingControlMode(string value) + { + if (string.Equals(value, Constants.ShippingControlMode.DynamicwebControlsShipping, StringComparison.OrdinalIgnoreCase)) + return Constants.ShippingControlMode.DynamicwebControlsShipping; + if (string.Equals(value, Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection, StringComparison.OrdinalIgnoreCase)) + return Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection; + return Constants.ShippingControlMode.ErpControlsShipping; + } + + /// + [Obsolete("Use ShippingControlMode instead.")] + public bool ErpControlsShipping + { + get => ShippingControlMode != Constants.ShippingControlMode.DynamicwebControlsShipping; + set => ShippingControlMode = value + ? Constants.ShippingControlMode.ErpControlsShipping + : Constants.ShippingControlMode.DynamicwebControlsShipping; + } /// /// Gets or sets the key for shipping item type. @@ -470,7 +493,7 @@ public static void UpdateFrom(ISettings source, ISettings target) target.SkipLedgerOrder = source.SkipLedgerOrder; target.ErpControlsDiscount = source.ErpControlsDiscount; target.DisableErpDiscountsForAnonymousUsers = source.DisableErpDiscountsForAnonymousUsers; - target.ErpControlsShipping = source.ErpControlsShipping; + target.ShippingControlMode = source.ShippingControlMode; target.ErpShippingItemType = source.ErpShippingItemType; target.ErpShippingItemKey = source.ErpShippingItemKey; diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj index c96f75e..cab6f7c 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj @@ -1,6 +1,6 @@  - 10.21.5 + 10.21.6 1.0.0.0 Live Integration Live Integration @@ -19,7 +19,7 @@ true true true - true + true snupkg diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.sln b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.sln new file mode 100644 index 0000000..cdb2341 --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynamicweb.Ecommerce.DynamicwebLiveIntegration", "Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj", "{5B09B2C6-8E15-A19A-AA43-E303D71A0874}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B09B2C6-8E15-A19A-AA43-E303D71A0874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B09B2C6-8E15-A19A-AA43-E303D71A0874}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B09B2C6-8E15-A19A-AA43-E303D71A0874}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B09B2C6-8E15-A19A-AA43-E303D71A0874}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8116CA11-5865-48E8-9C90-5CBF48FF6A82} + EndGlobalSection +EndGlobal diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs index d9d1e04..1a3bfbb 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs @@ -381,14 +381,23 @@ public LiveIntegrationAddIn() public bool ErpControlsDiscount { get; set; } /// - /// Gets or sets if ERP controls shipping calculations + /// Gets or sets the shipping control mode. /// - /// true if [ERP controls shipping calculations]; otherwise, false. - [AddInParameter("ERP controls shipping calculations")] - [AddInParameterEditor(typeof(YesNoParameterEditor), "")] + [AddInParameter("Shipping control")] + [AddInParameterEditor(typeof(DropDownParameterEditor), "none=false")] [AddInParameterGroup("Orders")] [AddInParameterOrder(157)] - public bool ErpControlsShipping { get; set; } + public string ShippingControlMode { get; set; } + + /// + [Obsolete("Use ShippingControlMode instead.")] + public bool ErpControlsShipping + { + get => ShippingControlMode != Constants.ShippingControlMode.DynamicwebControlsShipping; + set => ShippingControlMode = value + ? Constants.ShippingControlMode.ErpControlsShipping + : Constants.ShippingControlMode.DynamicwebControlsShipping; + } /// /// Gets or sets the key for shipping item type. @@ -750,11 +759,16 @@ IEnumerable IParameterOptions.GetParameterOptions(string dropdo options.Add(new("Fixed Asset", "FixedAsset")); options.Add(new("Resource", "Resource")); break; + case "Shipping control": + options.Add(new("Dynamicweb controls shipping", Constants.ShippingControlMode.DynamicwebControlsShipping)); + options.Add(new("ERP controls shipping", Constants.ShippingControlMode.ErpControlsShipping)); + options.Add(new("ERP calculates shipping cost based on Dynamicweb selection", Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection)); + break; case "ConnectionToType": options.Add(new(nameof(ConnectionType.Endpoint), ConnectionType.Endpoint)); options.Add(new("Dynamicweb connector web service", ConnectionType.WebService)); break; - case "ERP Local Currency": + case "ERP Local Currency": foreach (var currency in Services.Currencies.GetAllCurrencies()) { options.Add(new($"{currency.GetName(Services.Languages.GetDefaultLanguageId())} - {currency.Code}", currency.Code)); @@ -796,7 +810,7 @@ IEnumerable IParameterVisibility.GetHiddenParameterNames(string paramete result.Add("Include variants in the product information request"); result.Add("Max products per request"); } - break; + break; } return result; } diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs index 891b0dd..aaac6e0 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs @@ -6,6 +6,7 @@ using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Discounts; using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Extensions; using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Logging; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping; using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.XmlGenerators; using Dynamicweb.Ecommerce.Orders; using Dynamicweb.Ecommerce.Prices; @@ -91,6 +92,12 @@ private static ResponseCacheLevel GetOrderCacheLevel(Settings settings) bool erpControlsDiscount = user.IsUserErpDiscountAllowed(settings); + if (IsAllOrderLinesDiscounts(erpControlsDiscount, order, liveIntegrationSubmitType)) + { + Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.OrderHandler.UpdateOrder END"); + return null; + } + // default states successOrderStateId ??= settings.OrderStateAfterExportSucceeded; @@ -103,7 +110,7 @@ private static ResponseCacheLevel GetOrderCacheLevel(Settings settings) LiveIntegrationSubmitType = liveIntegrationSubmitType, ReferenceName = "OrdersPut", ErpControlsDiscount = erpControlsDiscount, - ErpControlsShipping = settings.ErpControlsShipping, + ShippingControlMode = settings.ShippingControlMode, ErpShippingItemKey = settings.ErpShippingItemKey, ErpShippingItemType = settings.ErpShippingItemType, CalculateOrderUsingProductNumber = settings.CalculateOrderUsingProductNumber, @@ -904,7 +911,7 @@ private static bool ProcessResponse(in OrderResponseContext ctx, XmlDocument res { XmlNode orderNode = response.SelectSingleNode("//item [@table='EcomOrders']"); PriceInfo shippingFeeSentInRequest = null; - if (!createOrder && !ctx.Settings.ErpControlsShipping && !string.IsNullOrEmpty(order.ShippingMethodId)) + if (!createOrder && ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping && !string.IsNullOrEmpty(order.ShippingMethodId)) { shippingFeeSentInRequest = order.ShippingFee; } @@ -954,8 +961,15 @@ private static bool ProcessResponse(in OrderResponseContext ctx, XmlDocument res else { SetOrderPrices(order, orderNode, ctx, logger, orderId, out updatePriceBeforeFeesFromOrderPrice); + } + if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) + { + ErpShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); + } + else if (ctx.Settings.ShippingControlMode != Constants.ShippingControlMode.DynamicwebControlsShipping) + { + LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } - LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } else { @@ -979,7 +993,7 @@ private static bool ProcessResponse(in OrderResponseContext ctx, XmlDocument res } else { - if (!ctx.Settings.ErpControlsShipping && shippingFeeSentInRequest != null) + if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping && shippingFeeSentInRequest != null) { UpdateDynamicwebShipping(order, orderNode, shippingFeeSentInRequest, ctx.Settings, logger, updatePriceBeforeFeesFromOrderPrice); } @@ -1174,7 +1188,7 @@ private static void SetPrices(Settings settings, Order order, XmlNode orderNode, /// The order node. private static void SetShippingWarning(Settings settings, Order order, XmlNode orderNode) { - if (!settings.ErpControlsShipping) + if (settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping) { var node = orderNode.SelectSingleNode("column [@columnName='OrderShippingWarning']"); if (node != null) @@ -1397,6 +1411,19 @@ internal static void SetCurrentlyProcessingOrder(Order order) internal static void RemoveCurrentlyProcessingOrder(Order order) { Caching.Cache.Current.Remove(OrderCacheKey(order)); - } + } + + private static bool IsAllOrderLinesDiscounts(bool erpControlsDiscountForUser, Order order, SubmitType liveIntegrationSubmitType) + { + //If no product lines and all lines are discounts remove discount lines and recalculate order + if (!order.Complete && erpControlsDiscountForUser && liveIntegrationSubmitType == SubmitType.LiveOrderOrCart && order.OrderLines.All(ol => ol.IsDiscount())) + { + order.OrderLines.RemoveDiscounts(); + order.AllowOverridePrices = false; + Services.Orders.ForcePriceRecalculation(order); + return true; + } + return false; + } } } \ No newline at end of file diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartAfterShippingCalculation.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartAfterShippingCalculation.cs new file mode 100644 index 0000000..92f5b8f --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartAfterShippingCalculation.cs @@ -0,0 +1,31 @@ +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.NotificationSubscribers; +using Dynamicweb.Ecommerce.Prices; +using Dynamicweb.Extensibility.Notifications; + +namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping +{ + [Subscribe(Ecommerce.Notifications.Ecommerce.Cart.AfterShippingCalculation)] + public class CartAfterShippingCalculation : NotificationSubscriberBase + { + public override void OnNotify(string notification, NotificationArgs args) + { + if (args is null || Context.Current?.Items is null) + return; + + var calculationArgs = (Ecommerce.Notifications.Ecommerce.Cart.AfterShippingCalculationArgs)args; + if (string.IsNullOrEmpty(calculationArgs.Shipping?.Id) || calculationArgs.Order is null || Context.Current.Session?[ErpShippingFeeProvider.OrderMarkerKey(calculationArgs.Order.Id)] is null) + { + return; + } + + var cached = Context.Current.Session[ErpShippingFeeProvider.MethodCacheKey(calculationArgs.Order.Id, calculationArgs.Shipping?.Id)] as PriceInfo; + if (cached is null) + return; + + calculationArgs.Price.PriceWithVAT = cached.PriceWithVAT; + calculationArgs.Price.PriceWithoutVAT = cached.PriceWithoutVAT; + calculationArgs.Price.VAT = cached.VAT; + calculationArgs.Price.VATPercent = cached.VATPercent; + } + } +} diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs new file mode 100644 index 0000000..904c6bd --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs @@ -0,0 +1,81 @@ +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Logging; +using Dynamicweb.Ecommerce.Orders; +using Dynamicweb.Ecommerce.Prices; +using System.Xml; + +namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping +{ + internal class ErpShippingFeeProvider + { + private const string CachePrefix = "ErpShippingFeeProvider_"; + + internal static string OrderMarkerKey(string orderId) => CachePrefix + orderId; + internal static string MethodCacheKey(string orderId, string shippingMethodId) => CachePrefix + orderId + "_" + shippingMethodId; + + internal static void ProcessShipping(Settings settings, Order order, XmlNode orderNode, Logger logger) + { + string shippingFee = orderNode.SelectSingleNode("column [@columnName='OrderShippingFee']")?.InnerText; + if (string.IsNullOrEmpty(shippingFee)) + { + ClearCache(order); + return; + } + + double fee = Helpers.ToDouble(settings, logger, shippingFee); + + string shippingFeeWithoutVat = orderNode.SelectSingleNode("column [@columnName='OrderShippingFeeWithoutVat']")?.InnerText; + double feeWithoutVat = 0; + double vatPercent = order.Price.VATPercent; + if (!string.IsNullOrEmpty(shippingFeeWithoutVat)) + { + feeWithoutVat = Helpers.ToDouble(settings, logger, shippingFeeWithoutVat); + feeWithoutVat = feeWithoutVat > fee ? fee : feeWithoutVat; + vatPercent = feeWithoutVat > 0 ? (fee / feeWithoutVat - 1) * 100 : 0; + } + if (feeWithoutVat <= 0) + { + feeWithoutVat = MinusVat(fee, vatPercent); + } + + var price = new PriceInfo(order.Currency); + price.PriceWithVAT = fee; + price.PriceWithoutVAT = feeWithoutVat; + price.VATPercent = vatPercent; + price.VAT = fee - feeWithoutVat; + + AddToCache(order, price); + + order.AllowOverrideShippingFee = true; + order.ShippingFee.PriceWithVAT = price.PriceWithVAT; + order.ShippingFee.VATPercent = price.VATPercent; + order.ShippingFee.PriceWithoutVAT = price.PriceWithoutVAT; + order.ShippingFee.VAT = price.VAT; + } + + internal static double MinusVat(double price, double percent) + { + return (double)((decimal)price / ((decimal)percent / 100M + 1M)); + } + + private static void AddToCache(Order order, PriceInfo shippingFee) + { + if (Context.Current?.Session is null || string.IsNullOrEmpty(order.ShippingMethodId)) + return; + + // Existence marker lets the notification subscriber know ERP fee caching is active for this order + Context.Current.Session[OrderMarkerKey(order.Id)] = true; + Context.Current.Session[MethodCacheKey(order.Id, order.ShippingMethodId)] = shippingFee; + } + + private static void ClearCache(Order order) + { + if (Context.Current?.Session is null) + return; + + Context.Current.Session.Remove(OrderMarkerKey(order.Id)); + if (!string.IsNullOrEmpty(order.ShippingMethodId)) + Context.Current.Session.Remove(MethodCacheKey(order.Id, order.ShippingMethodId)); + } + } +} diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveShippingFeeProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/LiveShippingFeeProvider.cs similarity index 87% rename from src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveShippingFeeProvider.cs rename to src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/LiveShippingFeeProvider.cs index c8452ea..3a69895 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveShippingFeeProvider.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/LiveShippingFeeProvider.cs @@ -28,13 +28,13 @@ public override PriceRaw CalculateShippingFee(Order order) PriceRaw rate = null; if (Context.Current != null && Context.Current.Items != null && Context.Current.Items["DynamicwebLiveShippingFeeProvider" + order.Id] != null) - { + { double shippingFee = (double)Context.Current.Items["DynamicwebLiveShippingFeeProvider" + order.Id]; rate = new PriceRaw(shippingFee, order.Currency); - } + } return rate; - } + } /// /// Processes the shipping. @@ -43,12 +43,9 @@ public override PriceRaw CalculateShippingFee(Order order) /// The order node. internal static void ProcessShipping(Settings settings, Order order, XmlNode orderNode, Logger logger) { - if (settings.ErpControlsShipping) - { - Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.LiveShippingFeeProvider.ProcessShipping START"); - ProcessLiveIntegrationShipping(settings, order, orderNode, logger); - Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.LiveShippingFeeProvider.ProcessShipping END"); - } + Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.LiveShippingFeeProvider.ProcessShipping START"); + ProcessLiveIntegrationShipping(settings, order, orderNode, logger); + Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.LiveShippingFeeProvider.ProcessShipping END"); } /// @@ -68,9 +65,9 @@ private static void AddToCache(Order order, double shippingFee) /// Gets the shipping. /// /// Shipping. - private static Shipping GetShipping() - { - return Services.Shippings.GetShippingsWithoutRegions(false).FirstOrDefault(s => !string.IsNullOrEmpty(s.ServiceSystemName) && + private static Ecommerce.Orders.Shipping GetShipping() + { + return Services.Shippings.GetShippingsWithoutRegions(false).FirstOrDefault(s => !string.IsNullOrEmpty(s.ServiceSystemName) && (string.Equals(typeof(LiveShippingFeeProvider).GetTypeNameWithAssembly(), s.ServiceSystemName) || string.Equals(s.ServiceSystemName, typeof(LiveShippingFeeProvider).FullName, StringComparison.OrdinalIgnoreCase))); } @@ -80,7 +77,7 @@ private static void ProcessLiveIntegrationShipping(Settings settings, Order orde string shippingFee = orderNode.SelectSingleNode("column [@columnName='OrderShippingFee']")?.InnerText; if (!string.IsNullOrEmpty(shippingFee)) { - Shipping liveIntegrationShipping = GetShipping(); + var liveIntegrationShipping = GetShipping(); if (liveIntegrationShipping != null) { order.ShippingMethodId = liveIntegrationShipping.Id; @@ -98,7 +95,7 @@ private static void ProcessLiveIntegrationShipping(Settings settings, Order orde if (!order.IsCart) { - order.ShippingFee.PriceWithVAT = fee; + order.ShippingFee.PriceWithVAT = fee; } else { diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UI/Commands/DownloadOrderXmlCommand.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UI/Commands/DownloadOrderXmlCommand.cs index 3c81743..8c5a352 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UI/Commands/DownloadOrderXmlCommand.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UI/Commands/DownloadOrderXmlCommand.cs @@ -85,7 +85,7 @@ private static string GetOrderCurrentXml(Settings settings, Order order) //For anonymous orders, CustomerAccessUserId can be unset, GetUserById returns null, and IsUserErpDiscountAllowed falls back //to the current anonymous-user setting. Therefore, ErpControlsDiscount here reflects current evaluation rather than historical checkout-time configuration. ErpControlsDiscount = isUserErpDiscountAllowed, - ErpControlsShipping = settings.ErpControlsShipping, + ShippingControlMode = settings.ShippingControlMode, ErpShippingItemKey = settings.ErpShippingItemKey, ErpShippingItemType = settings.ErpShippingItemType, CalculateOrderUsingProductNumber = settings.CalculateOrderUsingProductNumber diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs new file mode 100644 index 0000000..76398bd --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs @@ -0,0 +1,75 @@ +using Dynamicweb.Core; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; +using Dynamicweb.Updates; +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; + +namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Updates +{ + public sealed class LiveIntegrationUpdateProvider : UpdateProvider + { + public override IEnumerable GetUpdates() => new List() + { + new MethodUpdate("cd637b81-dd91-41ba-955e-6131c646a172", this, UpdateShippingControlMode), + }; + + private static void UpdateShippingControlMode(UpdateContext context) + { + if (!Directory.Exists(Path.Combine(SystemInformation.MapPath("/Files"), "System", "LiveIntegration"))) + return; + + bool updated = false; + + foreach (string file in Directory.GetFiles(Path.Combine(SystemInformation.MapPath("/Files"), "System", "LiveIntegration"), "*.Setup.xml")) + { + if (!file.Contains(Constants.AddInName, StringComparison.OrdinalIgnoreCase)) + continue; + + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(File.ReadAllText(file)); + + var shippingControlModeNode = doc.SelectSingleNode("//Settings/ShippingControlMode"); + if (shippingControlModeNode != null && !string.IsNullOrEmpty(shippingControlModeNode.InnerText)) + continue; + + var erpControlsShippingNode = doc.SelectSingleNode("//Settings/ErpControlsShipping"); + if (string.IsNullOrEmpty(erpControlsShippingNode?.InnerText)) + continue; + + bool erpControlsShipping = Converter.ToBoolean(erpControlsShippingNode.InnerText); + + string shippingControlMode = erpControlsShipping + ? Constants.ShippingControlMode.ErpControlsShipping + : Constants.ShippingControlMode.DynamicwebControlsShipping; + + XmlNode settingsNode = doc.SelectSingleNode("//Settings"); + if (settingsNode is not null) + { + if (shippingControlModeNode != null) + { + shippingControlModeNode.InnerText = shippingControlMode; + } + else + { + XmlElement newShippingControlModeNode = doc.CreateElement("ShippingControlMode"); + newShippingControlModeNode.InnerText = shippingControlMode; + settingsNode.AppendChild(newShippingControlModeNode); + } + + settingsNode.RemoveChild(erpControlsShippingNode); + + doc.Save(file); + updated = true; + } + } + + if (updated) + { + SettingsManager.Reload(); + } + } + } +} diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs index 1287a1a..882489f 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs @@ -57,7 +57,7 @@ public string GenerateOrderXml(Settings currentSettings, Order order, OrderXmlGe /// The order node. /// The order. private static void AddCustomerInformation(Settings currentSettings, XmlElement orderNode, Order order, User user) - { + { AddChildXmlNode(orderNode, "OrderCustomerAccessUserExternalId", !string.IsNullOrWhiteSpace(user?.ExternalID) ? user.ExternalID : currentSettings.AnonymousUserKey); AddChildXmlNode(orderNode, "OrderCustomerNumber", !string.IsNullOrWhiteSpace(user?.CustomerNumber) ? user.CustomerNumber : currentSettings.AnonymousUserKey); AddChildXmlNode(orderNode, "OrderCustomerName", !string.IsNullOrWhiteSpace(user?.Name) ? user.Name : order.CustomerName); @@ -87,10 +87,10 @@ private void AddOrderDeliveryInformation(OrderXmlGeneratorSettings settings, Xml UserAddress deliveryAddress = null; if (settings.CreateOrder && order.DeliveryAddressId > 0) { - deliveryAddress = UserManagementServices.UserAddresses.GetAddressById(order.DeliveryAddressId); + deliveryAddress = UserManagementServices.UserAddresses.GetAddressById(order.DeliveryAddressId); } string deliveryName = !string.IsNullOrEmpty(order.DeliveryName) ? order.DeliveryName : - !string.IsNullOrWhiteSpace(order.CustomerName) ? order.CustomerName : user?.Name ?? ""; + !string.IsNullOrWhiteSpace(order.CustomerName) ? order.CustomerName : user?.Name ?? ""; AddChildXmlNode(orderNode, "OrderDeliveryName", deliveryName); AddChildXmlNode(orderNode, "OrderDeliveryAddress", order.DeliveryAddress); @@ -228,29 +228,35 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, AddChildXmlNode(itemNode, "OrderShippingDate", order.ShippingDate.HasValue ? order.ShippingDate.ToIntegrationString(currentSettings, logger) : string.Empty); AddChildXmlNode(itemNode, "OrderReference", order.Reference); - if (settings.ErpControlsShipping) - { - // AX handles shipping - AddChildXmlNode(itemNode, "OrderShippingMethodName", string.Empty, true); - AddChildXmlNode(itemNode, "OrderShippingMethodId", string.Empty); - AddChildXmlNode(itemNode, "OrderShippingFee", string.Empty); - } - else + if (settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping) { - // Dynamicweb handles shipping + // DW calculates and sends shipping fee AddChildXmlNode(itemNode, "OrderShippingMethodName", order.ShippingMethod, true); AddChildXmlNode(itemNode, "OrderShippingMethodId", order.ShippingMethodId); - if (!settings.GenerateXmlForHash) - { - AddChildXmlNode(itemNode, "OrderShippingFee", order.ShippingFee.PriceWithVAT.ToIntegrationString(currentSettings, logger)); - AddChildXmlNode(itemNode, "OrderShippingFeeWithoutVat", order.ShippingFee.PriceWithoutVAT.ToIntegrationString(currentSettings, logger)); - } + AddShippingFeeNodes(currentSettings, itemNode, order, settings, logger); AddChildXmlNode(itemNode, "OrderShippingItemType", settings.ErpShippingItemType); AddChildXmlNode(itemNode, "OrderShippingItemKey", settings.ErpShippingItemKey); AddChildXmlNode(itemNode, "OrderShippingCode", order.ShippingMethodCode); AddChildXmlNode(itemNode, "OrderShippingAgentCode", order.ShippingMethodAgentCode); AddChildXmlNode(itemNode, "OrderShippingAgentServiceCode", order.ShippingMethodAgentServiceCode); } + else if (settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) + { + // DW selects the shipping method and sends the current fee; ERP may recalculate the freight cost based on the identity fields + AddChildXmlNode(itemNode, "OrderShippingMethodName", order.ShippingMethod, true); + AddChildXmlNode(itemNode, "OrderShippingMethodId", order.ShippingMethodId); + AddChildXmlNode(itemNode, "OrderShippingCode", order.ShippingMethodCode); + AddChildXmlNode(itemNode, "OrderShippingAgentCode", order.ShippingMethodAgentCode); + AddChildXmlNode(itemNode, "OrderShippingAgentServiceCode", order.ShippingMethodAgentServiceCode); + AddShippingFeeNodes(currentSettings, itemNode, order, settings, logger); + } + else + { + // ERP controls shipping entirely — send empty values + AddChildXmlNode(itemNode, "OrderShippingMethodName", string.Empty, true); + AddChildXmlNode(itemNode, "OrderShippingMethodId", string.Empty); + AddChildXmlNode(itemNode, "OrderShippingFee", string.Empty); + } if (!settings.GenerateXmlForHash) { @@ -273,6 +279,15 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, return tableNode; } + private static void AddShippingFeeNodes(Settings currentSettings, XmlElement itemNode, Order order, OrderXmlGeneratorSettings settings, Logger logger) + { + if (!settings.GenerateXmlForHash) + { + AddChildXmlNode(itemNode, "OrderShippingFee", order.ShippingFee.PriceWithVAT.ToIntegrationString(currentSettings, logger)); + AddChildXmlNode(itemNode, "OrderShippingFeeWithoutVat", order.ShippingFee.PriceWithoutVAT.ToIntegrationString(currentSettings, logger)); + } + } + /// /// Creates the order line XML. /// @@ -406,7 +421,7 @@ public double GetGiftCardAmountWithoutVat(OrderLine giftCardOrderLine) return unitPrice.PriceWithoutVAT; } else - { + { var giftCardDiscount = _orderPriceWithoutVat.Value; _orderPriceWithoutVat = 0d; return giftCardDiscount > 0 ? giftCardDiscount * -1 : giftCardDiscount; diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGeneratorSettings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGeneratorSettings.cs index b660251..2a1473b 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGeneratorSettings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGeneratorSettings.cs @@ -31,10 +31,9 @@ public class OrderXmlGeneratorSettings : XmlGeneratorSettings public bool ErpControlsDiscount { get; set; } /// - /// Gets or sets if ERP controls shipping + /// Gets or sets the shipping control mode. /// - /// true if [ERP controls shipping]; otherwise, false. - public bool ErpControlsShipping { get; set; } + public string ShippingControlMode { get; set; } /// /// Gets or sets the key for shipping item type.