diff --git a/src/Libraries/Nop.Core/Domain/Shipping/ShippingMethodStateProvinceMapping.cs b/src/Libraries/Nop.Core/Domain/Shipping/ShippingMethodStateProvinceMapping.cs new file mode 100644 index 00000000000..4e51117e4ff --- /dev/null +++ b/src/Libraries/Nop.Core/Domain/Shipping/ShippingMethodStateProvinceMapping.cs @@ -0,0 +1,22 @@ +namespace Nop.Core.Domain.Shipping; + +/// +/// Represents a shipping method-state/provicne mapping class +/// +public partial class ShippingMethodStateProvinceMapping : BaseEntity +{ + /// + /// Gets or sets the shipping method identifier + /// + public int ShippingMethodId { get; set; } + + /// + /// Gets or sets the country identifier + /// + public int CountryId { get; set; } + + /// + /// Gets or sets the state province identifier + /// + public int StateProvinceId { get; set; } +} \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/BaseNameCompatibility.cs b/src/Libraries/Nop.Data/Mapping/BaseNameCompatibility.cs index e943c332a8f..251cf49e811 100644 --- a/src/Libraries/Nop.Data/Mapping/BaseNameCompatibility.cs +++ b/src/Libraries/Nop.Data/Mapping/BaseNameCompatibility.cs @@ -28,6 +28,7 @@ public partial class BaseNameCompatibility : INameCompatibility { typeof(DiscountProductMapping), "Discount_AppliedToProducts" }, { typeof(PermissionRecordCustomerRoleMapping), "PermissionRecord_Role_Mapping" }, { typeof(ShippingMethodCountryMapping), "ShippingMethodRestrictions" }, + { typeof(ShippingMethodStateProvinceMapping), "ShippingMethodStateProvinceRestrictions" }, { typeof(ProductCategory), "Product_Category_Mapping" }, { typeof(ProductManufacturer), "Product_Manufacturer_Mapping" }, { typeof(ProductPicture), "Product_Picture_Mapping" }, @@ -66,5 +67,8 @@ public partial class BaseNameCompatibility : INameCompatibility { (typeof(CustomerAttributeValue), "AttributeId"), "CustomerAttributeId" }, { (typeof(AddressAttributeValue), "AttributeId"), "AddressAttributeId" }, { (typeof(CheckoutAttributeValue), "AttributeId"), "CheckoutAttributeId" }, + { (typeof(ShippingMethodStateProvinceMapping), "ShippingMethodId"), "ShippingMethod_Id" }, + { (typeof(ShippingMethodStateProvinceMapping), "CountryId"), "Country_Id" }, + { (typeof(ShippingMethodStateProvinceMapping), "StateProvinceId"), "StateProvince_Id" }, }; } \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/Builders/Shipping/ShippingMethodStateProvinceMappingBuilder.cs b/src/Libraries/Nop.Data/Mapping/Builders/Shipping/ShippingMethodStateProvinceMappingBuilder.cs new file mode 100644 index 00000000000..1c69e084170 --- /dev/null +++ b/src/Libraries/Nop.Data/Mapping/Builders/Shipping/ShippingMethodStateProvinceMappingBuilder.cs @@ -0,0 +1,31 @@ +using FluentMigrator.Builders.Create.Table; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Shipping; +using Nop.Data.Extensions; + +namespace Nop.Data.Mapping.Builders.Shipping; + +/// +/// Represents a shipping method state province mapping entity builder +/// +public partial class ShippingMethodStateProvinceMappingBuilder : NopEntityBuilder +{ + #region Methods + + /// + /// Apply entity configuration + /// + /// Create table expression builder + public override void MapEntity(CreateTableExpressionBuilder table) + { + table + .WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.ShippingMethodId))) + .AsInt32().PrimaryKey().ForeignKey() + .WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.CountryId))) + .AsInt32().PrimaryKey().ForeignKey().OnDelete(System.Data.Rule.None) + .WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.StateProvinceId))) + .AsInt32().PrimaryKey().ForeignKey(); + } + + #endregion +} \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Migrations/Installation/SchemaMigration.cs b/src/Libraries/Nop.Data/Migrations/Installation/SchemaMigration.cs index 32c00a304ac..242f46404db 100644 --- a/src/Libraries/Nop.Data/Migrations/Installation/SchemaMigration.cs +++ b/src/Libraries/Nop.Data/Migrations/Installation/SchemaMigration.cs @@ -116,6 +116,7 @@ public override void Up() Create.TableFor(); Create.TableFor(); Create.TableFor(); + Create.TableFor(); Create.TableFor(); Create.TableFor(); Create.TableFor(); diff --git a/src/Libraries/Nop.Services/Shipping/Caching/ShippingMethodStateProvinceMappingCacheEventConsumer.cs b/src/Libraries/Nop.Services/Shipping/Caching/ShippingMethodStateProvinceMappingCacheEventConsumer.cs new file mode 100644 index 00000000000..f2e2f8ccc3e --- /dev/null +++ b/src/Libraries/Nop.Services/Shipping/Caching/ShippingMethodStateProvinceMappingCacheEventConsumer.cs @@ -0,0 +1,11 @@ +using Nop.Core.Domain.Shipping; +using Nop.Services.Caching; + +namespace Nop.Services.Shipping.Caching; + +/// +/// Represents a shipping method-state/province mapping cache event consumer +/// +public partial class ShippingMethodStateProvinceMappingCacheEventConsumer : CacheEventConsumer +{ +} \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Shipping/IShippingService.cs b/src/Libraries/Nop.Services/Shipping/IShippingService.cs index 7b723fb9331..b5425942439 100644 --- a/src/Libraries/Nop.Services/Shipping/IShippingService.cs +++ b/src/Libraries/Nop.Services/Shipping/IShippingService.cs @@ -35,11 +35,12 @@ public partial interface IShippingService /// Gets all shipping methods /// /// The country identifier to filter by + /// The state province identifier to filter by /// /// A task that represents the asynchronous operation /// The task result contains the shipping methods /// - Task> GetAllShippingMethodsAsync(int? filterByCountryId = null); + Task> GetAllShippingMethodsAsync(int? filterByCountryId = null, int? filterByStateProvinceId = null); /// /// Inserts a shipping method @@ -91,6 +92,43 @@ public partial interface IShippingService /// A task that represents the asynchronous operation Task DeleteShippingMethodCountryMappingAsync(ShippingMethodCountryMapping shippingMethodCountryMapping); + /// + /// Does state province restriction exist + /// + /// Shipping method + /// State province identifier + /// + /// A task that represents the asynchronous operation + /// The task result contains the result + /// + Task StateProvinceRestrictionExistsAsync(ShippingMethod shippingMethod, int countryId, int stateProvinceId); + + /// + /// Gets shipping state province mappings + /// + /// The shipping method identifier + /// Country identifier + /// State province identifier + /// + /// A task that represents the asynchronous operation + /// The task result contains the shipping state province mappings + /// + Task> GetShippingMethodStateProvinceMappingAsync(int shippingMethodId, int countryId, int stateProvinceId); + + /// + /// Inserts a shipping state province mapping + /// + /// Shipping state province mapping + /// A task that represents the asynchronous operation + Task InsertShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping); + + /// + /// Delete the shipping state province mapping + /// + /// Shipping state province mapping + /// A task that represents the asynchronous operation + Task DeleteShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping); + #endregion #region Warehouses diff --git a/src/Libraries/Nop.Services/Shipping/NopShippingDefaults.cs b/src/Libraries/Nop.Services/Shipping/NopShippingDefaults.cs index ec4dba14f50..528380ea04d 100644 --- a/src/Libraries/Nop.Services/Shipping/NopShippingDefaults.cs +++ b/src/Libraries/Nop.Services/Shipping/NopShippingDefaults.cs @@ -15,8 +15,9 @@ public static partial class NopShippingDefaults /// /// /// {0} : country identifier + /// {1} : state province identifier /// - public static CacheKey ShippingMethodsAllCacheKey => new("Nop.shippingmethod.all.{0}", NopEntityCacheDefaults.AllPrefix); + public static CacheKey ShippingMethodsAllCacheKey => new("Nop.shippingmethod.all.{0}.{1}", NopEntityCacheDefaults.AllPrefix); #endregion } \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Shipping/ShippingService.cs b/src/Libraries/Nop.Services/Shipping/ShippingService.cs index 1c499ca8f9d..3a9b3eb9c40 100644 --- a/src/Libraries/Nop.Services/Shipping/ShippingService.cs +++ b/src/Libraries/Nop.Services/Shipping/ShippingService.cs @@ -42,6 +42,7 @@ public partial class ShippingService : IShippingService protected readonly IStoreContext _storeContext; protected readonly ShippingSettings _shippingSettings; protected readonly ShoppingCartSettings _shoppingCartSettings; + private readonly IRepository _shippingMethodStateProvinceMappingRepository; #endregion @@ -65,7 +66,8 @@ public ShippingService(IAddressService addressService, IStateProvinceService stateProvinceService, IStoreContext storeContext, ShippingSettings shippingSettings, - ShoppingCartSettings shoppingCartSettings) + ShoppingCartSettings shoppingCartSettings, + IRepository shippingMethodStateProvinceMappingRepository) { _addressService = addressService; _checkoutAttributeParser = checkoutAttributeParser; @@ -86,6 +88,7 @@ public ShippingService(IAddressService addressService, _storeContext = storeContext; _shippingSettings = shippingSettings; _shoppingCartSettings = shoppingCartSettings; + _shippingMethodStateProvinceMappingRepository = shippingMethodStateProvinceMappingRepository; } #endregion @@ -208,37 +211,67 @@ public virtual async Task GetShippingMethodByIdAsync(int shippin /// Gets all shipping methods /// /// The country identifier to filter by + /// The state province identifier to filter by /// /// A task that represents the asynchronous operation /// The task result contains the shipping methods /// - public virtual async Task> GetAllShippingMethodsAsync(int? filterByCountryId = null) + public virtual async Task> GetAllShippingMethodsAsync(int? filterByCountryId = null, int? filterByStateProvinceId = null) { if (filterByCountryId.HasValue && filterByCountryId.Value > 0) { + if (filterByStateProvinceId.HasValue && filterByStateProvinceId.Value > 0) + { + return await _shippingMethodRepository.GetAllAsync(query => + { + var query1 = from sm in query + join smcm in _shippingMethodCountryMappingRepository.Table on sm.Id equals smcm.ShippingMethodId + where smcm.CountryId == filterByCountryId.Value + select sm.Id; + query1 = query1.Distinct(); + + var query2 = from sm in query + join smspm in _shippingMethodStateProvinceMappingRepository.Table on sm.Id equals smspm.ShippingMethodId + where + smspm.CountryId == filterByCountryId.Value && + smspm.StateProvinceId == filterByStateProvinceId.Value + select sm.Id; + query2 = query2.Distinct(); + + var query3 = from sm in query + where + !query1.Contains(sm.Id) && + !query2.Contains(sm.Id) + orderby sm.DisplayOrder, sm.Id + select sm; + + return query3; + }, cache => cache.PrepareKeyForDefaultCache(NopShippingDefaults.ShippingMethodsAllCacheKey, filterByCountryId, filterByStateProvinceId)); + } + return await _shippingMethodRepository.GetAllAsync(query => { var query1 = from sm in query - join smcm in _shippingMethodCountryMappingRepository.Table on sm.Id equals smcm.ShippingMethodId - where smcm.CountryId == filterByCountryId.Value - select sm.Id; + join smcm in _shippingMethodCountryMappingRepository.Table on sm.Id equals smcm.ShippingMethodId + where smcm.CountryId == filterByCountryId.Value + select sm.Id; query1 = query1.Distinct(); var query2 = from sm in query - where !query1.Contains(sm.Id) - orderby sm.DisplayOrder, sm.Id - select sm; + where !query1.Contains(sm.Id) + orderby sm.DisplayOrder, sm.Id + select sm; return query2; - }, cache => cache.PrepareKeyForDefaultCache(NopShippingDefaults.ShippingMethodsAllCacheKey, filterByCountryId)); + }, cache => cache.PrepareKeyForDefaultCache(NopShippingDefaults.ShippingMethodsAllCacheKey, filterByCountryId, 0)); } return await _shippingMethodRepository.GetAllAsync(query => { return from sm in query - orderby sm.DisplayOrder, sm.Id - select sm; + orderby sm.DisplayOrder, sm.Id + select sm; }, cache => default); } @@ -319,6 +352,68 @@ public virtual async Task DeleteShippingMethodCountryMappingAsync(ShippingMethod await _shippingMethodCountryMappingRepository.DeleteAsync(shippingMethodCountryMapping); } + /// + /// Does state province restriction exist + /// + /// Shipping method + /// State province identifier + /// + /// A task that represents the asynchronous operation + /// The task result contains the result + /// + public virtual async Task StateProvinceRestrictionExistsAsync(ShippingMethod shippingMethod, int countryId, int stateProvinceId) + { + if (shippingMethod == null) + throw new ArgumentNullException(nameof(shippingMethod)); + + var result = await _shippingMethodStateProvinceMappingRepository.Table + .AnyAsync(smcm => smcm.ShippingMethodId == shippingMethod.Id && + smcm.CountryId == countryId && + smcm.StateProvinceId == stateProvinceId); + + return result; + } + + /// + /// Gets shipping state province mappings + /// + /// The shipping method identifier + /// Country identifier + /// State province identifier + /// + /// A task that represents the asynchronous operation + /// The task result contains the shipping state province mappings + /// + public virtual async Task> GetShippingMethodStateProvinceMappingAsync(int shippingMethodId, int countryId, int stateProvinceId) + { + var query = _shippingMethodStateProvinceMappingRepository.Table.Where(smcm => + smcm.ShippingMethodId == shippingMethodId && + smcm.CountryId == countryId && + smcm.StateProvinceId == stateProvinceId); + + return await query.ToListAsync(); + } + + /// + /// Inserts a shipping state province mapping + /// + /// Shipping state province mapping + /// A task that represents the asynchronous operation + public virtual async Task InsertShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping) + { + await _shippingMethodStateProvinceMappingRepository.InsertAsync(shippingMethodStateProvinceMapping); + } + + /// + /// Delete the shipping state province mapping + /// + /// Shipping state province mapping + /// A task that represents the asynchronous operation + public virtual async Task DeleteShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping) + { + await _shippingMethodStateProvinceMappingRepository.DeleteAsync(shippingMethodStateProvinceMapping); + } + #endregion #region Warehouses @@ -359,8 +454,8 @@ public virtual async Task> GetAllWarehousesAsync(string name = var warehouses = await _warehouseRepository.GetAllAsync(query => { return from wh in query - orderby wh.Name - select wh; + orderby wh.Name + select wh; }, cache => default); if (!string.IsNullOrEmpty(name)) diff --git a/src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal/FixedByWeightByTotalComputationMethod.cs b/src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal/FixedByWeightByTotalComputationMethod.cs index ad29f184439..30f70c26b86 100644 --- a/src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal/FixedByWeightByTotalComputationMethod.cs +++ b/src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal/FixedByWeightByTotalComputationMethod.cs @@ -163,7 +163,7 @@ public async Task GetShippingOptionsAsync(GetShipping //get weight of shipped items (excluding items with free shipping) var weight = await _shippingService.GetTotalWeightAsync(getShippingOptionRequest, ignoreFreeShippedItems: true); - foreach (var shippingMethod in await _shippingService.GetAllShippingMethodsAsync(countryId)) + foreach (var shippingMethod in await _shippingService.GetAllShippingMethodsAsync(countryId, stateProvinceId)) { int? transitDays = null; var rate = decimal.Zero; @@ -194,7 +194,8 @@ public async Task GetShippingOptionsAsync(GetShipping { //shipping rate calculation by fixed rate var restrictByCountryId = getShippingOptionRequest.ShippingAddress?.CountryId; - response.ShippingOptions = await (await _shippingService.GetAllShippingMethodsAsync(restrictByCountryId)).SelectAwait(async shippingMethod => new ShippingOption + var restrictByStateProvinceId = getShippingOptionRequest.ShippingAddress?.StateProvinceId; + response.ShippingOptions = await (await _shippingService.GetAllShippingMethodsAsync(restrictByCountryId, restrictByStateProvinceId)).SelectAwait(async shippingMethod => new ShippingOption { Name = await _localizationService.GetLocalizedAsync(shippingMethod, x => x.Name), Description = await _localizationService.GetLocalizedAsync(shippingMethod, x => x.Description), @@ -223,7 +224,8 @@ public async Task GetShippingOptionsAsync(GetShipping return null; var restrictByCountryId = getShippingOptionRequest.ShippingAddress?.CountryId; - var rates = await (await _shippingService.GetAllShippingMethodsAsync(restrictByCountryId)) + var restrictByStateProvinceId = getShippingOptionRequest.ShippingAddress?.StateProvinceId; + var rates = await (await _shippingService.GetAllShippingMethodsAsync(restrictByCountryId, restrictByStateProvinceId)) .SelectAwait(async shippingMethod => await GetRateAsync(shippingMethod.Id)).Distinct().ToListAsync(); //return default rate if all of them equal diff --git a/src/Presentation/Nop.Web.Framework/Infrastructure/AdminWidgetZones.cs b/src/Presentation/Nop.Web.Framework/Infrastructure/AdminWidgetZones.cs index 8eac0a44da8..cc57e681988 100644 --- a/src/Presentation/Nop.Web.Framework/Infrastructure/AdminWidgetZones.cs +++ b/src/Presentation/Nop.Web.Framework/Infrastructure/AdminWidgetZones.cs @@ -250,6 +250,7 @@ public static partial class AdminWidgetZones public static string ShippingMethodListButtons => "admin_shipping_method_list_buttons"; public static string ShippingProviderListButtons => "admin_shipping_provider_list_buttons"; public static string ShippingRestrictionListButtons => "admin_shipping_restriction_list_buttons"; + public static string ShippingStateProvinceRestrictionListButtons => "admin_shipping_state_province_restriction_list_buttons"; public static string ShippingDetailsBlock => "admin_shipping_details_block"; public static string ShippingSettingsButtons => "admin_shipping_settings_buttons"; public static string ShoppingCartSettingsDetailsBlock => "admin_shopping_cart_settings_details_block"; diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index e4ae39eef50..322cc540ea0 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -9612,6 +9612,21 @@ The settings have been updated successfully. + + Select states/provinces + + + Shipping method state/province restrictions + + + State / Province + + + Please mark the checkbox(es) for the state/province or states/provinces in which you want the shipping method(s) not available + + + The settings have been updated successfully. + Warehouses diff --git a/src/Presentation/Nop.Web/Areas/Admin/Controllers/ShippingController.cs b/src/Presentation/Nop.Web/Areas/Admin/Controllers/ShippingController.cs index 24f8c036755..4da39b60fa0 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Controllers/ShippingController.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Controllers/ShippingController.cs @@ -45,6 +45,7 @@ public partial class ShippingController : BaseAdminController protected readonly IGenericAttributeService _genericAttributeService; protected readonly IWorkContext _workContext; protected readonly ShippingSettings _shippingSettings; + private readonly IStateProvinceService _stateProvinceService; private static readonly char[] _separator = [',']; #endregion @@ -67,7 +68,8 @@ public ShippingController(IAddressService addressService, IShippingService shippingService, IGenericAttributeService genericAttributeService, IWorkContext workContext, - ShippingSettings shippingSettings) + ShippingSettings shippingSettings, + IStateProvinceService stateProvinceService) { _addressService = addressService; _countryService = countryService; @@ -86,6 +88,7 @@ public ShippingController(IAddressService addressService, _genericAttributeService = genericAttributeService; _workContext = workContext; _shippingSettings = shippingSettings; + _stateProvinceService = stateProvinceService; } #endregion @@ -863,5 +866,73 @@ public virtual async Task RestrictionSave(ShippingMethodRestricti return RedirectToAction("Restrictions"); } + public virtual async Task StateProvinceRestrictions(int countryId) + { + if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + //prepare model + var model = await _shippingModelFactory.PrepareShippingMethodStateProvinceRestrictionModelAsync(new ShippingMethodStateProvinceRestrictionModel() { CountryId = countryId }); + + return View(model); + } + + //we ignore this filter for increase RequestFormLimits + [IgnoreAntiforgeryToken] + //we use 2048 value because in some cases default value (1024) is too small for this action + [RequestFormLimits(ValueCountLimit = 2048)] + [HttpPost, ActionName("StateProvinceRestrictions")] + public virtual async Task StateProvinceRestrictionSave(ShippingMethodStateProvinceRestrictionModel model, IFormCollection form) + { + if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var stateProvinces = await _stateProvinceService.GetStateProvincesByCountryIdAsync(model.CountryId, showHidden: true); + var shippingMethods = await _shippingService.GetAllShippingMethodsAsync(); + + foreach (var shippingMethod in shippingMethods) + { + var formKey = "restrict_" + shippingMethod.Id; + var stateProvinceIdsToRestrict = !StringValues.IsNullOrEmpty(form[formKey]) + ? form[formKey].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToList() + : new List(); + + foreach (var stateProvince in stateProvinces) + { + var restrict = stateProvinceIdsToRestrict.Contains(stateProvince.Id); + var shippingMethodStateProvinceMappings = + await _shippingService.GetShippingMethodStateProvinceMappingAsync(shippingMethod.Id, model.CountryId, stateProvince.Id); + + if (restrict) + { + if (shippingMethodStateProvinceMappings.Any()) + continue; + + await _shippingService.InsertShippingMethodStateProvinceMappingAsync(new ShippingMethodStateProvinceMapping + { + ShippingMethodId = shippingMethod.Id, + CountryId = model.CountryId, + StateProvinceId = stateProvince.Id + }); + await _shippingService.UpdateShippingMethodAsync(shippingMethod); + } + else + { + if (!shippingMethodStateProvinceMappings.Any()) + continue; + + await _shippingService.DeleteShippingMethodStateProvinceMappingAsync(shippingMethodStateProvinceMappings.FirstOrDefault()); + await _shippingService.UpdateShippingMethodAsync(shippingMethod); + } + } + } + + _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Configuration.Shipping.StateProvinceRestrictions.Updated")); + + return RedirectToAction("StateProvinceRestrictions", new { countryId = model.CountryId }); + } + #endregion } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Factories/IShippingModelFactory.cs b/src/Presentation/Nop.Web/Areas/Admin/Factories/IShippingModelFactory.cs index e419d6664a9..7788e2aeeb8 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Factories/IShippingModelFactory.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Factories/IShippingModelFactory.cs @@ -177,4 +177,14 @@ Task PrepareProductAvailabilityRangeModelAsync(Pr /// The task result contains the shipping method restriction model /// Task PrepareShippingMethodRestrictionModelAsync(ShippingMethodRestrictionModel model); + + /// + /// Prepare shipping method state province restriction model + /// + /// Shipping method state province restriction model + /// + /// A task that represents the asynchronous operation + /// The task result contains the shipping method state province restriction model + /// + Task PrepareShippingMethodStateProvinceRestrictionModelAsync(ShippingMethodStateProvinceRestrictionModel model); } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Factories/ShippingModelFactory.cs b/src/Presentation/Nop.Web/Areas/Admin/Factories/ShippingModelFactory.cs index ba6acdadda1..841466a8ac6 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Factories/ShippingModelFactory.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Factories/ShippingModelFactory.cs @@ -530,5 +530,41 @@ public virtual async Task PrepareShippingMethodR return model; } + /// + /// Prepare shipping method state province restriction model + /// + /// Shipping method state province restriction model + /// + /// A task that represents the asynchronous operation + /// The task result contains the shipping method state province restriction model + /// + public virtual async Task PrepareShippingMethodStateProvinceRestrictionModelAsync(ShippingMethodStateProvinceRestrictionModel model) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + + var stateProvinces = await _stateProvinceService.GetStateProvincesByCountryIdAsync(model.CountryId, showHidden: true); + model.AvailableStateProvinces = stateProvinces.Select(stateProvince => + { + var stateProvinceModel = stateProvince.ToModel(); + + return stateProvinceModel; + }).ToList(); + + foreach (var shippingMethod in await _shippingService.GetAllShippingMethodsAsync()) + { + model.AvailableShippingMethods.Add(shippingMethod.ToModel()); + foreach (var stateProvince in stateProvinces) + { + if (!model.Restricted.ContainsKey(stateProvince.Id)) + model.Restricted[stateProvince.Id] = new Dictionary(); + + model.Restricted[stateProvince.Id][shippingMethod.Id] = await _shippingService.StateProvinceRestrictionExistsAsync(shippingMethod, model.CountryId, stateProvince.Id); + } + } + + return model; + } + #endregion } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Models/Shipping/ShippingMethodStateProvinceRestrictionModel.cs b/src/Presentation/Nop.Web/Areas/Admin/Models/Shipping/ShippingMethodStateProvinceRestrictionModel.cs new file mode 100644 index 00000000000..33ed70d964b --- /dev/null +++ b/src/Presentation/Nop.Web/Areas/Admin/Models/Shipping/ShippingMethodStateProvinceRestrictionModel.cs @@ -0,0 +1,34 @@ +using Nop.Web.Areas.Admin.Models.Directory; +using Nop.Web.Framework.Models; + +namespace Nop.Web.Areas.Admin.Models.Shipping; + +/// +/// Represents a shipping method state province restriction model +/// +public partial record ShippingMethodStateProvinceRestrictionModel : BaseNopModel +{ + #region Ctor + + public ShippingMethodStateProvinceRestrictionModel() + { + AvailableShippingMethods = new List(); + AvailableStateProvinces = new List(); + Restricted = new Dictionary>(); + } + + #endregion + + #region Properties + + public int CountryId { get; set; } + + public IList AvailableShippingMethods { get; set; } + + public IList AvailableStateProvinces { get; set; } + + //[state province id] / [shipping method id] / [restricted] + public IDictionary> Restricted { get; set; } + + #endregion +} \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/Restrictions.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/Restrictions.cshtml index d5d64e45222..96ba42c987e 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/Restrictions.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/Restrictions.cshtml @@ -79,6 +79,7 @@ @c.Name + (@T("Admin.Configuration.Shipping.StateProvinceRestrictions.Select")) @foreach (var sm in Model.AvailableShippingMethods) { diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/StateProvinceRestrictions.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/StateProvinceRestrictions.cshtml new file mode 100644 index 00000000000..83497368ec7 --- /dev/null +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shipping/StateProvinceRestrictions.cshtml @@ -0,0 +1,103 @@ +@model ShippingMethodStateProvinceRestrictionModel + +@{ + //page title + ViewBag.PageTitle = T("Admin.Configuration.Shipping.StateProvinceRestrictions").Text; + //active menu item (system name) + NopHtml.SetActiveMenuItemSystemName("Shipping providers"); +} + +
+
+

+ @T("Admin.Configuration.Shipping.StateProvinceRestrictions") + + + @T("Admin.Configuration.Shipping.Providers.BackToList") + +

+
+ + @await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.ShippingStateProvinceRestrictionListButtons, additionalData = Model }) +
+
+ +
+
+
+
+
+
+

@T("Admin.Configuration.Shipping.StateProvinceRestrictions.Description")

+ + + + @if (Model.AvailableStateProvinces.Count == 0) + { + No states/provinces defined + } + else if (Model.AvailableShippingMethods.Count == 0) + { + No shipping methods available + } + else + { + + + + + + + @foreach (var sm in Model.AvailableShippingMethods) + { + + } + + + + @foreach (var sp in Model.AvailableStateProvinces) + { + + + @foreach (var sm in Model.AvailableShippingMethods) + { + var restricted = Model.Restricted.ContainsKey(sp.Id) && Model.Restricted[sp.Id][sm.Id]; + + } + + } + +
+ @T("Admin.Configuration.Shipping.StateProvinceRestrictions.StateProvince") + +
+ +
+
+ @sp.Name + + +
+ } +
+
+
+
+
+
+
\ No newline at end of file