Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shipping Method Restrictions based on 'StateProvince' #7163

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Nop.Core.Domain.Shipping;

/// <summary>
/// Represents a shipping method-state/provicne mapping class
/// </summary>
public partial class ShippingMethodStateProvinceMapping : BaseEntity
{
/// <summary>
/// Gets or sets the shipping method identifier
/// </summary>
public int ShippingMethodId { get; set; }

/// <summary>
/// Gets or sets the country identifier
/// </summary>
public int CountryId { get; set; }

/// <summary>
/// Gets or sets the state province identifier
/// </summary>
public int StateProvinceId { get; set; }
}
4 changes: 4 additions & 0 deletions src/Libraries/Nop.Data/Mapping/BaseNameCompatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down Expand Up @@ -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" },
};
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Represents a shipping method state province mapping entity builder
/// </summary>
public partial class ShippingMethodStateProvinceMappingBuilder : NopEntityBuilder<ShippingMethodStateProvinceMapping>
{
#region Methods

/// <summary>
/// Apply entity configuration
/// </summary>
/// <param name="table">Create table expression builder</param>
public override void MapEntity(CreateTableExpressionBuilder table)
{
table
.WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.ShippingMethodId)))
.AsInt32().PrimaryKey().ForeignKey<ShippingMethod>()
.WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.CountryId)))
.AsInt32().PrimaryKey().ForeignKey<Country>().OnDelete(System.Data.Rule.None)
.WithColumn(NameCompatibilityManager.GetColumnName(typeof(ShippingMethodStateProvinceMapping), nameof(ShippingMethodStateProvinceMapping.StateProvinceId)))
.AsInt32().PrimaryKey().ForeignKey<StateProvince>();
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public override void Up()
Create.TableFor<ShipmentItem>();
Create.TableFor<ShippingMethod>();
Create.TableFor<ShippingMethodCountryMapping>();
Create.TableFor<ShippingMethodStateProvinceMapping>();
Create.TableFor<ProductWarehouseInventory>();
Create.TableFor<StockQuantityHistory>();
Create.TableFor<Download>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Nop.Core.Domain.Shipping;
using Nop.Services.Caching;

namespace Nop.Services.Shipping.Caching;

/// <summary>
/// Represents a shipping method-state/province mapping cache event consumer
/// </summary>
public partial class ShippingMethodStateProvinceMappingCacheEventConsumer : CacheEventConsumer<ShippingMethodStateProvinceMapping>
{
}
40 changes: 39 additions & 1 deletion src/Libraries/Nop.Services/Shipping/IShippingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ public partial interface IShippingService
/// Gets all shipping methods
/// </summary>
/// <param name="filterByCountryId">The country identifier to filter by</param>
/// <param name="filterByStateProvinceId">The state province identifier to filter by</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the shipping methods
/// </returns>
Task<IList<ShippingMethod>> GetAllShippingMethodsAsync(int? filterByCountryId = null);
Task<IList<ShippingMethod>> GetAllShippingMethodsAsync(int? filterByCountryId = null, int? filterByStateProvinceId = null);

/// <summary>
/// Inserts a shipping method
Expand Down Expand Up @@ -91,6 +92,43 @@ public partial interface IShippingService
/// <returns>A task that represents the asynchronous operation</returns>
Task DeleteShippingMethodCountryMappingAsync(ShippingMethodCountryMapping shippingMethodCountryMapping);

/// <summary>
/// Does state province restriction exist
/// </summary>
/// <param name="shippingMethod">Shipping method</param>
/// <param name="stateProvinceId">State province identifier</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the result
/// </returns>
Task<bool> StateProvinceRestrictionExistsAsync(ShippingMethod shippingMethod, int countryId, int stateProvinceId);

/// <summary>
/// Gets shipping state province mappings
/// </summary>
/// <param name="shippingMethodId">The shipping method identifier</param>
/// <param name="countryId">Country identifier</param>
/// <param name="stateProvinceId">State province identifier</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the shipping state province mappings
/// </returns>
Task<IList<ShippingMethodStateProvinceMapping>> GetShippingMethodStateProvinceMappingAsync(int shippingMethodId, int countryId, int stateProvinceId);

/// <summary>
/// Inserts a shipping state province mapping
/// </summary>
/// <param name="shippingMethodStateProvinceMapping">Shipping state province mapping</param>
/// <returns>A task that represents the asynchronous operation</returns>
Task InsertShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping);

/// <summary>
/// Delete the shipping state province mapping
/// </summary>
/// <param name="shippingMethodStateProvinceMapping">Shipping state province mapping</param>
/// <returns>A task that represents the asynchronous operation</returns>
Task DeleteShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping);

#endregion

