From 5ebfa7ab37cf5a85f875fb33d29deab2a3959676 Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Fri, 22 May 2026 10:04:15 +0300 Subject: [PATCH 1/7] =?UTF-8?q?New=20"Hybrid"=20Shipping=20Control=20Mode?= =?UTF-8?q?=20=E2=80=94=20DW=20Selects,=20BC=20Calculates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configuration/Constants.cs | 12 +++ .../Configuration/ISettings.cs | 5 +- .../Configuration/Settings.cs | 9 +- ...Ecommerce.DynamicwebLiveIntegration.csproj | 4 +- ...eb.Ecommerce.DynamicwebLiveIntegration.sln | 24 +++++ .../LiveIntegrationAddIn.cs | 18 ++-- .../OrderHandler.cs | 20 ++-- .../Shipping/CartBeforeShippingCalculation.cs | 22 ++++ .../Shipping/ErpShippingFeeProvider.cs | 102 ++++++++++++++++++ .../{ => Shipping}/LiveShippingFeeProvider.cs | 25 ++--- .../UI/Commands/DownloadOrderXmlCommand.cs | 2 +- .../Updates/LiveIntegrationUpdateProvider.cs | 69 ++++++++++++ .../XmlGenerators/OrderXmlGenerator.cs | 35 +++--- .../OrderXmlGeneratorSettings.cs | 5 +- 14 files changed, 298 insertions(+), 54 deletions(-) create mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.sln create mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs create mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs rename src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/{ => Shipping}/LiveShippingFeeProvider.cs (87%) create mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs index b38da14..7dbc07a 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs @@ -77,5 +77,17 @@ internal static class OrderConfiguration public const string DefaultShippingItemType = "ItemCharge"; } + + internal 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..bc8073e 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs @@ -266,10 +266,9 @@ 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. - bool ErpControlsShipping { get; set; } + string ShippingControlMode { get; set; } /// /// Gets or sets if the orderline should be set to fixed when unitprice is set by the orderhandler diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs index 46743a7..dea3345 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,9 @@ 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; set; } /// /// Gets or sets the key for shipping item type. @@ -470,7 +469,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..47df938 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs @@ -381,14 +381,13 @@ 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; } /// /// Gets or sets the key for shipping item type. @@ -750,11 +749,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 +800,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..7fb3923 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; @@ -103,7 +104,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 +905,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 +955,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.ErpControlsShipping) + { + LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); + } + else if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) + { + ErpShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } - LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } else { @@ -979,7 +987,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 +1182,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 +1405,6 @@ internal static void SetCurrentlyProcessingOrder(Order order) internal static void RemoveCurrentlyProcessingOrder(Order order) { Caching.Cache.Current.Remove(OrderCacheKey(order)); - } + } } } \ No newline at end of file diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs new file mode 100644 index 0000000..09424a1 --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs @@ -0,0 +1,22 @@ +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.NotificationSubscribers; +using Dynamicweb.Extensibility.Notifications; + +namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping +{ + [Subscribe(Ecommerce.Notifications.Ecommerce.Cart.BeforeShippingCalculation)] + public class CartBeforeShippingCalculation : NotificationSubscriberBase + { + public override void OnNotify(string notification, NotificationArgs args) + { + if (args is null || Context.Current?.Items is null) + return; + + var myArgs = (Ecommerce.Notifications.Ecommerce.Cart.BeforeShippingCalculationArgs)args; + if (myArgs.Shipping is null || myArgs.Order is null || Context.Current.Session?["ErpShippingFeeProvider_" + myArgs.Order.Id] is null) + { + return; + } + Context.Current.Items[$"CartBeforeShippingCalculationShippingId{myArgs.Order.Id}"] = myArgs.Shipping.Id; + } + } +} diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs new file mode 100644 index 0000000..0848f03 --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs @@ -0,0 +1,102 @@ +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Logging; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.NotificationSubscribers; +using Dynamicweb.Ecommerce.Orders; +using Dynamicweb.Ecommerce.Prices; +using System.Xml; + +namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping +{ + public class ErpShippingFeeProvider : FeeProvider + { + private const string CachePrefix = "ErpShippingFeeProvider_"; + + private static string OrderMarkerKey(string orderId) => CachePrefix + orderId; + private static string MethodCacheKey(string orderId, string shippingMethodId) => CachePrefix + orderId + "_" + shippingMethodId; + private static string BeforeShippingItemKey(string orderId) => "CartBeforeShippingCalculationShippingId" + orderId; + + public override PriceRaw FindFee(Order order) + { + if (Context.Current?.Session is null || Context.Current.Items is null) + return null; + + var shippingMethodId = Core.Converter.ToString(Context.Current.Items[BeforeShippingItemKey(order.Id)]); + if (string.IsNullOrEmpty(shippingMethodId)) + return null; + + var cached = Context.Current.Session[MethodCacheKey(order.Id, shippingMethodId)] as PriceInfo; + if (cached is null) + return null; + + return new PriceRaw(cached.PriceWithVAT, order.Currency); + } + + internal static void ProcessShipping(Settings settings, Order order, XmlNode orderNode, Logger logger) + { + Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.ErpShippingFeeProvider START"); + + 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.ShippingFee.PriceWithVAT = price.PriceWithVAT; + order.ShippingFee.VATPercent = price.VATPercent; + order.ShippingFee.PriceWithoutVAT = price.PriceWithoutVAT; + order.ShippingFee.VAT = price.VAT; + + Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.ErpShippingFeeProvider END"); + } + + 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..0cf7d59 --- /dev/null +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs @@ -0,0 +1,69 @@ +using Dynamicweb.Core; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; +using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping; +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; + + string name = Path.GetFileNameWithoutExtension(file).Replace(".Setup", string.Empty, StringComparison.OrdinalIgnoreCase); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(File.ReadAllText(file)); + + if (!string.IsNullOrEmpty(doc.SelectSingleNode("//Settings/ShippingControlMode")?.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) + { + XmlElement shippingControlModeNode = doc.CreateElement("ShippingControlMode"); + shippingControlModeNode.InnerText = shippingControlMode; + settingsNode.AppendChild(shippingControlModeNode); + + 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..8c7f06c 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,16 +228,9 @@ 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) + if (settings.ShippingControlMode == Constants.ShippingControlMode.DynamicwebControlsShipping) { - // AX handles shipping - AddChildXmlNode(itemNode, "OrderShippingMethodName", string.Empty, true); - AddChildXmlNode(itemNode, "OrderShippingMethodId", string.Empty); - AddChildXmlNode(itemNode, "OrderShippingFee", string.Empty); - } - else - { - // Dynamicweb handles shipping + // DW calculates and sends shipping fee AddChildXmlNode(itemNode, "OrderShippingMethodName", order.ShippingMethod, true); AddChildXmlNode(itemNode, "OrderShippingMethodId", order.ShippingMethodId); if (!settings.GenerateXmlForHash) @@ -251,6 +244,22 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, AddChildXmlNode(itemNode, "OrderShippingAgentCode", order.ShippingMethodAgentCode); AddChildXmlNode(itemNode, "OrderShippingAgentServiceCode", order.ShippingMethodAgentServiceCode); } + else if (settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) + { + // DW selects the shipping method; ERP calculates the freight cost — send identity fields, no fee + 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); + } + 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) { @@ -406,7 +415,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. From b86f869632f9af8ba2ad604cd3e09531c550887f Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Fri, 22 May 2026 10:11:15 +0300 Subject: [PATCH 2/7] fix build --- .../Notifications/OrderAfterGenerateXmlSubscriber.cs | 2 +- .../Configuration/Constants.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) 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 7dbc07a..d34d157 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Constants.cs @@ -78,7 +78,14 @@ internal static class OrderConfiguration public const string DefaultShippingItemType = "ItemCharge"; } - internal static class ShippingControlMode + /// + /// 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"; From a1f17496e7d539db882db316b061e85a622655dd Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Tue, 26 May 2026 11:48:43 +0300 Subject: [PATCH 3/7] code review and bug fixes --- .../OrderHandler.cs | 29 +++++++++++++--- .../Shipping/CartAfterShippingCalculation.cs | 31 +++++++++++++++++ .../Shipping/CartBeforeShippingCalculation.cs | 22 ------------- .../Shipping/ErpShippingFeeProvider.cs | 33 ++++--------------- .../XmlGenerators/OrderXmlGenerator.cs | 16 ++++++--- 5 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartAfterShippingCalculation.cs delete mode 100644 src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs index 7fb3923..aaac6e0 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/OrderHandler.cs @@ -92,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; @@ -955,14 +961,14 @@ private static bool ProcessResponse(in OrderResponseContext ctx, XmlDocument res else { SetOrderPrices(order, orderNode, ctx, logger, orderId, out updatePriceBeforeFeesFromOrderPrice); - } - if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.ErpControlsShipping) + } + if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) { - LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); + ErpShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } - else if (ctx.Settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) + else if (ctx.Settings.ShippingControlMode != Constants.ShippingControlMode.DynamicwebControlsShipping) { - ErpShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); + LiveShippingFeeProvider.ProcessShipping(ctx.Settings, order, orderNode, logger); } } else @@ -1406,5 +1412,18 @@ 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/CartBeforeShippingCalculation.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs deleted file mode 100644 index 09424a1..0000000 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/CartBeforeShippingCalculation.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.NotificationSubscribers; -using Dynamicweb.Extensibility.Notifications; - -namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping -{ - [Subscribe(Ecommerce.Notifications.Ecommerce.Cart.BeforeShippingCalculation)] - public class CartBeforeShippingCalculation : NotificationSubscriberBase - { - public override void OnNotify(string notification, NotificationArgs args) - { - if (args is null || Context.Current?.Items is null) - return; - - var myArgs = (Ecommerce.Notifications.Ecommerce.Cart.BeforeShippingCalculationArgs)args; - if (myArgs.Shipping is null || myArgs.Order is null || Context.Current.Session?["ErpShippingFeeProvider_" + myArgs.Order.Id] is null) - { - return; - } - Context.Current.Items[$"CartBeforeShippingCalculationShippingId{myArgs.Order.Id}"] = myArgs.Shipping.Id; - } - } -} diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs index 0848f03..904c6bd 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Shipping/ErpShippingFeeProvider.cs @@ -1,40 +1,20 @@ using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Logging; -using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.NotificationSubscribers; using Dynamicweb.Ecommerce.Orders; using Dynamicweb.Ecommerce.Prices; using System.Xml; namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping { - public class ErpShippingFeeProvider : FeeProvider + internal class ErpShippingFeeProvider { private const string CachePrefix = "ErpShippingFeeProvider_"; - private static string OrderMarkerKey(string orderId) => CachePrefix + orderId; - private static string MethodCacheKey(string orderId, string shippingMethodId) => CachePrefix + orderId + "_" + shippingMethodId; - private static string BeforeShippingItemKey(string orderId) => "CartBeforeShippingCalculationShippingId" + orderId; - - public override PriceRaw FindFee(Order order) - { - if (Context.Current?.Session is null || Context.Current.Items is null) - return null; - - var shippingMethodId = Core.Converter.ToString(Context.Current.Items[BeforeShippingItemKey(order.Id)]); - if (string.IsNullOrEmpty(shippingMethodId)) - return null; - - var cached = Context.Current.Session[MethodCacheKey(order.Id, shippingMethodId)] as PriceInfo; - if (cached is null) - return null; - - return new PriceRaw(cached.PriceWithVAT, order.Currency); - } + 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) { - Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.ErpShippingFeeProvider START"); - string shippingFee = orderNode.SelectSingleNode("column [@columnName='OrderShippingFee']")?.InnerText; if (string.IsNullOrEmpty(shippingFee)) { @@ -66,12 +46,11 @@ internal static void ProcessShipping(Settings settings, Order order, XmlNode ord 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; - - Diagnostics.ExecutionTable.Current.Add("DynamicwebLiveIntegration.ErpShippingFeeProvider END"); + order.ShippingFee.VAT = price.VAT; } internal static double MinusVat(double price, double percent) @@ -98,5 +77,5 @@ private static void ClearCache(Order order) if (!string.IsNullOrEmpty(order.ShippingMethodId)) Context.Current.Session.Remove(MethodCacheKey(order.Id, order.ShippingMethodId)); } - } + } } diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs index 8c7f06c..0c5c975 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs @@ -233,11 +233,7 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, // 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); @@ -252,6 +248,7 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, AddChildXmlNode(itemNode, "OrderShippingCode", order.ShippingMethodCode); AddChildXmlNode(itemNode, "OrderShippingAgentCode", order.ShippingMethodAgentCode); AddChildXmlNode(itemNode, "OrderShippingAgentServiceCode", order.ShippingMethodAgentServiceCode); + AddShippingFeeNodes(currentSettings, itemNode, order, settings, logger); } else { @@ -282,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. /// From 3254a6e7c6c1d26bde279041911fc0adbd7287b3 Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Fri, 29 May 2026 14:59:44 +0300 Subject: [PATCH 4/7] NormalizeShippingControlMode --- .../Configuration/Settings.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs index dea3345..9c9111f 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs @@ -265,7 +265,21 @@ public string InstanceName /// /// Gets or sets the shipping control mode. /// - public string ShippingControlMode { 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; + } /// /// Gets or sets the key for shipping item type. From e0c64e5f28e04cb129a0ed4826726da0a5b09a08 Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Tue, 2 Jun 2026 12:43:27 +0300 Subject: [PATCH 5/7] fix breaking change by obsolete old ErpControlsShipping property --- .../Configuration/ISettings.cs | 8 ++++++++ .../Configuration/Settings.cs | 10 ++++++++++ .../LiveIntegrationAddIn.cs | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs index bc8073e..f6003e2 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/ISettings.cs @@ -270,6 +270,14 @@ public interface ISettings /// 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; } + /// /// Gets or sets if the orderline should be set to fixed when unitprice is set by the orderhandler /// diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs index 9c9111f..b540e36 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Configuration/Settings.cs @@ -281,6 +281,16 @@ private static string NormalizeShippingControlMode(string value) 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. /// diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs index 47df938..1a3bfbb 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs @@ -389,6 +389,16 @@ public LiveIntegrationAddIn() [AddInParameterOrder(157)] 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. /// From e1c27415773df2cafba09a907252b0434b52160f Mon Sep 17 00:00:00 2001 From: DWDBE <123462359+DWDBE@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:01:23 +0300 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Updates/LiveIntegrationUpdateProvider.cs | 22 ++++++++++++++----- .../XmlGenerators/OrderXmlGenerator.cs | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs index 0cf7d59..cc80f3f 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs @@ -1,6 +1,5 @@ using Dynamicweb.Core; using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Configuration; -using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Shipping; using Dynamicweb.Updates; using System; using System.Collections.Generic; @@ -28,12 +27,12 @@ private static void UpdateShippingControlMode(UpdateContext context) if (!file.Contains(Constants.AddInName, StringComparison.OrdinalIgnoreCase)) continue; - string name = Path.GetFileNameWithoutExtension(file).Replace(".Setup", string.Empty, StringComparison.OrdinalIgnoreCase); XmlDocument doc = new XmlDocument(); doc.LoadXml(File.ReadAllText(file)); - if (!string.IsNullOrEmpty(doc.SelectSingleNode("//Settings/ShippingControlMode")?.InnerText)) + var shippingControlModeNode = doc.SelectSingleNode("//Settings/ShippingControlMode"); + if (shippingControlModeNode != null && !string.IsNullOrEmpty(shippingControlModeNode.InnerText)) continue; var erpControlsShippingNode = doc.SelectSingleNode("//Settings/ErpControlsShipping"); @@ -49,15 +48,26 @@ private static void UpdateShippingControlMode(UpdateContext context) XmlNode settingsNode = doc.SelectSingleNode("//Settings"); if (settingsNode is not null) { - XmlElement shippingControlModeNode = doc.CreateElement("ShippingControlMode"); - shippingControlModeNode.InnerText = shippingControlMode; - settingsNode.AppendChild(shippingControlModeNode); + 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; } + + doc.Save(file); + updated = true; + } } if (updated) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs index 0c5c975..882489f 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs @@ -242,7 +242,7 @@ private XmlNode BuildOrderXml(Settings currentSettings, XmlDocument xmlDocument, } else if (settings.ShippingControlMode == Constants.ShippingControlMode.ErpCalculatesBasedOnDwSelection) { - // DW selects the shipping method; ERP calculates the freight cost — send identity fields, no fee + // 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); From 70f8edd2d03f1f12a9f0b0f7cbea309fe4de53d0 Mon Sep 17 00:00:00 2001 From: Dmitriy Benyuk Date: Tue, 2 Jun 2026 13:07:37 +0300 Subject: [PATCH 7/7] fix build --- .../Updates/LiveIntegrationUpdateProvider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs index cc80f3f..76398bd 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Updates/LiveIntegrationUpdateProvider.cs @@ -64,10 +64,6 @@ private static void UpdateShippingControlMode(UpdateContext context) doc.Save(file); updated = true; } - - doc.Save(file); - updated = true; - } } if (updated)