diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/MerchandiseReturnService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/MerchandiseReturnService.cs index 8d16ae9b4..697e12501 100644 --- a/src/Business/Grand.Business.Checkout/Services/Orders/MerchandiseReturnService.cs +++ b/src/Business/Grand.Business.Checkout/Services/Orders/MerchandiseReturnService.cs @@ -117,15 +117,20 @@ public virtual async Task> SearchMerchandiseReturn /// /// Gets all merchandise return actions /// + /// Store identifier; empty to load all entries /// Merchandise return actions - public virtual async Task> GetAllMerchandiseReturnActions() + public virtual async Task> GetAllMerchandiseReturnActions(string storeId = "") { - return await _cacheBase.GetAsync(CacheKey.MERCHANDISE_RETURN_ACTIONS_ALL_KEY, async () => + var key = string.Format(CacheKey.MERCHANDISE_RETURN_ACTIONS_ALL_KEY, storeId); + return await _cacheBase.GetAsync(key, async () => { var query = from rra in _merchandiseReturnActionRepository.Table orderby rra.DisplayOrder select rra; - return await Task.FromResult(query.ToList()); + var actions = query.ToList(); + if (!string.IsNullOrEmpty(storeId)) + actions = actions.Where(x => !x.LimitedToStores || x.Stores.Contains(storeId)).ToList(); + return await Task.FromResult(actions); }); } @@ -203,7 +208,7 @@ public virtual async Task InsertMerchandiseReturnAction(MerchandiseReturnAction await _mediator.EntityInserted(merchandiseReturnAction); //clear cache - await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_ACTIONS_ALL_KEY); + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_ACTIONS_PATTERN_KEY); } /// @@ -220,7 +225,7 @@ public virtual async Task UpdateMerchandiseReturnAction(MerchandiseReturnAction await _mediator.EntityUpdated(merchandiseReturnAction); //clear cache - await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_ACTIONS_ALL_KEY); + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_ACTIONS_PATTERN_KEY); } /// @@ -235,6 +240,9 @@ public virtual async Task DeleteMerchandiseReturnAction(MerchandiseReturnAction //event notification await _mediator.EntityDeleted(merchandiseReturnAction); + + //clear cache + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_ACTIONS_PATTERN_KEY); } /// @@ -251,21 +259,26 @@ public virtual async Task DeleteMerchandiseReturnReason(MerchandiseReturnReason await _mediator.EntityDeleted(merchandiseReturnReason); //clear cache - await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_ALL_KEY); + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_PATTERN_KEY); } /// /// Gets all merchandise return reasons /// + /// Store identifier; empty to load all entries /// Merchandise return reasons - public virtual async Task> GetAllMerchandiseReturnReasons() + public virtual async Task> GetAllMerchandiseReturnReasons(string storeId = "") { - return await _cacheBase.GetAsync(CacheKey.MERCHANDISE_RETURN_REASONS_ALL_KEY, async () => + var key = string.Format(CacheKey.MERCHANDISE_RETURN_REASONS_ALL_KEY, storeId); + return await _cacheBase.GetAsync(key, async () => { var query = from rra in _merchandiseReturnReasonRepository.Table orderby rra.DisplayOrder select rra; - return await Task.FromResult(query.ToList()); + var reasons = query.ToList(); + if (!string.IsNullOrEmpty(storeId)) + reasons = reasons.Where(x => !x.LimitedToStores || x.Stores.Contains(storeId)).ToList(); + return await Task.FromResult(reasons); }); } @@ -293,7 +306,7 @@ public virtual async Task InsertMerchandiseReturnReason(MerchandiseReturnReason await _mediator.EntityInserted(merchandiseReturnReason); //clear cache - await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_ALL_KEY); + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_PATTERN_KEY); } /// @@ -310,7 +323,7 @@ public virtual async Task UpdateMerchandiseReturnReason(MerchandiseReturnReason await _mediator.EntityUpdated(merchandiseReturnReason); //clear cache - await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_ALL_KEY); + await _cacheBase.RemoveByPrefix(CacheKey.MERCHANDISE_RETURN_REASONS_PATTERN_KEY); } #region Merchandise return notes diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IMerchandiseReturnService.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IMerchandiseReturnService.cs index 711d1ea93..7cdb0a88f 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IMerchandiseReturnService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IMerchandiseReturnService.cs @@ -62,8 +62,9 @@ Task> SearchMerchandiseReturns(string storeId = "" /// /// Gets all merchandise return actions /// + /// Store identifier; empty to load all entries /// Merchandise return actions - Task> GetAllMerchandiseReturnActions(); + Task> GetAllMerchandiseReturnActions(string storeId = ""); /// /// Gets a merchandise return action @@ -93,8 +94,9 @@ Task> SearchMerchandiseReturns(string storeId = "" /// /// Gets all merchandise return reasons /// + /// Store identifier; empty to load all entries /// Merchandise return reasons - Task> GetAllMerchandiseReturnReasons(); + Task> GetAllMerchandiseReturnReasons(string storeId = ""); /// /// Gets a merchandise return reasons diff --git a/src/Core/Grand.Domain/Orders/MerchandiseReturnAction.cs b/src/Core/Grand.Domain/Orders/MerchandiseReturnAction.cs index e3fcc132b..935b34e12 100644 --- a/src/Core/Grand.Domain/Orders/MerchandiseReturnAction.cs +++ b/src/Core/Grand.Domain/Orders/MerchandiseReturnAction.cs @@ -1,11 +1,12 @@ using Grand.Domain.Localization; +using Grand.Domain.Stores; namespace Grand.Domain.Orders; /// /// Represents a merchandise return action /// -public class MerchandiseReturnAction : BaseEntity, ITranslationEntity +public class MerchandiseReturnAction : BaseEntity, ITranslationEntity, IStoreLinkEntity { /// /// Gets or sets the name @@ -21,4 +22,14 @@ public class MerchandiseReturnAction : BaseEntity, ITranslationEntity /// Gets or sets the collection of locales /// public IList Locales { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets or sets the stores + /// + public IList Stores { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Core/Grand.Domain/Orders/MerchandiseReturnReason.cs b/src/Core/Grand.Domain/Orders/MerchandiseReturnReason.cs index 3374141c2..765c9a081 100644 --- a/src/Core/Grand.Domain/Orders/MerchandiseReturnReason.cs +++ b/src/Core/Grand.Domain/Orders/MerchandiseReturnReason.cs @@ -1,11 +1,12 @@ using Grand.Domain.Localization; +using Grand.Domain.Stores; namespace Grand.Domain.Orders; /// /// Represents a merchandise return reason /// -public class MerchandiseReturnReason : BaseEntity, ITranslationEntity +public class MerchandiseReturnReason : BaseEntity, ITranslationEntity, IStoreLinkEntity { /// /// Gets or sets the name @@ -21,4 +22,14 @@ public class MerchandiseReturnReason : BaseEntity, ITranslationEntity /// Gets or sets the collection of locales /// public IList Locales { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets or sets the stores + /// + public IList Stores { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Core/Grand.Infrastructure/Caching/Constants/MerchandiseReturnCacheKey.cs b/src/Core/Grand.Infrastructure/Caching/Constants/MerchandiseReturnCacheKey.cs index a5d206b8b..d30d29bd2 100644 --- a/src/Core/Grand.Infrastructure/Caching/Constants/MerchandiseReturnCacheKey.cs +++ b/src/Core/Grand.Infrastructure/Caching/Constants/MerchandiseReturnCacheKey.cs @@ -3,12 +3,22 @@ public static partial class CacheKey { /// - /// Key for all merchandise return reason + /// Key for all merchandise return reasons. {0} - store ID /// - public static string MERCHANDISE_RETURN_REASONS_ALL_KEY => "Grand.merchandisereturn.reasons.all"; + public static string MERCHANDISE_RETURN_REASONS_ALL_KEY => "Grand.merchandisereturn.reasons.all-{0}"; /// - /// Key for all merchandise return actions + /// Key pattern to clear merchandise return reasons cache /// - public static string MERCHANDISE_RETURN_ACTIONS_ALL_KEY => "Grand.merchandisereturn.actions.all"; + public static string MERCHANDISE_RETURN_REASONS_PATTERN_KEY => "Grand.merchandisereturn.reasons"; + + /// + /// Key for all merchandise return actions. {0} - store ID + /// + public static string MERCHANDISE_RETURN_ACTIONS_ALL_KEY => "Grand.merchandisereturn.actions.all-{0}"; + + /// + /// Key pattern to clear merchandise return actions cache + /// + public static string MERCHANDISE_RETURN_ACTIONS_PATTERN_KEY => "Grand.merchandisereturn.actions"; } \ No newline at end of file diff --git a/src/Modules/Grand.Module.Installer/Utilities/StandardAdminSiteMap.cs b/src/Modules/Grand.Module.Installer/Utilities/StandardAdminSiteMap.cs index 15297f172..40c6721f3 100644 --- a/src/Modules/Grand.Module.Installer/Utilities/StandardAdminSiteMap.cs +++ b/src/Modules/Grand.Module.Installer/Utilities/StandardAdminSiteMap.cs @@ -967,6 +967,7 @@ public static class StandardAdminSiteMap new() { SystemName = "Vendor settings", ResourceName = "Admin.Settings.Vendor", + PermissionNames = new List { PermissionSystemName.System }, ControllerName = "Setting", ActionName = "Vendor", DisplayOrder = 6, @@ -975,6 +976,7 @@ public static class StandardAdminSiteMap new() { SystemName = "Push notifications settings", ResourceName = "Admin.Settings.PushNotifications", + PermissionNames = new List { PermissionSystemName.System }, ControllerName = "Setting", ActionName = "PushNotifications", DisplayOrder = 7, @@ -983,6 +985,7 @@ public static class StandardAdminSiteMap new() { SystemName = "Admin search settings", ResourceName = "Admin.Settings.AdminSearch", + PermissionNames = new List { PermissionSystemName.System }, ControllerName = "Setting", ActionName = "AdminSearch", DisplayOrder = 8, @@ -991,6 +994,7 @@ public static class StandardAdminSiteMap new() { SystemName = "System settings", ResourceName = "Admin.Settings.System", + PermissionNames = new List { PermissionSystemName.System }, ControllerName = "Setting", ActionName = "SystemSetting", DisplayOrder = 9, diff --git a/src/Modules/Grand.Module.Migration/Migrations/2.4/MigrationUpdateAdminSiteMap.cs b/src/Modules/Grand.Module.Migration/Migrations/2.4/MigrationUpdateAdminSiteMap.cs new file mode 100644 index 000000000..44ee35887 --- /dev/null +++ b/src/Modules/Grand.Module.Migration/Migrations/2.4/MigrationUpdateAdminSiteMap.cs @@ -0,0 +1,54 @@ +using Grand.Data; +using Grand.Domain.Admin; +using Grand.Domain.Permissions; +using Grand.Infrastructure.Migrations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Grand.Module.Migration.Migrations._2._4; + +public class MigrationUpdateAdminSiteMap : IMigration +{ + public int Priority => 1; + public DbVersion Version => new(2, 4); + public Guid Identity => new("B7C3D8E2-F1A4-4B9E-8C62-197E3F5A4D21"); + public string Name => "Update standard admin site map - restrict Push notifications, Admin search, System settings to System permission"; + + /// + /// Upgrade process + /// + /// + /// + public bool UpgradeProcess(IServiceProvider serviceProvider) + { + var repository = serviceProvider.GetRequiredService>(); + var logService = serviceProvider.GetRequiredService>(); + + try + { + var sitemapSettings = repository.Table.FirstOrDefault(x => x.SystemName == "Settings"); + if (sitemapSettings == null) return true; + + var adminOnlyItems = new[] { + "Push notifications settings", + "Admin search settings", + "System settings" + }; + + foreach (var itemName in adminOnlyItems) + { + var item = sitemapSettings.ChildNodes.FirstOrDefault(x => x.SystemName == itemName); + if (item != null && !item.PermissionNames.Contains(PermissionSystemName.System)) + item.PermissionNames.Add(PermissionSystemName.System); + } + + repository.Update(sitemapSettings); + } + catch (InvalidOperationException ex) + { + logService.LogError(ex, "UpgradeProcess - UpdateAdminSiteMap (2.4)"); + } + + return true; + } +} diff --git a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml index 8f4a54b9b..cfd0f0c3c 100644 --- a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml +++ b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml @@ -40,5 +40,12 @@ +
+ +
+ + +
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml index dc3f9fd33..3793e673e 100644 --- a/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml +++ b/src/Web/Grand.Web.Admin/Areas/Admin/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml @@ -40,5 +40,12 @@ +
+ +
+ + +
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Admin/Controllers/SettingController.cs b/src/Web/Grand.Web.Admin/Controllers/SettingController.cs index 0020c1c5f..e5e682b73 100644 --- a/src/Web/Grand.Web.Admin/Controllers/SettingController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/SettingController.cs @@ -306,7 +306,8 @@ public async Task MerchandiseReturnReasonList() [HttpPost] public async Task MerchandiseReturnReasonList(DataSourceRequest command) { - var reasons = await merchandiseReturnService.GetAllMerchandiseReturnReasons(); + var storeId = await GetActiveStore(); + var reasons = await merchandiseReturnService.GetAllMerchandiseReturnReasons(storeId); var gridModel = new DataSourceResult { Data = reasons.Select(x => x.ToModel()), Total = reasons.Count @@ -317,7 +318,10 @@ public async Task MerchandiseReturnReasonList(DataSourceRequest c //create public async Task MerchandiseReturnReasonCreate() { - var model = new MerchandiseReturnReasonModel(); + var storeId = await GetActiveStore(); + var model = new MerchandiseReturnReasonModel { + Stores = !string.IsNullOrEmpty(storeId) ? [storeId] : [] + }; //locales await AddLocales(languageService, model.Locales); return View(model); @@ -417,7 +421,8 @@ public async Task MerchandiseReturnActionList() [HttpPost] public async Task MerchandiseReturnActionList(DataSourceRequest command) { - var actions = await merchandiseReturnService.GetAllMerchandiseReturnActions(); + var storeId = await GetActiveStore(); + var actions = await merchandiseReturnService.GetAllMerchandiseReturnActions(storeId); var gridModel = new DataSourceResult { Data = actions.Select(x => x.ToModel()), Total = actions.Count @@ -428,7 +433,10 @@ public async Task MerchandiseReturnActionList(DataSourceRequest c //create public async Task MerchandiseReturnActionCreate() { - var model = new MerchandiseReturnActionModel(); + var storeId = await GetActiveStore(); + var model = new MerchandiseReturnActionModel { + Stores = !string.IsNullOrEmpty(storeId) ? [storeId] : [] + }; //locales await AddLocales(languageService, model.Locales); return View(model); diff --git a/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnActionProfile.cs b/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnActionProfile.cs index e03021761..b5632d1fc 100644 --- a/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnActionProfile.cs +++ b/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnActionProfile.cs @@ -11,10 +11,13 @@ public class MerchandiseReturnActionProfile : Profile, IAutoMapperProfile public MerchandiseReturnActionProfile() { CreateMap() - .ForMember(dest => dest.Locales, mo => mo.Ignore()); + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.Stores, mo => mo.MapFrom(src => src.Stores.ToArray())); CreateMap() .ForMember(dest => dest.Locales, mo => mo.MapFrom(x => x.Locales.ToTranslationProperty())) - .ForMember(dest => dest.Id, mo => mo.Ignore()); + .ForMember(dest => dest.Id, mo => mo.Ignore()) + .ForMember(dest => dest.LimitedToStores, mo => mo.MapFrom(x => x.Stores != null && x.Stores.Any())) + .ForMember(dest => dest.Stores, mo => mo.MapFrom(x => x.Stores != null ? x.Stores.ToList() : new List())); } public int Order => 0; diff --git a/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnReasonProfile.cs b/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnReasonProfile.cs index 607a3cbf5..67b6a8b90 100644 --- a/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnReasonProfile.cs +++ b/src/Web/Grand.Web.AdminShared/Mapper/MerchandiseReturnReasonProfile.cs @@ -11,10 +11,13 @@ public class MerchandiseReturnReasonProfile : Profile, IAutoMapperProfile public MerchandiseReturnReasonProfile() { CreateMap() - .ForMember(dest => dest.Locales, mo => mo.Ignore()); + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.Stores, mo => mo.MapFrom(src => src.Stores.ToArray())); CreateMap() .ForMember(dest => dest.Locales, mo => mo.MapFrom(x => x.Locales.ToTranslationProperty())) - .ForMember(dest => dest.Id, mo => mo.Ignore()); + .ForMember(dest => dest.Id, mo => mo.Ignore()) + .ForMember(dest => dest.LimitedToStores, mo => mo.MapFrom(x => x.Stores != null && x.Stores.Any())) + .ForMember(dest => dest.Stores, mo => mo.MapFrom(x => x.Stores != null ? x.Stores.ToList() : new List())); } public int Order => 0; diff --git a/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnActionModel.cs b/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnActionModel.cs index 4a22dc1dc..6faed1733 100644 --- a/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnActionModel.cs +++ b/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnActionModel.cs @@ -1,10 +1,13 @@ using Grand.Infrastructure.ModelBinding; using Grand.Infrastructure.Models; +using Grand.Web.Common.Link; using Grand.Web.Common.Models; +using System.ComponentModel.DataAnnotations; namespace Grand.Web.AdminShared.Models.Settings; -public class MerchandiseReturnActionModel : BaseEntityModel, ILocalizedModel +public class MerchandiseReturnActionModel : BaseEntityModel, ILocalizedModel, + IStoreLinkModel { [GrandResourceDisplayName("Admin.Settings.Order.MerchandiseReturnActions.Name")] @@ -15,6 +18,10 @@ public class MerchandiseReturnActionModel : BaseEntityModel, ILocalizedModel Locales { get; set; } = new List(); + + [GrandResourceDisplayName("Admin.Settings.Order.MerchandiseReturnActions.LimitedToStores")] + [UIHint("Stores")] + public string[] Stores { get; set; } } public class MerchandiseReturnActionLocalizedModel : ILocalizedModelLocal diff --git a/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnReasonModel.cs b/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnReasonModel.cs index f375060d8..0a2c5219f 100644 --- a/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnReasonModel.cs +++ b/src/Web/Grand.Web.AdminShared/Models/Settings/MerchandiseReturnReasonModel.cs @@ -1,10 +1,13 @@ using Grand.Infrastructure.ModelBinding; using Grand.Infrastructure.Models; +using Grand.Web.Common.Link; using Grand.Web.Common.Models; +using System.ComponentModel.DataAnnotations; namespace Grand.Web.AdminShared.Models.Settings; -public class MerchandiseReturnReasonModel : BaseEntityModel, ILocalizedModel +public class MerchandiseReturnReasonModel : BaseEntityModel, ILocalizedModel, + IStoreLinkModel { [GrandResourceDisplayName("Admin.Settings.Order.MerchandiseReturnReasons.Name")] @@ -15,6 +18,10 @@ public class MerchandiseReturnReasonModel : BaseEntityModel, ILocalizedModel Locales { get; set; } = new List(); + + [GrandResourceDisplayName("Admin.Settings.Order.MerchandiseReturnReasons.LimitedToStores")] + [UIHint("Stores")] + public string[] Stores { get; set; } } public class MerchandiseReturnReasonLocalizedModel : ILocalizedModelLocal diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Catalog.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Catalog.cshtml new file mode 100644 index 000000000..24bdc1b67 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Catalog.cshtml @@ -0,0 +1,81 @@ +@model CatalogSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.Catalog"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Catalog"] +
+
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Content.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Content.cshtml new file mode 100644 index 000000000..f957222cb --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Content.cshtml @@ -0,0 +1,53 @@ +@model ContentSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.Content"]; +} +
+
+
+
+
+
+
+ + @Loc["Admin.Settings.Content"] +
+
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Customer.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Customer.cshtml new file mode 100644 index 000000000..3685188ce --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Customer.cshtml @@ -0,0 +1,60 @@ +@model CustomerSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.Customer"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Customer"] +
+
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/GeneralCommon.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/GeneralCommon.cshtml new file mode 100644 index 000000000..7ba42bc1e --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/GeneralCommon.cshtml @@ -0,0 +1,67 @@ +@model GeneralCommonSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.GeneralCommon"]; +} +
+
+
+
+
+
+
+ + @Loc["Admin.Settings.GeneralCommon"] +
+
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Media.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Media.cshtml new file mode 100644 index 000000000..bdacef702 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Media.cshtml @@ -0,0 +1,242 @@ +@model MediaSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.Media"]; +} +
+
+
+
+
+
+
+ + @Loc["Admin.Settings.Media"] +
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionCreate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionCreate.cshtml new file mode 100644 index 000000000..becefb3a1 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionCreate.cshtml @@ -0,0 +1,33 @@ +@model MerchandiseReturnActionModel +@{ + ViewBag.Title = Loc["Admin.Settings.Order.MerchandiseReturnActions.AddNew"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Order.MerchandiseReturnActions.AddNew"] + + + @Loc["Admin.Settings.Order.MerchandiseReturnActions.BackToList"] + +
+
+ + +
+
+
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionEdit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionEdit.cshtml new file mode 100644 index 000000000..fd398c521 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnActionEdit.cshtml @@ -0,0 +1,39 @@ +@model MerchandiseReturnActionModel +@{ + ViewBag.Title = Loc["Admin.Settings.Order.MerchandiseReturnActions.EditDetails"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Order.MerchandiseReturnActions.EditDetails"] - @Model.Name + + + @Loc["Admin.Settings.Order.MerchandiseReturnActions.BackToList"] + +
+
+
+ + + + @Loc["Admin.Common.Delete"] + +
+
+
+
+ +
+
+
+
+
+ diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonCreate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonCreate.cshtml new file mode 100644 index 000000000..01fd1ab81 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonCreate.cshtml @@ -0,0 +1,33 @@ +@model MerchandiseReturnReasonModel +@{ + ViewBag.Title = Loc["Admin.Settings.Order.MerchandiseReturnReasons.AddNew"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Order.MerchandiseReturnReasons.AddNew"] + + + @Loc["Admin.Settings.Order.MerchandiseReturnReasons.BackToList"] + +
+
+ + +
+
+
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonEdit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonEdit.cshtml new file mode 100644 index 000000000..5bae9f869 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/MerchandiseReturnReasonEdit.cshtml @@ -0,0 +1,39 @@ +@model MerchandiseReturnReasonModel +@{ + ViewBag.Title = Loc["Admin.Settings.Order.MerchandiseReturnReasons.EditDetails"]; +} +
+
+
+
+
+
+ + @Loc["Admin.Settings.Order.MerchandiseReturnReasons.EditDetails"] - @Model.Name + + + @Loc["Admin.Settings.Order.MerchandiseReturnReasons.BackToList"] + +
+
+
+ + + + @Loc["Admin.Common.Delete"] + +
+
+
+
+ +
+
+
+
+
+ diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabCompareProducts.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabCompareProducts.cshtml new file mode 100644 index 000000000..960b0a9dd --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabCompareProducts.cshtml @@ -0,0 +1,42 @@ +@model CatalogSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabGeneralSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabGeneralSettings.cshtml new file mode 100644 index 000000000..2e04d9a56 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabGeneralSettings.cshtml @@ -0,0 +1,710 @@ +@model CatalogSettingsModel + + +
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ @**@ + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabPerformance.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabPerformance.cshtml new file mode 100644 index 000000000..259842d46 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabPerformance.cshtml @@ -0,0 +1,66 @@ +@model CatalogSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabProductReviews.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabProductReviews.cshtml new file mode 100644 index 000000000..648069ef3 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabProductReviews.cshtml @@ -0,0 +1,84 @@ +@model CatalogSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSearchSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSearchSettings.cshtml new file mode 100644 index 000000000..74c484cb0 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSearchSettings.cshtml @@ -0,0 +1,146 @@ +@model CatalogSettingsModel + + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSharing.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSharing.cshtml new file mode 100644 index 000000000..54f53c541 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSharing.cshtml @@ -0,0 +1,44 @@ +@model CatalogSettingsModel + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSortOptions.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSortOptions.cshtml new file mode 100644 index 000000000..92995b94f --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Catalog.TabSortOptions.cshtml @@ -0,0 +1,96 @@ +@model CatalogSettingsModel + +
+
+
+
+
+ + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabBlogSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabBlogSettings.cshtml new file mode 100644 index 000000000..d81b35654 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabBlogSettings.cshtml @@ -0,0 +1,101 @@ +@model ContentSettingsModel +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabKnowledgebaseSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabKnowledgebaseSettings.cshtml new file mode 100644 index 000000000..e8a16fb1c --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabKnowledgebaseSettings.cshtml @@ -0,0 +1,42 @@ +@model ContentSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabNewsSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabNewsSettings.cshtml new file mode 100644 index 000000000..1defad5c8 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Content.TabNewsSettings.cshtml @@ -0,0 +1,72 @@ +@model ContentSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml new file mode 100644 index 000000000..45ae666d7 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnAction.cshtml @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model MerchandiseReturnActionModel + +
+ + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} + +
+ +
+
+ +
+ + +
+
+
+
+ +
+
+ +
+ + +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml new file mode 100644 index 000000000..df85b8780 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/CreateOrUpdate.MerchandiseReturnReason.cshtml @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model MerchandiseReturnReasonModel + +
+ + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} + +
+ +
+
+ +
+ + +
+
+
+
+ +
+
+ +
+ + +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabAddressFormFields.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabAddressFormFields.cshtml new file mode 100644 index 000000000..885fd3465 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabAddressFormFields.cshtml @@ -0,0 +1,356 @@ +@model CustomerSettingsModel + +
+
+
+
+ @Loc["Admin.Settings.Customer.AddressFormFields.Description"] +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerFormFields.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerFormFields.cshtml new file mode 100644 index 000000000..721872c51 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerFormFields.cshtml @@ -0,0 +1,438 @@ +@model CustomerSettingsModel + +
+
+
+
+ @Loc["Admin.Settings.Customer.CustomerFormFields.Description"] +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml new file mode 100644 index 000000000..1d6321c51 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml @@ -0,0 +1,145 @@ +@using Grand.Domain.Customers +@model CustomerSettingsModel + + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ You can use this regex:
+ ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@@$%^&*-]).{8,}$
+ This regex will enforce these rules:
+ • At least one upper case english letter
+ • At least one lower case english letter
+ • At least one digit
+ • At least one special character
+ • Minimum 8 in length +
+
+ +
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSettings.cshtml new file mode 100644 index 000000000..6bd6facfa --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSettings.cshtml @@ -0,0 +1,304 @@ +@using Grand.Domain.Customers +@model CustomerSettingsModel + + +
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabMenuSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabMenuSettings.cshtml new file mode 100644 index 000000000..60ebca75d --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabMenuSettings.cshtml @@ -0,0 +1,78 @@ +@model GeneralCommonSettingsModel +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabPdfSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabPdfSettings.cshtml new file mode 100644 index 000000000..62c1abf61 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabPdfSettings.cshtml @@ -0,0 +1,45 @@ +@model GeneralCommonSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSEOSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSEOSettings.cshtml new file mode 100644 index 000000000..3999d88ff --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSEOSettings.cshtml @@ -0,0 +1,187 @@ +@model GeneralCommonSettingsModel + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ @Loc["Admin.Settings.GeneralCommon.SeoCharConversion.Note"] +
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+ @{ + ViewData["Reference"] = "Settings"; + ViewData["ObjectId"] = "SeoSettings.StorePicture"; + } +
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSecuritySettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSecuritySettings.cshtml new file mode 100644 index 000000000..c29b4d31e --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabSecuritySettings.cshtml @@ -0,0 +1,287 @@ +@model GeneralCommonSettingsModel + + +
+
+
+
+ +
+
+ + +
+
+
+
+ A CAPTCHA is a program that can tell whether its user is a human or a computer. + You've probably seen them — colorful images with distorted text at the bottom of + Web registration forms. CAPTCHAs are used by many websites to prevent abuse from + "bots," or automated programs usually written to generate spam. No computer program + can read distorted text as well as humans can, so bots cannot navigate sites protected + by CAPTCHAs. reCAPTCHA. +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabStoreInformationSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabStoreInformationSettings.cshtml new file mode 100644 index 000000000..bee83c203 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/GeneralCommon.TabStoreInformationSettings.cshtml @@ -0,0 +1,341 @@ +@model GeneralCommonSettingsModel + + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+
+
    + @foreach (var theme in Model.StoreInformationSettings.AvailableStoreThemes) + { +
  • +

    + checked="checked" + }> +

    +

    + +

    +
  • + } +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnActions.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnActions.cshtml new file mode 100644 index 000000000..52b04d48a --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnActions.cshtml @@ -0,0 +1,65 @@ +
+
+

+

@Loc["Admin.Settings.Order.MerchandiseReturnActions"]

+

+
+
+
+
+ +
+ + + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnReasons.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnReasons.cshtml new file mode 100644 index 000000000..2ab85d502 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/MerchandiseReturnReasons.cshtml @@ -0,0 +1,64 @@ +
+
+

+

@Loc["Admin.Settings.Order.MerchandiseReturnReasons"]

+

+
+
+
+
+ +
+ + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml new file mode 100644 index 000000000..c71b24ede --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml @@ -0,0 +1,120 @@ +@model SalesSettingsModel + + +
+
+
+
+ @Loc["Admin.Settings.LoyaltyPoints.Description"] +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ @Loc["Admin.Settings.LoyaltyPoints.ExchangeRate.Hint2"] + + @Model.LoyaltyPointsSettings.PrimaryStoreCurrencyCode + +
+
+
+
+ +
+
+ + +
+
+
+
+ @Loc["Admin.Settings.LoyaltyPoints.Earning"] +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ @Loc["Admin.Settings.LoyaltyPoints.Earning.Hint1"] + + @Model.LoyaltyPointsSettings.PrimaryStoreCurrencyCode +   + @Loc["Admin.Settings.LoyaltyPoints.Earning.Hint2"] + + @Loc["Admin.Settings.LoyaltyPoints.Earning.Hint3"] + + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabMerchandiseReturn.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabMerchandiseReturn.cshtml new file mode 100644 index 000000000..fd7dc01e4 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabMerchandiseReturn.cshtml @@ -0,0 +1,72 @@ +@model SalesSettingsModel + +
+
+
+
+ @Loc["Admin.Settings.Order.MerchandiseReturnsDescription1"] +
+ @Loc["Admin.Settings.Order.MerchandiseReturnsDescription2"] +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabOrderSettings.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabOrderSettings.cshtml new file mode 100644 index 000000000..a6f813c7d --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabOrderSettings.cshtml @@ -0,0 +1,266 @@ +@model SalesSettingsModel +
+
+
+
+ +
+
+ + +
+
+
+ @Loc["Admin.Settings.Orders.MinOrderSubtotalAmount.Note"] +
+
+
+ +
+
+ + @Model.OrderSettings.PrimaryStoreCurrencyCode +
+
+
+
+ +
+
+ + @Model.OrderSettings.PrimaryStoreCurrencyCode +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabShoppingCart.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabShoppingCart.cshtml new file mode 100644 index 000000000..fdef08894 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabShoppingCart.cshtml @@ -0,0 +1,305 @@ +@model SalesSettingsModel + + +
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Sales.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Sales.cshtml new file mode 100644 index 000000000..6a0a35773 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Sales.cshtml @@ -0,0 +1,60 @@ +@model SalesSettingsModel +@{ + ViewBag.Title = Loc["Admin.Settings.Sales"]; +} +
+
+
+
+
+
+
+ + @Loc["Admin.Settings.Sales"] +
+
+
+ +
+
+
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml index af197fbf1..0d49682e6 100644 --- a/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml @@ -33,6 +33,7 @@ @using Grand.Web.AdminShared.Models.Blogs @using Grand.Web.AdminShared.Models.Messages @using Grand.Web.AdminShared.Models.Pages +@using Grand.Web.AdminShared.Models.Settings @inject LocService Loc @inject IEnumTranslationService EnumTranslationService \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Controllers/SettingController.cs b/src/Web/Grand.Web.Store/Controllers/SettingController.cs new file mode 100644 index 000000000..098550cfe --- /dev/null +++ b/src/Web/Grand.Web.Store/Controllers/SettingController.cs @@ -0,0 +1,668 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Checkout.Orders; +using Grand.Business.Core.Interfaces.Common.Configuration; +using Grand.Business.Core.Interfaces.Common.Directory; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Storage; +using Grand.Domain.Blogs; +using Grand.Domain.Catalog; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Directory; +using Grand.Domain.Knowledgebase; +using Grand.Domain.Localization; +using Grand.Domain.Media; +using Grand.Domain.News; +using Grand.Domain.Orders; +using Grand.Domain.Permissions; +using Grand.Domain.Security; +using Grand.Domain.Seo; +using Grand.Domain.Stores; +using Grand.Infrastructure; +using Grand.Infrastructure.Caching; +using Grand.Web.AdminShared.Extensions.Mapping; +using Grand.Web.AdminShared.Extensions.Mapping.Settings; +using Grand.Web.AdminShared.Models.Settings; +using Grand.Web.Common.DataSource; +using Grand.Web.Common.Localization; +using Grand.Web.Common.Security.Authorization; +using Grand.Web.Common.Security.Captcha; +using Grand.Web.Common.Themes; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Grand.Web.Store.Controllers; + +[PermissionAuthorize(PermissionSystemName.Settings)] +public class SettingController( + ISettingService settingService, + IPictureService pictureService, + ITranslationService translationService, + IMerchandiseReturnService merchandiseReturnService, + ILanguageService languageService, + ICacheBase cacheBase, + IContextAccessor contextAccessor, + IEnumTranslationService enumTranslationService) + : BaseStoreController +{ + #region Utilities + + private async Task ClearCache() + { + await cacheBase.Clear(); + } + + private string GetStoreScope() => contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + + /// + /// Returns true if the store owner is allowed to access (edit/delete) the given store-linked item. + /// Store owners can only access items that are explicitly assigned to their store + /// (LimitedToStores = true and their storeId is in the Stores collection). + /// Items available to all stores (LimitedToStores = false) are not editable by store owners. + /// + private static bool IsStoreOwnerAccessAllowed(string storeId, bool limitedToStores, ICollection stores) + => string.IsNullOrEmpty(storeId) || (limitedToStores && stores.Contains(storeId)); + + #endregion + + #region General common settings + + public async Task GeneralCommon([FromServices] IEnumerable themes, + [FromServices] IDateTimeService dateTimeService) + { + var storeScope = GetStoreScope(); + var model = new GeneralCommonSettingsModel { + ActiveStore = storeScope + }; + + var dateTimeSettings = await settingService.LoadSetting(storeScope); + model.DateTimeSettings.DefaultStoreTimeZoneId = dateTimeSettings.DefaultStoreTimeZoneId; + var isWindows = Grand.Infrastructure.OperatingSystem.IsWindows(); + foreach (var timeZone in dateTimeService.GetSystemTimeZones()) + { + var name = isWindows ? timeZone.DisplayName : $"{timeZone.StandardName} ({timeZone.Id})"; + model.DateTimeSettings.AvailableTimeZones.Add(new SelectListItem { + Text = name, + Value = timeZone.Id, + Selected = timeZone.Id.Equals(dateTimeSettings.DefaultStoreTimeZoneId, + StringComparison.OrdinalIgnoreCase) + }); + } + + var storeInformationSettings = await settingService.LoadSetting(storeScope); + model.StoreInformationSettings = storeInformationSettings.ToModel(); + model.StoreInformationSettings.AvailableStoreThemes = + themes.Where(x => x.AreaName == "").Select(x => + new GeneralCommonSettingsModel.StoreInformationSettingsModel.ThemeConfigurationModel { + ThemeName = x.ThemeName, + ThemeTitle = x.ThemeInfo.Title, + PreviewImageUrl = x.ThemeInfo.PreviewImageUrl, + PreviewText = x.ThemeInfo.PreviewText, + SupportRtl = x.ThemeInfo.SupportRtl, + Selected = x.ThemeName == storeInformationSettings.DefaultStoreTheme + }).ToList(); + + var commonSettings = await settingService.LoadSetting(storeScope); + model.CommonSettings = commonSettings.ToModel(); + + var seoSettings = await settingService.LoadSetting(storeScope); + model.SeoSettings = seoSettings.ToModel(); + + var captchaSettings = await settingService.LoadSetting(storeScope); + model.SecuritySettings = captchaSettings.ToModel(); + model.SecuritySettings.AvailableReCaptchaVersions = + enumTranslationService.ToSelectList(GoogleReCaptchaVersion.V2, false).ToList(); + + var pdfSettings = await settingService.LoadSetting(storeScope); + model.PdfSettings = pdfSettings.ToModel(); + + var displayMenuItemSettings = await settingService.LoadSetting(storeScope); + model.DisplayMenuSettings = displayMenuItemSettings.ToModel(); + + return View(model); + } + + [HttpPost] + public async Task GeneralCommon(GeneralCommonSettingsModel model) + { + var storeScope = GetStoreScope(); + + var storeInformationSettings = await settingService.LoadSetting(storeScope); + storeInformationSettings = model.StoreInformationSettings.ToEntity(storeInformationSettings); + await settingService.SaveSetting(storeInformationSettings, storeScope); + + var dateTimeSettings = await settingService.LoadSetting(storeScope); + dateTimeSettings.DefaultStoreTimeZoneId = model.DateTimeSettings.DefaultStoreTimeZoneId; + await settingService.SaveSetting(dateTimeSettings, storeScope); + + var commonSettings = await settingService.LoadSetting(storeScope); + commonSettings = model.CommonSettings.ToEntity(commonSettings); + await settingService.SaveSetting(commonSettings, storeScope); + + var seoSettings = await settingService.LoadSetting(storeScope); + seoSettings = model.SeoSettings.ToEntity(seoSettings); + await settingService.SaveSetting(seoSettings, storeScope); + + var captchaSettings = await settingService.LoadSetting(storeScope); + captchaSettings = model.SecuritySettings.ToEntity(captchaSettings); + await settingService.SaveSetting(captchaSettings, storeScope); + if (captchaSettings.Enabled && + (string.IsNullOrWhiteSpace(captchaSettings.ReCaptchaPublicKey) || + string.IsNullOrWhiteSpace(captchaSettings.ReCaptchaPrivateKey))) + Error("Captcha is enabled but the appropriate keys are not entered"); + + var pdfSettings = await settingService.LoadSetting(storeScope); + pdfSettings = model.PdfSettings.ToEntity(pdfSettings); + await settingService.SaveSetting(pdfSettings, storeScope); + + var displayMenuItemSettings = await settingService.LoadSetting(storeScope); + displayMenuItemSettings = model.DisplayMenuSettings.ToEntity(displayMenuItemSettings); + await settingService.SaveSetting(displayMenuItemSettings, storeScope); + + await ClearCache(); + + Success(translationService.GetResource("Admin.Configuration.Updated")); + await SaveSelectedTabIndex(); + return RedirectToAction("GeneralCommon"); + } + + #endregion + + #region Catalog settings + + public async Task Catalog() + { + var storeScope = GetStoreScope(); + var catalogSettings = await settingService.LoadSetting(storeScope); + var model = catalogSettings.ToModel(); + model.ActiveStore = storeScope; + return View(model); + } + + [HttpPost] + public async Task Catalog(CatalogSettingsModel model) + { + var storeScope = GetStoreScope(); + var catalogSettings = await settingService.LoadSetting(storeScope); + catalogSettings = model.ToEntity(catalogSettings); + await settingService.SaveSetting(catalogSettings, storeScope); + await ClearCache(); + Success(translationService.GetResource("Admin.Configuration.Updated")); + await SaveSelectedTabIndex(); + return RedirectToAction("Catalog"); + } + + #region Sort options + + [HttpPost] + public async Task SortOptionsList(DataSourceRequest command) + { + var storeScope = GetStoreScope(); + var catalogSettings = await settingService.LoadSetting(storeScope); + var model = new List(); + foreach (int option in Enum.GetValues(typeof(ProductSortingEnum))) + model.Add(new SortOptionModel { + Id = option, + Name = enumTranslationService.GetTranslationEnum((ProductSortingEnum)option), + IsActive = !catalogSettings.ProductSortingEnumDisabled.Contains(option), + DisplayOrder = catalogSettings.ProductSortingEnumDisplayOrder.TryGetValue(option, out var value) + ? value + : option + }); + var gridModel = new DataSourceResult { + Data = model.OrderBy(option => option.DisplayOrder), + Total = model.Count + }; + return Json(gridModel); + } + + [HttpPost] + public async Task SortOptionUpdate(SortOptionModel model) + { + var storeScope = GetStoreScope(); + var catalogSettings = await settingService.LoadSetting(storeScope); + catalogSettings.ProductSortingEnumDisplayOrder[model.Id] = model.DisplayOrder; + switch (model.IsActive) + { + case true when catalogSettings.ProductSortingEnumDisabled.Contains(model.Id): + catalogSettings.ProductSortingEnumDisabled.Remove(model.Id); + break; + case false when !catalogSettings.ProductSortingEnumDisabled.Contains(model.Id): + catalogSettings.ProductSortingEnumDisabled.Add(model.Id); + break; + } + + await settingService.SaveSetting(catalogSettings, storeScope); + await ClearCache(); + return new JsonResult(""); + } + + #endregion + + #endregion + + #region Sales settings + + public async Task Sales([FromServices] IOrderStatusService orderStatusService, + [FromServices] ICurrencyService currencyService) + { + var storeScope = GetStoreScope(); + var loyaltyPointsSettings = await settingService.LoadSetting(storeScope); + var orderSettings = await settingService.LoadSetting(storeScope); + var shoppingCartSettings = await settingService.LoadSetting(storeScope); + + var model = new SalesSettingsModel { + LoyaltyPointsSettings = loyaltyPointsSettings.ToModel(), + OrderSettings = orderSettings.ToModel(), + ShoppingCartSettings = shoppingCartSettings.ToModel(), + ActiveStore = storeScope + }; + + var currencySettings = await settingService.LoadSetting(); + var currency = await currencyService.GetCurrencyById(currencySettings.PrimaryStoreCurrencyId); + + model.LoyaltyPointsSettings.PrimaryStoreCurrencyCode = currency?.CurrencyCode; + var status = await orderStatusService.GetAll(); + model.LoyaltyPointsSettings.PointsForPurchases_Awarded_OrderStatuses = status + .Select(x => new SelectListItem { Value = x.StatusId.ToString(), Text = x.Name }).ToList(); + + model.OrderSettings.PrimaryStoreCurrencyCode = currency?.CurrencyCode; + model.OrderSettings.GiftVouchers_Activated_OrderStatuses = status + .Select(x => new SelectListItem { Value = x.StatusId.ToString(), Text = x.Name }).ToList(); + model.OrderSettings.GiftVouchers_Activated_OrderStatuses.Insert(0, + new SelectListItem { Text = "---", Value = "0" }); + + return View(model); + } + + [HttpPost] + public async Task Sales(SalesSettingsModel model) + { + var storeScope = GetStoreScope(); + + if (ModelState.IsValid) + { + var loyaltyPointsSettings = await settingService.LoadSetting(storeScope); + loyaltyPointsSettings = model.LoyaltyPointsSettings.ToEntity(loyaltyPointsSettings); + await settingService.SaveSetting(loyaltyPointsSettings, storeScope); + + var shoppingCartSettings = await settingService.LoadSetting(storeScope); + shoppingCartSettings = model.ShoppingCartSettings.ToEntity(shoppingCartSettings); + await settingService.SaveSetting(shoppingCartSettings, storeScope); + + var orderSettings = await settingService.LoadSetting(storeScope); + orderSettings = model.OrderSettings.ToEntity(orderSettings); + await settingService.SaveSetting(orderSettings, storeScope); + + await ClearCache(); + } + else + { + foreach (var modelState in ModelState.Values) + foreach (var error in modelState.Errors) + Error(error.ErrorMessage); + } + + await SaveSelectedTabIndex(); + await ClearCache(); + Success(translationService.GetResource("Admin.Configuration.Updated")); + return RedirectToAction("Sales"); + } + + #region Merchandise return reasons + + public async Task MerchandiseReturnReasonList() + { + const int customerFormFieldIndex = 1; + await SaveSelectedTabIndex(customerFormFieldIndex); + return RedirectToAction("Sales", "Setting"); + } + + [HttpPost] + public async Task MerchandiseReturnReasonList(DataSourceRequest command) + { + var storeId = GetStoreScope(); + var reasons = await merchandiseReturnService.GetAllMerchandiseReturnReasons(storeId); + var gridModel = new DataSourceResult { + Data = reasons.Select(x => x.ToModel()), + Total = reasons.Count + }; + return Json(gridModel); + } + + public async Task MerchandiseReturnReasonCreate() + { + var storeId = GetStoreScope(); + var model = new MerchandiseReturnReasonModel { + Stores = !string.IsNullOrEmpty(storeId) ? [storeId] : [] + }; + await AddLocales(languageService, model.Locales); + return View(model); + } + + [HttpPost] + [Grand.Web.Common.Filters.ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task MerchandiseReturnReasonCreate(MerchandiseReturnReasonModel model, + bool continueEditing) + { + if (ModelState.IsValid) + { + var storeId = GetStoreScope(); + if (!string.IsNullOrEmpty(storeId)) + model.Stores = [storeId]; + var rrr = model.ToEntity(); + await merchandiseReturnService.InsertMerchandiseReturnReason(rrr); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnReasons.Added")); + return continueEditing + ? RedirectToAction("MerchandiseReturnReasonEdit", new { id = rrr.Id }) + : RedirectToAction("MerchandiseReturnReasonList"); + } + + return View(model); + } + + public async Task MerchandiseReturnReasonEdit(string id) + { + var rrr = await merchandiseReturnService.GetMerchandiseReturnReasonById(id); + if (rrr == null) return RedirectToAction("MerchandiseReturnReasonList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rrr.LimitedToStores, rrr.Stores)) + return RedirectToAction("MerchandiseReturnReasonList"); + + var model = rrr.ToModel(); + await AddLocales(languageService, model.Locales, (locale, languageId) => { + locale.Name = rrr.GetTranslation(x => x.Name, languageId, false); + }); + return View(model); + } + + [HttpPost] + [Grand.Web.Common.Filters.ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task MerchandiseReturnReasonEdit(MerchandiseReturnReasonModel model, + bool continueEditing) + { + var rrr = await merchandiseReturnService.GetMerchandiseReturnReasonById(model.Id); + if (rrr == null) return RedirectToAction("MerchandiseReturnReasonList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rrr.LimitedToStores, rrr.Stores)) + return RedirectToAction("MerchandiseReturnReasonList"); + + if (ModelState.IsValid) + { + var existingStores = rrr.Stores; + var existingLimitedToStores = rrr.LimitedToStores; + rrr = model.ToEntity(rrr); + rrr.Stores = existingStores; + rrr.LimitedToStores = existingLimitedToStores; + await merchandiseReturnService.UpdateMerchandiseReturnReason(rrr); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnReasons.Updated")); + if (continueEditing) + { + await SaveSelectedTabIndex(); + return RedirectToAction("MerchandiseReturnReasonEdit", new { id = rrr.Id }); + } + + return RedirectToAction("MerchandiseReturnReasonList"); + } + + return View(model); + } + + [HttpPost] + public async Task MerchandiseReturnReasonDelete(string id) + { + var rrr = await merchandiseReturnService.GetMerchandiseReturnReasonById(id); + if (rrr == null) return RedirectToAction("MerchandiseReturnReasonList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rrr.LimitedToStores, rrr.Stores)) + return RedirectToAction("MerchandiseReturnReasonList"); + + await merchandiseReturnService.DeleteMerchandiseReturnReason(rrr); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnReasons.Deleted")); + return RedirectToAction("MerchandiseReturnReasonList"); + } + + #endregion + + #region Merchandise return actions + + public async Task MerchandiseReturnActionList() + { + const int customerFormFieldIndex = 1; + await SaveSelectedTabIndex(customerFormFieldIndex); + return RedirectToAction("Sales", "Setting"); + } + + [HttpPost] + public async Task MerchandiseReturnActionList(DataSourceRequest command) + { + var storeId = GetStoreScope(); + var actions = await merchandiseReturnService.GetAllMerchandiseReturnActions(storeId); + var gridModel = new DataSourceResult { + Data = actions.Select(x => x.ToModel()), + Total = actions.Count + }; + return Json(gridModel); + } + + public async Task MerchandiseReturnActionCreate() + { + var storeId = GetStoreScope(); + var model = new MerchandiseReturnActionModel { + Stores = !string.IsNullOrEmpty(storeId) ? [storeId] : [] + }; + await AddLocales(languageService, model.Locales); + return View(model); + } + + [HttpPost] + [Grand.Web.Common.Filters.ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task MerchandiseReturnActionCreate(MerchandiseReturnActionModel model, + bool continueEditing) + { + if (ModelState.IsValid) + { + var storeId = GetStoreScope(); + if (!string.IsNullOrEmpty(storeId)) + model.Stores = [storeId]; + var rra = model.ToEntity(); + await merchandiseReturnService.InsertMerchandiseReturnAction(rra); + await ClearCache(); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnActions.Added")); + return continueEditing + ? RedirectToAction("MerchandiseReturnActionEdit", new { id = rra.Id }) + : RedirectToAction("MerchandiseReturnActionList"); + } + + return View(model); + } + + public async Task MerchandiseReturnActionEdit(string id) + { + var rra = await merchandiseReturnService.GetMerchandiseReturnActionById(id); + if (rra == null) return RedirectToAction("MerchandiseReturnActionList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rra.LimitedToStores, rra.Stores)) + return RedirectToAction("MerchandiseReturnActionList"); + + var model = rra.ToModel(); + await AddLocales(languageService, model.Locales, (locale, languageId) => { + locale.Name = rra.GetTranslation(x => x.Name, languageId, false); + }); + return View(model); + } + + [HttpPost] + [Grand.Web.Common.Filters.ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + public async Task MerchandiseReturnActionEdit(MerchandiseReturnActionModel model, + bool continueEditing) + { + var rra = await merchandiseReturnService.GetMerchandiseReturnActionById(model.Id); + if (rra == null) return RedirectToAction("MerchandiseReturnActionList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rra.LimitedToStores, rra.Stores)) + return RedirectToAction("MerchandiseReturnActionList"); + + if (ModelState.IsValid) + { + var existingStores = rra.Stores; + var existingLimitedToStores = rra.LimitedToStores; + rra = model.ToEntity(rra); + rra.Stores = existingStores; + rra.LimitedToStores = existingLimitedToStores; + await merchandiseReturnService.UpdateMerchandiseReturnAction(rra); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnActions.Updated")); + if (continueEditing) + { + await SaveSelectedTabIndex(); + return RedirectToAction("MerchandiseReturnActionEdit", new { id = rra.Id }); + } + + return RedirectToAction("MerchandiseReturnActionList"); + } + + return View(model); + } + + [HttpPost] + public async Task MerchandiseReturnActionDelete(string id) + { + var rra = await merchandiseReturnService.GetMerchandiseReturnActionById(id); + if (rra == null) return RedirectToAction("MerchandiseReturnActionList"); + + var storeId = GetStoreScope(); + if (!IsStoreOwnerAccessAllowed(storeId, rra.LimitedToStores, rra.Stores)) + return RedirectToAction("MerchandiseReturnActionList"); + + await merchandiseReturnService.DeleteMerchandiseReturnAction(rra); + Success(translationService.GetResource("Admin.Settings.Order.MerchandiseReturnActions.Deleted")); + return RedirectToAction("MerchandiseReturnActionList"); + } + + #endregion + + #endregion + + #region Media settings + + public async Task Media() + { + var storeScope = GetStoreScope(); + var mediaSettings = await settingService.LoadSetting(storeScope); + var model = mediaSettings.ToModel(); + model.ActiveStore = storeScope; + return View(model); + } + + [HttpPost] + public async Task Media(MediaSettingsModel model) + { + var storeScope = GetStoreScope(); + var mediaSettings = await settingService.LoadSetting(storeScope); + + // Preserve file manager settings - store owners cannot change these + var allowedFileTypes = mediaSettings.AllowedFileTypes; + var fileManagerEnabledCommands = mediaSettings.FileManagerEnabledCommands; + var fileManagerDisabledUICommands = mediaSettings.FileManagerDisabledUICommands; + + mediaSettings = model.ToEntity(mediaSettings); + + // Restore preserved file manager settings + mediaSettings.AllowedFileTypes = allowedFileTypes; + mediaSettings.FileManagerEnabledCommands = fileManagerEnabledCommands; + mediaSettings.FileManagerDisabledUICommands = fileManagerDisabledUICommands; + + await settingService.SaveSetting(mediaSettings, storeScope); + await ClearCache(); + await pictureService.ClearThumbs(); + Success(translationService.GetResource("Admin.Configuration.Updated")); + return RedirectToAction("Media"); + } + + #endregion + + #region Customer settings + + public async Task Customer() + { + var storeScope = GetStoreScope(); + var customerSettings = await settingService.LoadSetting(storeScope); + var addressSettings = await settingService.LoadSetting(storeScope); + + var model = new CustomerSettingsModel { + CustomerSettings = customerSettings.ToModel(), + AddressSettings = addressSettings.ToModel() + }; + + return View(model); + } + + [HttpPost] + public async Task Customer(CustomerSettingsModel model) + { + var storeScope = GetStoreScope(); + var customerSettings = await settingService.LoadSetting(storeScope); + var addressSettings = await settingService.LoadSetting(storeScope); + + customerSettings = model.CustomerSettings.ToEntity(customerSettings); + await settingService.SaveSetting(customerSettings, storeScope); + + addressSettings = model.AddressSettings.ToEntity(addressSettings); + await settingService.SaveSetting(addressSettings, storeScope); + + await ClearCache(); + Success(translationService.GetResource("Admin.Configuration.Updated")); + await SaveSelectedTabIndex(); + return RedirectToAction("Customer"); + } + + #endregion + + #region Content settings + + public async Task Content() + { + var storeScope = GetStoreScope(); + var blogSettings = await settingService.LoadSetting(storeScope); + var newsSettings = await settingService.LoadSetting(storeScope); + var knowledgebaseSettings = await settingService.LoadSetting(storeScope); + var model = new ContentSettingsModel { + BlogSettings = blogSettings.ToModel(), + NewsSettings = newsSettings.ToModel(), + KnowledgebaseSettings = knowledgebaseSettings.ToModel(), + ActiveStore = storeScope + }; + + return View(model); + } + + [HttpPost] + public async Task Content(ContentSettingsModel model) + { + var storeScope = GetStoreScope(); + + var blogSettings = await settingService.LoadSetting(storeScope); + blogSettings = model.BlogSettings.ToEntity(blogSettings); + await settingService.SaveSetting(blogSettings, storeScope); + + var newsSettings = await settingService.LoadSetting(storeScope); + newsSettings = model.NewsSettings.ToEntity(newsSettings); + await settingService.SaveSetting(newsSettings, storeScope); + + var knowledgeBaseSettings = await settingService.LoadSetting(storeScope); + knowledgeBaseSettings = model.KnowledgebaseSettings.ToEntity(knowledgeBaseSettings); + await settingService.SaveSetting(knowledgeBaseSettings, storeScope); + + await SaveSelectedTabIndex(); + await ClearCache(); + Success(translationService.GetResource("Admin.Configuration.Updated")); + return RedirectToAction("Content"); + } + + #endregion +} diff --git a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnHandler.cs b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnHandler.cs index 91c715952..f3469b3f6 100644 --- a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnHandler.cs @@ -84,7 +84,7 @@ public async Task Handle(GetMerchandiseReturn request, private async Task> PrepareAvailableReturnReasons() { var reasons = new List(); - foreach (var rrr in await _merchandiseReturnService.GetAllMerchandiseReturnReasons()) + foreach (var rrr in await _merchandiseReturnService.GetAllMerchandiseReturnReasons(_contextAccessor.StoreContext.CurrentStore.Id)) reasons.Add(new MerchandiseReturnModel.MerchandiseReturnReasonModel { Id = rrr.Id, Name = rrr.GetTranslation(x => x.Name, _contextAccessor.WorkContext.WorkingLanguage.Id) @@ -95,7 +95,7 @@ public async Task Handle(GetMerchandiseReturn request, private async Task> PrepareAvailableReturnActions() { var actions = new List(); - foreach (var rra in await _merchandiseReturnService.GetAllMerchandiseReturnActions()) + foreach (var rra in await _merchandiseReturnService.GetAllMerchandiseReturnActions(_contextAccessor.StoreContext.CurrentStore.Id)) actions.Add(new MerchandiseReturnModel.MerchandiseReturnActionModel { Id = rra.Id, Name = rra.GetTranslation(x => x.Name, _contextAccessor.WorkContext.WorkingLanguage.Id)