#region Warehouses
Expand Down
3 changes: 2 additions & 1 deletion src/Libraries/Nop.Services/Shipping/NopShippingDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public static partial class NopShippingDefaults
/// </summary>
/// <remarks>
/// {0} : country identifier
/// {1} : state province identifier
/// </remarks>
public static CacheKey ShippingMethodsAllCacheKey => new("Nop.shippingmethod.all.{0}", NopEntityCacheDefaults<ShippingMethod>.AllPrefix);
public static CacheKey ShippingMethodsAllCacheKey => new("Nop.shippingmethod.all.{0}.{1}", NopEntityCacheDefaults<ShippingMethod>.AllPrefix);

#endregion
}
121 changes: 108 additions & 13 deletions src/Libraries/Nop.Services/Shipping/ShippingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public partial class ShippingService : IShippingService
protected readonly IStoreContext _storeContext;
protected readonly ShippingSettings _shippingSettings;
protected readonly ShoppingCartSettings _shoppingCartSettings;
private readonly IRepository<ShippingMethodStateProvinceMapping> _shippingMethodStateProvinceMappingRepository;

#endregion

Expand All @@ -65,7 +66,8 @@ public ShippingService(IAddressService addressService,
IStateProvinceService stateProvinceService,
IStoreContext storeContext,
ShippingSettings shippingSettings,
ShoppingCartSettings shoppingCartSettings)
ShoppingCartSettings shoppingCartSettings,
IRepository<ShippingMethodStateProvinceMapping> shippingMethodStateProvinceMappingRepository)
{
_addressService = addressService;
_checkoutAttributeParser = checkoutAttributeParser;
Expand All @@ -86,6 +88,7 @@ public ShippingService(IAddressService addressService,
_storeContext = storeContext;
_shippingSettings = shippingSettings;
_shoppingCartSettings = shoppingCartSettings;
_shippingMethodStateProvinceMappingRepository = shippingMethodStateProvinceMappingRepository;
}

#endregion
Expand Down Expand Up @@ -208,37 +211,67 @@ public virtual async Task<ShippingMethod> GetShippingMethodByIdAsync(int shippin
/// Gets all shipping methods
/// </summary>
/// <param name="filterByCountryId">The country identifier to filter by</param>
/// <param name="filterByStateProvinceId">The state province identifier to filter by</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the shipping methods
/// </returns>
public virtual async Task<IList<ShippingMethod>> GetAllShippingMethodsAsync(int? filterByCountryId = null)
public virtual async Task<IList<ShippingMethod>> 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);
}

Expand Down Expand Up @@ -319,6 +352,68 @@ public virtual async Task DeleteShippingMethodCountryMappingAsync(ShippingMethod
await _shippingMethodCountryMappingRepository.DeleteAsync(shippingMethodCountryMapping);
}

/// <summary>
/// Does state province restriction exist
/// </summary>
/// <param name="shippingMethod">Shipping method</param>
/// <param name="stateProvinceId">State province identifier</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the result
/// </returns>
public virtual async Task<bool> 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;
}

/// <summary>
/// Gets shipping state province mappings
/// </summary>
/// <param name="shippingMethodId">The shipping method identifier</param>
/// <param name="countryId">Country identifier</param>
/// <param name="stateProvinceId">State province identifier</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the shipping state province mappings
/// </returns>
public virtual async Task<IList<ShippingMethodStateProvinceMapping>> 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();
}

/// <summary>
/// Inserts a shipping state province mapping
/// </summary>
/// <param name="shippingMethodStateProvinceMapping">Shipping state province mapping</param>
/// <returns>A task that represents the asynchronous operation</returns>
public virtual async Task InsertShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping)
{
await _shippingMethodStateProvinceMappingRepository.InsertAsync(shippingMethodStateProvinceMapping);
}

/// <summary>
/// Delete the shipping state province mapping
/// </summary>
/// <param name="shippingMethodStateProvinceMapping">Shipping state province mapping</param>
/// <returns>A task that represents the asynchronous operation</returns>
public virtual async Task DeleteShippingMethodStateProvinceMappingAsync(ShippingMethodStateProvinceMapping shippingMethodStateProvinceMapping)
{
await _shippingMethodStateProvinceMappingRepository.DeleteAsync(shippingMethodStateProvinceMapping);
}

#endregion

#region Warehouses
Expand Down Expand Up @@ -359,8 +454,8 @@ public virtual async Task<IList<Warehouse>> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public async Task<GetShippingOptionResponse> 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;
Expand Down Expand Up @@ -194,7 +194,8 @@ public async Task<GetShippingOptionResponse> 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),
Expand Down Expand Up @@ -223,7 +224,8 @@ public async Task<GetShippingOptionResponse> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading