diff --git a/src/Business/Grand.Business.Common/Services/Addresses/AddressAttributeParser.cs b/src/Business/Grand.Business.Common/Services/Addresses/AddressAttributeParser.cs index a6a88e6b9..b472de2d3 100644 --- a/src/Business/Grand.Business.Common/Services/Addresses/AddressAttributeParser.cs +++ b/src/Business/Grand.Business.Common/Services/Addresses/AddressAttributeParser.cs @@ -92,6 +92,18 @@ public virtual IList AddAddressAttribute(IList /// Attributes /// Warnings public virtual async Task> GetAttributeWarnings(IList customAttributes) + { + return await GetAttributeWarnings(customAttributes, string.Empty); + } + + /// + /// Validates address attributes limited to the specified store + /// + /// Attributes + /// Store identifier + /// Warnings + public virtual async Task> GetAttributeWarnings(IList customAttributes, + string storeId) { var warnings = new List(); @@ -99,7 +111,7 @@ public virtual async Task> GetAttributeWarnings(IListCache manager /// Address attribute repository /// Mediator + /// Access control config public AddressAttributeService(ICacheBase cacheBase, IRepository addressAttributeRepository, - IMediator mediator) + IMediator mediator, + AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _addressAttributeRepository = addressAttributeRepository; _mediator = mediator; + _accessControlConfig = accessControlConfig; } #endregion @@ -37,6 +41,7 @@ public AddressAttributeService(ICacheBase cacheBase, private readonly IRepository _addressAttributeRepository; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; + private readonly AccessControlConfig _accessControlConfig; #endregion @@ -48,12 +53,32 @@ public AddressAttributeService(ICacheBase cacheBase, /// Address attributes public virtual async Task> GetAllAddressAttributes() { - var key = CacheKey.ADDRESSATTRIBUTES_ALL_KEY; + return await GetAllAddressAttributes(string.Empty); + } + + /// + /// Gets all address attributes for the specified store + /// + /// Store identifier + /// Address attributes + public virtual async Task> GetAllAddressAttributes(string storeId) + { + var key = string.IsNullOrEmpty(storeId) + ? CacheKey.ADDRESSATTRIBUTES_ALL_KEY + : $"{CacheKey.ADDRESSATTRIBUTES_ALL_KEY}.{storeId}"; return await _cacheBase.GetAsync(key, async () => { var query = from aa in _addressAttributeRepository.Table - orderby aa.DisplayOrder select aa; + + query = query.OrderBy(aa => aa.DisplayOrder); + + //Store acl + if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations) + query = from aa in query + where !aa.LimitedToStores || aa.Stores.Contains(storeId) + select aa; + return await Task.FromResult(query.ToList()); }); } diff --git a/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeParser.cs b/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeParser.cs index 0c074c2fe..27a1f0415 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeParser.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeParser.cs @@ -39,6 +39,14 @@ IList AddAddressAttribute(IList customAttribut /// Warnings Task> GetAttributeWarnings(IList customAttributes); + /// + /// Validates address attributes limited to the specified store + /// + /// Attributes + /// Store identifier + /// Warnings + Task> GetAttributeWarnings(IList customAttributes, string storeId); + /// /// Formats attributes /// diff --git a/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeService.cs b/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeService.cs index ad4223fba..4dfc6ed12 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Common/Addresses/IAddressAttributeService.cs @@ -13,6 +13,13 @@ public interface IAddressAttributeService /// Address attributes Task> GetAllAddressAttributes(); + /// + /// Gets all address attributes for the specified store + /// + /// Store identifier + /// Address attributes + Task> GetAllAddressAttributes(string storeId); + /// /// Gets an address attribute /// diff --git a/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeParser.cs b/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeParser.cs index 03752a0df..9b1e0ab51 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeParser.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeParser.cs @@ -40,6 +40,14 @@ IList AddCustomerAttribute(IList customAttribu /// Warnings Task> GetAttributeWarnings(IList customAttributes); + /// + /// Validates customer attributes limited to the specified store + /// + /// Attributes + /// Store identifier + /// Warnings + Task> GetAttributeWarnings(IList customAttributes, string storeId); + /// /// Formats attributes /// diff --git a/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeService.cs b/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeService.cs index f89872780..c94fd1ef3 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Customers/ICustomerAttributeService.cs @@ -13,6 +13,13 @@ public interface ICustomerAttributeService /// Customer attributes Task> GetAllCustomerAttributes(); + /// + /// Gets all customer attributes for the specified store + /// + /// Store identifier + /// Customer attributes + Task> GetAllCustomerAttributes(string storeId); + /// /// Gets a customer attribute /// diff --git a/src/Business/Grand.Business.Customers/Services/CustomerAttributeParser.cs b/src/Business/Grand.Business.Customers/Services/CustomerAttributeParser.cs index 7b4f7ef67..421d34401 100644 --- a/src/Business/Grand.Business.Customers/Services/CustomerAttributeParser.cs +++ b/src/Business/Grand.Business.Customers/Services/CustomerAttributeParser.cs @@ -93,6 +93,18 @@ public virtual IList AddCustomerAttribute(IListAttributes /// Warnings public virtual async Task> GetAttributeWarnings(IList customAttributes) + { + return await GetAttributeWarnings(customAttributes, string.Empty); + } + + /// + /// Validates customer attributes limited to the specified store + /// + /// Attributes + /// Store identifier + /// Warnings + public virtual async Task> GetAttributeWarnings(IList customAttributes, + string storeId) { var warnings = new List(); @@ -103,7 +115,7 @@ public virtual async Task> GetAttributeWarnings(IListCache manager /// Customer attribute repository /// Mediator + /// Access control config public CustomerAttributeService(ICacheBase cacheBase, IRepository customerAttributeRepository, - IMediator mediator) + IMediator mediator, + AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _customerAttributeRepository = customerAttributeRepository; _mediator = mediator; + _accessControlConfig = accessControlConfig; } #endregion @@ -37,6 +41,7 @@ public CustomerAttributeService(ICacheBase cacheBase, private readonly IRepository _customerAttributeRepository; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; + private readonly AccessControlConfig _accessControlConfig; #endregion @@ -48,12 +53,32 @@ public CustomerAttributeService(ICacheBase cacheBase, /// Customer attributes public virtual async Task> GetAllCustomerAttributes() { - var key = CacheKey.CUSTOMERATTRIBUTES_ALL_KEY; + return await GetAllCustomerAttributes(string.Empty); + } + + /// + /// Gets all customer attributes for the specified store + /// + /// Store identifier + /// Customer attributes + public virtual async Task> GetAllCustomerAttributes(string storeId) + { + var key = string.IsNullOrEmpty(storeId) + ? CacheKey.CUSTOMERATTRIBUTES_ALL_KEY + : $"{CacheKey.CUSTOMERATTRIBUTES_ALL_KEY}.{storeId}"; return await _cacheBase.GetAsync(key, async () => { var query = from ca in _customerAttributeRepository.Table - orderby ca.DisplayOrder select ca; + + query = query.OrderBy(ca => ca.DisplayOrder); + + //Store acl + if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations) + query = from ca in query + where !ca.LimitedToStores || ca.Stores.Contains(storeId) + select ca; + return await Task.FromResult(query.ToList()); }); } diff --git a/src/Core/Grand.Domain/Common/AddressAttribute.cs b/src/Core/Grand.Domain/Common/AddressAttribute.cs index dddafe4e8..3ae14065c 100644 --- a/src/Core/Grand.Domain/Common/AddressAttribute.cs +++ b/src/Core/Grand.Domain/Common/AddressAttribute.cs @@ -1,12 +1,13 @@ using Grand.Domain.Catalog; using Grand.Domain.Localization; +using Grand.Domain.Stores; namespace Grand.Domain.Common; /// /// Represents an address attribute /// -public class AddressAttribute : BaseEntity, ITranslationEntity +public class AddressAttribute : BaseEntity, ITranslationEntity, IStoreLinkEntity { private ICollection _addressAttributeValues; @@ -51,4 +52,11 @@ public virtual ICollection AddressAttributeValues { /// 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; } + + public IList Stores { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Core/Grand.Domain/Customers/CustomerAttribute.cs b/src/Core/Grand.Domain/Customers/CustomerAttribute.cs index 4965ab966..f28802132 100644 --- a/src/Core/Grand.Domain/Customers/CustomerAttribute.cs +++ b/src/Core/Grand.Domain/Customers/CustomerAttribute.cs @@ -1,12 +1,13 @@ using Grand.Domain.Catalog; using Grand.Domain.Localization; +using Grand.Domain.Stores; namespace Grand.Domain.Customers; /// /// Represents a customer attribute /// -public class CustomerAttribute : BaseEntity, ITranslationEntity +public class CustomerAttribute : BaseEntity, ITranslationEntity, IStoreLinkEntity { private ICollection _customerAttributeValues; @@ -47,4 +48,11 @@ public virtual ICollection CustomerAttributeValues { /// 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; } + + public IList Stores { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Common.Tests/Services/Addresses/AddressAttributeServiceTests.cs b/src/Tests/Grand.Business.Common.Tests/Services/Addresses/AddressAttributeServiceTests.cs index bbfa56180..9b74cb9f5 100644 --- a/src/Tests/Grand.Business.Common.Tests/Services/Addresses/AddressAttributeServiceTests.cs +++ b/src/Tests/Grand.Business.Common.Tests/Services/Addresses/AddressAttributeServiceTests.cs @@ -3,6 +3,7 @@ using Grand.Data.Mongo; using Grand.Domain.Common; using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Events; using Grand.SharedKernel.Extensions; using MediatR; @@ -27,7 +28,8 @@ public void Init() _cacheMock = new Mock(); _repositoryMock = new Mock>(Mock.Of()); _mediatorMock = new Mock(); - _service = new AddressAttributeService(_cacheMock.Object, _repositoryMock.Object, _mediatorMock.Object); + _service = new AddressAttributeService(_cacheMock.Object, _repositoryMock.Object, _mediatorMock.Object, + new AccessControlConfig()); } [TestMethod] diff --git a/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeParserTests.cs b/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeParserTests.cs index 1af4f1a29..9b05b7167 100644 --- a/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeParserTests.cs +++ b/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeParserTests.cs @@ -99,7 +99,7 @@ public void AddCustomerAttribute_ReturnExpectedValues() [TestMethod] public async Task GetAttributeWarnings_Return_NoWarning() { - _customerAtrServiceMock.Setup(c => c.GetAllCustomerAttributes()) + _customerAtrServiceMock.Setup(c => c.GetAllCustomerAttributes(It.IsAny())) .Returns(() => Task.FromResult((IList)_customerAtr)); _customerAtrServiceMock.Setup(c => c.GetCustomerAttributeById(It.IsAny())).Returns((string w) => Task.FromResult(_customerAtr.FirstOrDefault(a => a.Id.Equals(w)))); @@ -111,7 +111,7 @@ public async Task GetAttributeWarnings_Return_NoWarning() public async Task GetAttributeWarnings_Return_WithWarning() { _customerAtr.FirstOrDefault(x => x.Id == "key5").IsRequired = true; - _customerAtrServiceMock.Setup(c => c.GetAllCustomerAttributes()) + _customerAtrServiceMock.Setup(c => c.GetAllCustomerAttributes(It.IsAny())) .Returns(() => Task.FromResult((IList)_customerAtr)); _customerAtrServiceMock.Setup(c => c.GetCustomerAttributeById(It.IsAny())).Returns((string w) => Task.FromResult(_customerAtr.FirstOrDefault(a => a.Id.Equals(w)))); diff --git a/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeServiceTests.cs b/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeServiceTests.cs index 0072e583d..e085167e3 100644 --- a/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeServiceTests.cs +++ b/src/Tests/Grand.Business.Customers.Tests/Services/CustomerAttributeServiceTests.cs @@ -2,6 +2,7 @@ using Grand.Data; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Events; using MediatR; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -23,7 +24,8 @@ public void Init() _cacheMock = new Mock(); _repositoryMock = new Mock>(); _mediatorMock = new Mock(); - _atrService = new CustomerAttributeService(_cacheMock.Object, _repositoryMock.Object, _mediatorMock.Object); + _atrService = new CustomerAttributeService(_cacheMock.Object, _repositoryMock.Object, _mediatorMock.Object, + new AccessControlConfig()); } [TestMethod] diff --git a/src/Tests/Grand.Mapping.Tests/AdminShared/CommonMappingTests.AddressAttributeModel_ToDomain.verified.txt b/src/Tests/Grand.Mapping.Tests/AdminShared/CommonMappingTests.AddressAttributeModel_ToDomain.verified.txt index 146d5dde0..981196b6b 100644 --- a/src/Tests/Grand.Mapping.Tests/AdminShared/CommonMappingTests.AddressAttributeModel_ToDomain.verified.txt +++ b/src/Tests/Grand.Mapping.Tests/AdminShared/CommonMappingTests.AddressAttributeModel_ToDomain.verified.txt @@ -4,5 +4,6 @@ AttributeControlTypeId: 1, DisplayOrder: 1, AttributeControlType: DropdownList, + LimitedToStores: false, Id: ObjectId_1 } \ No newline at end of file diff --git a/src/Tests/Grand.Mapping.Tests/AdminShared/CustomerMappingTests.CustomerAttributeModel_ToDomain.verified.txt b/src/Tests/Grand.Mapping.Tests/AdminShared/CustomerMappingTests.CustomerAttributeModel_ToDomain.verified.txt index 60adbf5d0..68610d81c 100644 --- a/src/Tests/Grand.Mapping.Tests/AdminShared/CustomerMappingTests.CustomerAttributeModel_ToDomain.verified.txt +++ b/src/Tests/Grand.Mapping.Tests/AdminShared/CustomerMappingTests.CustomerAttributeModel_ToDomain.verified.txt @@ -4,5 +4,6 @@ IsReadOnly: false, AttributeControlTypeId: RadioList, DisplayOrder: 1, + LimitedToStores: false, Id: ObjectId_1 } \ No newline at end of file diff --git a/src/Tests/Grand.Web.Tests/Validators/Customer/CustomerValidatorsStoreScopingTests.cs b/src/Tests/Grand.Web.Tests/Validators/Customer/CustomerValidatorsStoreScopingTests.cs index 1994fb4a1..89017bcb7 100644 --- a/src/Tests/Grand.Web.Tests/Validators/Customer/CustomerValidatorsStoreScopingTests.cs +++ b/src/Tests/Grand.Web.Tests/Validators/Customer/CustomerValidatorsStoreScopingTests.cs @@ -82,7 +82,7 @@ public async Task RegisterValidator_ScopesEmailLookupToCurrentStore() mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var attrParser = new Mock(); - attrParser.Setup(p => p.GetAttributeWarnings(It.IsAny>())) + attrParser.Setup(p => p.GetAttributeWarnings(It.IsAny>(), It.IsAny())) .ReturnsAsync(new List()); var validator = new RegisterValidator( @@ -139,7 +139,7 @@ public async Task CustomerInfoValidator_ScopesEmailLookupToCurrentStore_WhenEmai mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var attrParser = new Mock(); - attrParser.Setup(p => p.GetAttributeWarnings(It.IsAny>())) + attrParser.Setup(p => p.GetAttributeWarnings(It.IsAny>(), It.IsAny())) .ReturnsAsync(new List()); var validator = new CustomerInfoValidator( diff --git a/src/Web/Grand.Web.Admin/Areas/Admin/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml b/src/Web/Grand.Web.Admin/Areas/Admin/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml index 4270607f1..f966fb3cd 100644 --- a/src/Web/Grand.Web.Admin/Areas/Admin/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml +++ b/src/Web/Grand.Web.Admin/Areas/Admin/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml @@ -68,6 +68,13 @@ +
+ +
+ + +
+
diff --git a/src/Web/Grand.Web.Admin/Areas/Admin/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml b/src/Web/Grand.Web.Admin/Areas/Admin/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml index c96e862cc..b7a29b415 100644 --- a/src/Web/Grand.Web.Admin/Areas/Admin/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml +++ b/src/Web/Grand.Web.Admin/Areas/Admin/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml @@ -92,6 +92,13 @@
+
+ +
+ + +
+
diff --git a/src/Web/Grand.Web.AdminShared/Mapper/AddressAttributeProfile.cs b/src/Web/Grand.Web.AdminShared/Mapper/AddressAttributeProfile.cs index 381e921cb..f13d2b7fa 100644 --- a/src/Web/Grand.Web.AdminShared/Mapper/AddressAttributeProfile.cs +++ b/src/Web/Grand.Web.AdminShared/Mapper/AddressAttributeProfile.cs @@ -17,6 +17,7 @@ public AddressAttributeProfile() .ForMember(dest => dest.Id, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.MapFrom(x => x.Locales.ToTranslationProperty())) .ForMember(dest => dest.AttributeControlType, mo => mo.Ignore()) + .ForMember(dest => dest.LimitedToStores, mo => mo.MapFrom(x => x.Stores != null && x.Stores.Any())) .ForMember(dest => dest.AddressAttributeValues, mo => mo.Ignore()); } diff --git a/src/Web/Grand.Web.AdminShared/Mapper/CustomerAttributeProfile.cs b/src/Web/Grand.Web.AdminShared/Mapper/CustomerAttributeProfile.cs index 73c274f66..5d0b07c1b 100644 --- a/src/Web/Grand.Web.AdminShared/Mapper/CustomerAttributeProfile.cs +++ b/src/Web/Grand.Web.AdminShared/Mapper/CustomerAttributeProfile.cs @@ -17,6 +17,7 @@ public CustomerAttributeProfile() CreateMap() .ForMember(dest => dest.Id, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.MapFrom(x => x.Locales.ToTranslationProperty())) + .ForMember(dest => dest.LimitedToStores, mo => mo.MapFrom(x => x.Stores != null && x.Stores.Any())) .ForMember(dest => dest.CustomerAttributeValues, mo => mo.Ignore()); } diff --git a/src/Web/Grand.Web.AdminShared/Models/Common/AddressAttributeModel.cs b/src/Web/Grand.Web.AdminShared/Models/Common/AddressAttributeModel.cs index 5e420e8f0..37f04b971 100644 --- a/src/Web/Grand.Web.AdminShared/Models/Common/AddressAttributeModel.cs +++ b/src/Web/Grand.Web.AdminShared/Models/Common/AddressAttributeModel.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.Common; -public class AddressAttributeModel : BaseEntityModel, ILocalizedModel +public class AddressAttributeModel : BaseEntityModel, ILocalizedModel, + IStoreLinkModel { [GrandResourceDisplayName("Admin.Address.AddressAttributes.Fields.Name")] @@ -23,6 +26,10 @@ public class AddressAttributeModel : BaseEntityModel, ILocalizedModel Locales { get; set; } = new List(); } diff --git a/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerAttributeModel.cs b/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerAttributeModel.cs index 2b920b5d1..65a371870 100644 --- a/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerAttributeModel.cs +++ b/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerAttributeModel.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.Customers; -public class CustomerAttributeModel : BaseEntityModel, ILocalizedModel +public class CustomerAttributeModel : BaseEntityModel, ILocalizedModel, + IStoreLinkModel { [GrandResourceDisplayName("Admin.Customers.CustomerAttributes.Fields.Name")] public string Name { get; set; } @@ -25,6 +28,10 @@ public class CustomerAttributeModel : BaseEntityModel, ILocalizedModel Locales { get; set; } = new List(); } diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Create.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Create.cshtml new file mode 100644 index 000000000..6c551d80a --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Create.cshtml @@ -0,0 +1,35 @@ +@model AddressAttributeStoreModel +@{ + ViewBag.Title = Loc["Admin.Address.AddressAttributes.AddNew"]; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Address.AddressAttributes.AddNew"] + + @Html.ActionLink(Loc["Admin.Address.AddressAttributes.BackToList"], "List") + +
+
+
+ + +
+
+
+
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Edit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Edit.cshtml new file mode 100644 index 000000000..572cb4529 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Edit.cshtml @@ -0,0 +1,54 @@ +@model AddressAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; + ViewBag.Title = Loc["Admin.Address.AddressAttributes.EditAttributeDetails"]; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Address.AddressAttributes.EditAttributeDetails"] - @Model.Name + + @Html.ActionLink(Loc["Admin.Address.AddressAttributes.BackToList"], "List") + +
+ @if (!isReadOnly) + { +
+
+ + + + @Loc["Admin.Common.Delete"] + +
+
+ } + else + { +
+
+ @Loc["Admin.Address.AddressAttributes.GlobalReadOnly"] +
+
+ } +
+
+ +
+
+
+
+
+@if (!isReadOnly) +{ + +} diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/List.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/List.cshtml new file mode 100644 index 000000000..62ec137e6 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/List.cshtml @@ -0,0 +1,97 @@ +@{ + ViewBag.Title = Loc["Admin.Address.AddressAttributes"]; +} + +
+
+
+
+
+ + @Loc["Admin.Address.AddressAttributes"] +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml new file mode 100644 index 000000000..de254bef5 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabInfo.cshtml @@ -0,0 +1,81 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model AddressAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; +} + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} + +
+ +
+
+ +
+ + +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ @{ + var attributeControlTypes = EnumTranslationService.ToSelectList(((AttributeControlType)Model.AttributeControlTypeId), valuesToExclude: + //custom address attributes don't support some attribute control types + [(int)AttributeControlType.Hidden, (int)AttributeControlType.FileUpload, (int)AttributeControlType.Datepicker, (int)AttributeControlType.ColorSquares, (int)AttributeControlType.ImageSquares]); + } + + +
+
+
+ +
+ + +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabValues.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabValues.cshtml new file mode 100644 index 000000000..cca808934 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.TabValues.cshtml @@ -0,0 +1,182 @@ +@model AddressAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; + + if (!string.IsNullOrEmpty(Model.Id)) + { +
+
+
+
+ @if (!isReadOnly) + { + + } + else + { + + } +
+ + if (!isReadOnly) + { + + } + else + { + + } + } + else + { +
+ @Loc["Admin.Address.AddressAttributes.Values.SaveBeforeEdit"] +
+ } +} diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.cshtml new file mode 100644 index 000000000..dad3a2e0b --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdate.cshtml @@ -0,0 +1,23 @@ +@model AddressAttributeStoreModel + +
+ + + + + + +
+ +
+
+
+ + +
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdateValue.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdateValue.cshtml new file mode 100644 index 000000000..32471312e --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/Partials/CreateOrUpdateValue.cshtml @@ -0,0 +1,58 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model AddressAttributeValueModel +
+ + + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} +
+ +
+
+ +
+ + +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueCreatePopup.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueCreatePopup.cshtml new file mode 100644 index 000000000..8467d7e42 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueCreatePopup.cshtml @@ -0,0 +1,54 @@ +@using Grand.Web.Common.Extensions +@model AddressAttributeValueModel +@{ + Layout = ""; +} +
+
+
+
+
+
+ + @Loc["Admin.Address.AddressAttributes.Values.AddNew"] +
+
+
+ +
+
+
+
+ +
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueEditPopup.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueEditPopup.cshtml new file mode 100644 index 000000000..c2b9bd21c --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/AddressAttribute/ValueEditPopup.cshtml @@ -0,0 +1,54 @@ +@using Grand.Web.Common.Extensions +@model AddressAttributeValueModel +@{ + Layout = ""; +} +
+
+
+
+
+
+ + @Loc["Admin.Address.AddressAttributes.Values.EditValueDetails"] +
+
+
+ +
+
+
+
+ +
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Create.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Create.cshtml new file mode 100644 index 000000000..4d8872d79 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Create.cshtml @@ -0,0 +1,35 @@ +@model CustomerAttributeStoreModel +@{ + ViewBag.Title = Loc["Admin.Customers.CustomerAttributes.AddNew"]; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Customers.CustomerAttributes.AddNew"] + + @Html.ActionLink(Loc["Admin.Customers.CustomerAttributes.BackToList"], "List") + +
+
+
+ + +
+
+
+
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Edit.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Edit.cshtml new file mode 100644 index 000000000..9723f0252 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Edit.cshtml @@ -0,0 +1,54 @@ +@model CustomerAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; + ViewBag.Title = Loc["Admin.Customers.CustomerAttributes.EditAttributeDetails"]; +} +
+ +
+
+
+
+
+ + @Loc["Admin.Customers.CustomerAttributes.EditAttributeDetails"] - @Model.Name + + @Html.ActionLink(Loc["Admin.Customers.CustomerAttributes.BackToList"], "List") + +
+ @if (!isReadOnly) + { +
+
+ + + + @Loc["Admin.Common.Delete"] + +
+
+ } + else + { +
+
+ @Loc["Admin.Customers.CustomerAttributes.GlobalReadOnly"] +
+
+ } +
+
+ +
+
+
+
+
+@if (!isReadOnly) +{ + +} diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/List.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/List.cshtml new file mode 100644 index 000000000..567e5f7f1 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/List.cshtml @@ -0,0 +1,97 @@ +@{ + ViewBag.Title = Loc["Admin.Customers.CustomerAttributes"]; +} + +
+
+
+
+
+ + @Loc["Admin.Customers.CustomerAttributes"] +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml new file mode 100644 index 000000000..426e0c99f --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabInfo.cshtml @@ -0,0 +1,104 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model CustomerAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; +} + + + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} + +
+ +
+
+ +
+ + +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ @{ + var attributeControlTypes = + EnumTranslationService.ToSelectList( + ((AttributeControlType)Model.AttributeControlTypeId), valuesToExclude: //custom customer attributes don't support some attribute control types + [(int)AttributeControlType.FileUpload, (int)AttributeControlType.Datepicker, (int)AttributeControlType.ColorSquares, (int)AttributeControlType.ImageSquares, (int)AttributeControlType.ReadonlyCheckboxes]); + } + + +
+
+
+ +
+ + +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabValues.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabValues.cshtml new file mode 100644 index 000000000..515719983 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.TabValues.cshtml @@ -0,0 +1,182 @@ +@model CustomerAttributeStoreModel +@{ + var isReadOnly = Model.IsGlobalAttribute; + + if (!string.IsNullOrEmpty(Model.Id)) + { +
+
+
+
+ @if (!isReadOnly) + { + + } + else + { + + } +
+ + if (!isReadOnly) + { + + } + else + { + + } + } + else + { +
+ @Loc["Admin.Customers.CustomerAttributes.Values.SaveBeforeEdit"] +
+ } +} diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.cshtml new file mode 100644 index 000000000..6fae17d89 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdate.cshtml @@ -0,0 +1,23 @@ +@model CustomerAttributeStoreModel + +
+ + + + + + +
+ +
+
+
+ + +
+ +
+
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdateValue.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdateValue.cshtml new file mode 100644 index 000000000..6c6432520 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/Partials/CreateOrUpdateValue.cshtml @@ -0,0 +1,58 @@ +@using Microsoft.AspNetCore.Mvc.Razor +@model CustomerAttributeValueModel +
+ + + +@{ + Func + template = @
+
+ +
+ + +
+
+ +
; +} +
+ +
+
+ +
+ + +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueCreatePopup.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueCreatePopup.cshtml new file mode 100644 index 000000000..805a709a5 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueCreatePopup.cshtml @@ -0,0 +1,54 @@ +@using Grand.Web.Common.Extensions +@model CustomerAttributeValueModel +@{ + Layout = ""; +} +
+
+
+
+
+
+ + @Loc["Admin.Customers.CustomerAttributes.Values.AddNew"] +
+
+
+ +
+
+
+
+ +
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueEditPopup.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueEditPopup.cshtml new file mode 100644 index 000000000..44d941ce1 --- /dev/null +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/CustomerAttribute/ValueEditPopup.cshtml @@ -0,0 +1,54 @@ +@using Grand.Web.Common.Extensions +@model CustomerAttributeValueModel +@{ + Layout = ""; +} +
+
+
+
+
+
+ + @Loc["Admin.Customers.CustomerAttributes.Values.EditValueDetails"] +
+
+
+ +
+
+
+
+ +
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 38a41e485..a35e3b263 100644 --- a/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml @@ -39,6 +39,8 @@ @using Grand.Web.AdminShared.Models.Discounts @using Grand.Web.AdminShared.Models.Tax @using Grand.Web.Store.Models.Messages +@using Grand.Web.Store.Models.Customers +@using Grand.Web.Store.Models.Common @inject LocService Loc @inject IEnumTranslationService EnumTranslationService \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Controllers/AddressAttributeController.cs b/src/Web/Grand.Web.Store/Controllers/AddressAttributeController.cs new file mode 100644 index 000000000..2b68b5dd4 --- /dev/null +++ b/src/Web/Grand.Web.Store/Controllers/AddressAttributeController.cs @@ -0,0 +1,297 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Common.Addresses; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Domain.Common; +using Grand.Domain.Permissions; +using Grand.Infrastructure; +using Grand.Infrastructure.Mapper; +using Grand.Web.AdminShared.Extensions; +using Grand.Web.AdminShared.Interfaces; +using Grand.Web.AdminShared.Models.Common; +using Grand.Web.Store.Models.Common; +using Grand.Web.Common.DataSource; +using Grand.Web.Common.Filters; +using Grand.Web.Common.Security.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Grand.Web.Store.Controllers; + +[PermissionAuthorize(PermissionSystemName.AddressAttributes)] +public class AddressAttributeController : BaseStoreController +{ + private readonly IAddressAttributeViewModelService _addressAttributeViewModelService; + private readonly IAddressAttributeService _addressAttributeService; + private readonly ILanguageService _languageService; + private readonly ITranslationService _translationService; + private readonly IContextAccessor _contextAccessor; + + public AddressAttributeController( + IAddressAttributeViewModelService addressAttributeViewModelService, + IAddressAttributeService addressAttributeService, + ILanguageService languageService, + ITranslationService translationService, + IContextAccessor contextAccessor) + { + _addressAttributeViewModelService = addressAttributeViewModelService; + _addressAttributeService = addressAttributeService; + _languageService = languageService; + _translationService = translationService; + _contextAccessor = contextAccessor; + } + + private string CurrentStoreId => _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + + private bool IsVisibleToStore(AddressAttribute attr) => + !attr.LimitedToStores || attr.Stores.Contains(CurrentStoreId); + + #region Address attributes + + public IActionResult Index() + { + return RedirectToAction("List"); + } + + public IActionResult List() + { + return View(); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.List)] + public async Task List(DataSourceRequest command) + { + var storeId = CurrentStoreId; + var (addressAttributes, _) = await _addressAttributeViewModelService.PrepareAddressAttributes(); + var visibleAttributes = addressAttributes + .Where(x => x.Stores == null || x.Stores.Length == 0 || x.Stores.Contains(storeId)) + .ToList(); + var gridModel = new DataSourceResult { + Data = visibleAttributes.Select(x => new { + x.Id, + x.Name, + x.AttributeControlTypeName, + x.IsRequired, + x.DisplayOrder, + IsGlobalAttribute = !(x.Stores is { Length: 1 } && x.Stores.Contains(storeId)) + }).ToList(), + Total = visibleAttributes.Count + }; + return Json(gridModel); + } + + [PermissionAuthorizeAction(PermissionActionName.Create)] + public async Task Create() + { + var model = new AddressAttributeStoreModel(); + await AddLocales(_languageService, model.Locales); + return View(model); + } + + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + [PermissionAuthorizeAction(PermissionActionName.Create)] + public async Task Create(AddressAttributeStoreModel model, bool continueEditing) + { + if (ModelState.IsValid) + { + model.Stores = [CurrentStoreId]; + var addressAttribute = await _addressAttributeViewModelService.InsertAddressAttributeModel(model); + Success(_translationService.GetResource("Admin.Address.AddressAttributes.Added")); + return continueEditing + ? RedirectToAction("Edit", new { id = addressAttribute.Id }) + : RedirectToAction("List"); + } + + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task Edit(string id) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(id); + if (addressAttribute == null || !IsVisibleToStore(addressAttribute)) + return RedirectToAction("List"); + + var model = addressAttribute.MapTo(); + model.IsGlobalAttribute = !addressAttribute.AccessToEntityByStore(CurrentStoreId); + await AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = addressAttribute.GetTranslation(x => x.Name, languageId, false); + }); + + return View(model); + } + + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task Edit(AddressAttributeModel model, bool continueEditing) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(model.Id); + if (addressAttribute == null) + return RedirectToAction("List"); + + if (!addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + model.Stores = [CurrentStoreId]; + addressAttribute = + await _addressAttributeViewModelService.UpdateAddressAttributeModel(model, addressAttribute); + Success(_translationService.GetResource("Admin.Address.AddressAttributes.Updated")); + if (continueEditing) + { + await SaveSelectedTabIndex(); + return RedirectToAction("Edit", new { id = addressAttribute.Id }); + } + + return RedirectToAction("List"); + } + + var storeModel = addressAttribute.MapTo(); + storeModel.IsGlobalAttribute = false; + await AddLocales(_languageService, storeModel.Locales, (locale, languageId) => + { + locale.Name = addressAttribute.GetTranslation(x => x.Name, languageId, false); + }); + return View(storeModel); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Delete)] + public async Task Delete(string id) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(id); + if (addressAttribute == null) + return RedirectToAction("List"); + + if (!addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("Edit", new { id }); + + if (ModelState.IsValid) + { + await _addressAttributeService.DeleteAddressAttribute(addressAttribute); + Success(_translationService.GetResource("Admin.Address.AddressAttributes.Deleted")); + return RedirectToAction("List"); + } + + Error(ModelState); + return RedirectToAction("Edit", new { id }); + } + + #endregion + + #region Address attribute values + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task ValueList(string addressAttributeId, DataSourceRequest command) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(addressAttributeId); + if (addressAttribute == null || !IsVisibleToStore(addressAttribute)) + return new JsonResult(new DataSourceResult { Errors = "Access denied" }); + + var (addressAttributeValues, totalCount) = + await _addressAttributeViewModelService.PrepareAddressAttributeValues(addressAttributeId); + var gridModel = new DataSourceResult { + Data = addressAttributeValues.ToList(), + Total = totalCount + }; + return Json(gridModel); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueCreatePopup(string addressAttributeId) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(addressAttributeId); + if (addressAttribute == null || !addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var model = _addressAttributeViewModelService.PrepareAddressAttributeValueModel(addressAttributeId); + await AddLocales(_languageService, model.Locales); + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueCreatePopup(AddressAttributeValueModel model) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(model.AddressAttributeId); + if (addressAttribute == null || !addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _addressAttributeViewModelService.InsertAddressAttributeValueModel(model); + return Content(""); + } + + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueEditPopup(string id, string addressAttributeId) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(addressAttributeId); + if (addressAttribute == null || !addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var cav = addressAttribute.AddressAttributeValues.FirstOrDefault(x => x.Id == id); + if (cav == null) + return RedirectToAction("List"); + + var model = _addressAttributeViewModelService.PrepareAddressAttributeValueModel(cav); + await AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = cav.GetTranslation(x => x.Name, languageId, false); + }); + + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueEditPopup(AddressAttributeValueModel model) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(model.AddressAttributeId); + if (addressAttribute == null || !addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var cav = addressAttribute.AddressAttributeValues.FirstOrDefault(x => x.Id == model.Id); + if (cav == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _addressAttributeViewModelService.UpdateAddressAttributeValueModel(model, cav); + return Content(""); + } + + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueDelete(AddressAttributeValueModel model) + { + var addressAttribute = await _addressAttributeService.GetAddressAttributeById(model.AddressAttributeId); + if (addressAttribute == null || !addressAttribute.AccessToEntityByStore(CurrentStoreId)) + return new JsonResult(new DataSourceResult { Errors = "Access denied" }); + + var cav = addressAttribute.AddressAttributeValues.FirstOrDefault(x => x.Id == model.Id); + if (cav == null) + return new JsonResult(new DataSourceResult + { Errors = "No address attribute value found with the specified id" }); + + if (ModelState.IsValid) + { + await _addressAttributeService.DeleteAddressAttributeValue(cav); + return new JsonResult(""); + } + + return ErrorForKendoGridJson(ModelState); + } + + #endregion +} diff --git a/src/Web/Grand.Web.Store/Controllers/CustomerAttributeController.cs b/src/Web/Grand.Web.Store/Controllers/CustomerAttributeController.cs new file mode 100644 index 000000000..b93bc625a --- /dev/null +++ b/src/Web/Grand.Web.Store/Controllers/CustomerAttributeController.cs @@ -0,0 +1,294 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Customers; +using Grand.Domain.Customers; +using Grand.Domain.Permissions; +using Grand.Infrastructure; +using Grand.Infrastructure.Mapper; +using Grand.Web.AdminShared.Extensions; +using Grand.Web.AdminShared.Interfaces; +using Grand.Web.AdminShared.Models.Customers; +using Grand.Web.Store.Models.Customers; +using Grand.Web.Common.DataSource; +using Grand.Web.Common.Filters; +using Grand.Web.Common.Security.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Grand.Web.Store.Controllers; + +[PermissionAuthorize(PermissionSystemName.CustomerAttributes)] +public class CustomerAttributeController : BaseStoreController +{ + private readonly ICustomerAttributeViewModelService _customerAttributeViewModelService; + private readonly ICustomerAttributeService _customerAttributeService; + private readonly ILanguageService _languageService; + private readonly ITranslationService _translationService; + private readonly IContextAccessor _contextAccessor; + + public CustomerAttributeController( + ICustomerAttributeViewModelService customerAttributeViewModelService, + ICustomerAttributeService customerAttributeService, + ILanguageService languageService, + ITranslationService translationService, + IContextAccessor contextAccessor) + { + _customerAttributeViewModelService = customerAttributeViewModelService; + _customerAttributeService = customerAttributeService; + _languageService = languageService; + _translationService = translationService; + _contextAccessor = contextAccessor; + } + + private string CurrentStoreId => _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + + private bool IsVisibleToStore(CustomerAttribute attr) => + !attr.LimitedToStores || attr.Stores.Contains(CurrentStoreId); + + #region Customer attributes + + public IActionResult Index() + { + return RedirectToAction("List"); + } + + public IActionResult List() + { + return View(); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.List)] + public async Task List(DataSourceRequest command) + { + var storeId = CurrentStoreId; + var customerAttributes = (await _customerAttributeViewModelService.PrepareCustomerAttributes()) + .Where(x => x.Stores == null || x.Stores.Length == 0 || x.Stores.Contains(storeId)) + .ToList(); + var gridModel = new DataSourceResult { + Data = customerAttributes.Select(x => new { + x.Id, + x.Name, + x.AttributeControlTypeName, + x.IsRequired, + x.DisplayOrder, + IsGlobalAttribute = !(x.Stores is { Length: 1 } && x.Stores.Contains(storeId)) + }).ToList(), + Total = customerAttributes.Count + }; + return Json(gridModel); + } + + [PermissionAuthorizeAction(PermissionActionName.Create)] + public async Task Create() + { + var model = new CustomerAttributeStoreModel(); + await AddLocales(_languageService, model.Locales); + return View(model); + } + + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + [PermissionAuthorizeAction(PermissionActionName.Create)] + public async Task Create(CustomerAttributeStoreModel model, bool continueEditing) + { + if (ModelState.IsValid) + { + model.Stores = [CurrentStoreId]; + var customerAttribute = await _customerAttributeViewModelService.InsertCustomerAttributeModel(model); + Success(_translationService.GetResource("Admin.Customers.CustomerAttributes.Added")); + return continueEditing + ? RedirectToAction("Edit", new { id = customerAttribute.Id }) + : RedirectToAction("List"); + } + + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task Edit(string id) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(id); + if (customerAttribute == null || !IsVisibleToStore(customerAttribute)) + return RedirectToAction("List"); + + var model = customerAttribute.MapTo(); + model.IsGlobalAttribute = !customerAttribute.AccessToEntityByStore(CurrentStoreId); + await AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = customerAttribute.GetTranslation(x => x.Name, languageId, false); + }); + + return View(model); + } + + [HttpPost] + [ArgumentNameFilter(KeyName = "save-continue", Argument = "continueEditing")] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task Edit(CustomerAttributeModel model, bool continueEditing) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(model.Id); + if (customerAttribute == null) + return RedirectToAction("List"); + + if (!customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + model.Stores = [CurrentStoreId]; + customerAttribute = + await _customerAttributeViewModelService.UpdateCustomerAttributeModel(model, customerAttribute); + Success(_translationService.GetResource("Admin.Customers.CustomerAttributes.Updated")); + if (continueEditing) + { + await SaveSelectedTabIndex(); + return RedirectToAction("Edit", new { id = customerAttribute.Id }); + } + + return RedirectToAction("List"); + } + + var storeModel = customerAttribute.MapTo(); + storeModel.IsGlobalAttribute = false; + await AddLocales(_languageService, storeModel.Locales, (locale, languageId) => + { + locale.Name = customerAttribute.GetTranslation(x => x.Name, languageId, false); + }); + return View(storeModel); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Delete)] + public async Task Delete(string id) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(id); + if (customerAttribute == null) + return RedirectToAction("List"); + + if (!customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("Edit", new { id }); + + if (ModelState.IsValid) + { + await _customerAttributeViewModelService.DeleteCustomerAttribute(id); + Success(_translationService.GetResource("Admin.Customers.CustomerAttributes.Deleted")); + return RedirectToAction("List"); + } + + Error(ModelState); + return RedirectToAction("Edit", new { id }); + } + + #endregion + + #region Customer attribute values + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Preview)] + public async Task ValueList(string customerAttributeId, DataSourceRequest command) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(customerAttributeId); + if (customerAttribute == null || !IsVisibleToStore(customerAttribute)) + return new JsonResult(new DataSourceResult { Errors = "Access denied" }); + + var values = await _customerAttributeViewModelService.PrepareCustomerAttributeValues(customerAttributeId); + var gridModel = new DataSourceResult { + Data = values.ToList(), + Total = values.Count() + }; + return Json(gridModel); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueCreatePopup(string customerAttributeId) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(customerAttributeId); + if (customerAttribute == null || !customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var model = _customerAttributeViewModelService.PrepareCustomerAttributeValueModel(customerAttributeId); + await AddLocales(_languageService, model.Locales); + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueCreatePopup(CustomerAttributeValueModel model) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(model.CustomerAttributeId); + if (customerAttribute == null || !customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _customerAttributeViewModelService.InsertCustomerAttributeValueModel(model); + return Content(""); + } + + return View(model); + } + + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueEditPopup(string id, string customerAttributeId) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(customerAttributeId); + if (customerAttribute == null || !customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var cav = customerAttribute.CustomerAttributeValues.FirstOrDefault(x => x.Id == id); + if (cav == null) + return RedirectToAction("List"); + + var model = _customerAttributeViewModelService.PrepareCustomerAttributeValueModel(cav); + await AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = cav.GetTranslation(x => x.Name, languageId, false); + }); + + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueEditPopup(CustomerAttributeValueModel model) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(model.CustomerAttributeId); + if (customerAttribute == null || !customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return RedirectToAction("List"); + + var cav = customerAttribute.CustomerAttributeValues.FirstOrDefault(x => x.Id == model.Id); + if (cav == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + await _customerAttributeViewModelService.UpdateCustomerAttributeValueModel(model, cav); + return Content(""); + } + + return View(model); + } + + [HttpPost] + [PermissionAuthorizeAction(PermissionActionName.Edit)] + public async Task ValueDelete(CustomerAttributeValueModel model) + { + var customerAttribute = await _customerAttributeService.GetCustomerAttributeById(model.CustomerAttributeId); + if (customerAttribute == null || !customerAttribute.AccessToEntityByStore(CurrentStoreId)) + return new JsonResult(new DataSourceResult { Errors = "Access denied" }); + + if (customerAttribute.CustomerAttributeValues.All(x => x.Id != model.Id)) + return new JsonResult(new DataSourceResult + { Errors = "No customer attribute value found with the specified id" }); + + if (ModelState.IsValid) + { + await _customerAttributeViewModelService.DeleteCustomerAttributeValue(model); + return new JsonResult(""); + } + + return ErrorForKendoGridJson(ModelState); + } + + #endregion +} diff --git a/src/Web/Grand.Web.Store/Mapper/AddressAttributeStoreProfile.cs b/src/Web/Grand.Web.Store/Mapper/AddressAttributeStoreProfile.cs new file mode 100644 index 000000000..d1ead6dff --- /dev/null +++ b/src/Web/Grand.Web.Store/Mapper/AddressAttributeStoreProfile.cs @@ -0,0 +1,19 @@ +using Grand.Domain.Common; +using Grand.Infrastructure.Mapper; +using Grand.Mapping; +using Grand.Web.Store.Models.Common; + +namespace Grand.Web.Store.Mapper; + +public class AddressAttributeStoreProfile : Profile, IAutoMapperProfile +{ + public AddressAttributeStoreProfile() + { + CreateMap() + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.IsGlobalAttribute, mo => mo.Ignore()); + } + + public int Order => 0; +} diff --git a/src/Web/Grand.Web.Store/Mapper/CustomerAttributeStoreProfile.cs b/src/Web/Grand.Web.Store/Mapper/CustomerAttributeStoreProfile.cs new file mode 100644 index 000000000..8300b3e95 --- /dev/null +++ b/src/Web/Grand.Web.Store/Mapper/CustomerAttributeStoreProfile.cs @@ -0,0 +1,19 @@ +using Grand.Domain.Customers; +using Grand.Infrastructure.Mapper; +using Grand.Mapping; +using Grand.Web.Store.Models.Customers; + +namespace Grand.Web.Store.Mapper; + +public class CustomerAttributeStoreProfile : Profile, IAutoMapperProfile +{ + public CustomerAttributeStoreProfile() + { + CreateMap() + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.IsGlobalAttribute, mo => mo.Ignore()); + } + + public int Order => 0; +} diff --git a/src/Web/Grand.Web.Store/Models/Common/AddressAttributeStoreModel.cs b/src/Web/Grand.Web.Store/Models/Common/AddressAttributeStoreModel.cs new file mode 100644 index 000000000..67799d7ed --- /dev/null +++ b/src/Web/Grand.Web.Store/Models/Common/AddressAttributeStoreModel.cs @@ -0,0 +1,8 @@ +using Grand.Web.AdminShared.Models.Common; + +namespace Grand.Web.Store.Models.Common; + +public class AddressAttributeStoreModel : AddressAttributeModel +{ + public bool IsGlobalAttribute { get; set; } +} diff --git a/src/Web/Grand.Web.Store/Models/Customers/CustomerAttributeStoreModel.cs b/src/Web/Grand.Web.Store/Models/Customers/CustomerAttributeStoreModel.cs new file mode 100644 index 000000000..5ba547754 --- /dev/null +++ b/src/Web/Grand.Web.Store/Models/Customers/CustomerAttributeStoreModel.cs @@ -0,0 +1,8 @@ +using Grand.Web.AdminShared.Models.Customers; + +namespace Grand.Web.Store.Models.Customers; + +public class CustomerAttributeStoreModel : CustomerAttributeModel +{ + public bool IsGlobalAttribute { get; set; } +} diff --git a/src/Web/Grand.Web.Store/Validators/AddressAttributeStoreValidator.cs b/src/Web/Grand.Web.Store/Validators/AddressAttributeStoreValidator.cs new file mode 100644 index 000000000..e37cdd198 --- /dev/null +++ b/src/Web/Grand.Web.Store/Validators/AddressAttributeStoreValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using Grand.Infrastructure.Validators; +using Grand.Web.AdminShared.Models.Common; +using Grand.Web.Store.Models.Common; + +namespace Grand.Web.Store.Validators; + +public class AddressAttributeStoreValidator : BaseGrandValidator +{ + public AddressAttributeStoreValidator( + IEnumerable> validators, + IValidator baseValidator) + : base(validators) + { + Include(baseValidator); + } +} diff --git a/src/Web/Grand.Web.Store/Validators/CustomerAttributeStoreValidator.cs b/src/Web/Grand.Web.Store/Validators/CustomerAttributeStoreValidator.cs new file mode 100644 index 000000000..49dd0d035 --- /dev/null +++ b/src/Web/Grand.Web.Store/Validators/CustomerAttributeStoreValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using Grand.Infrastructure.Validators; +using Grand.Web.AdminShared.Models.Customers; +using Grand.Web.Store.Models.Customers; + +namespace Grand.Web.Store.Validators; + +public class CustomerAttributeStoreValidator : BaseGrandValidator +{ + public CustomerAttributeStoreValidator( + IEnumerable> validators, + IValidator baseValidator) + : base(validators) + { + Include(baseValidator); + } +} diff --git a/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml b/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml index 0b432bbbc..722947333 100644 Binary files a/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml and b/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml differ diff --git a/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs index 93ab6dd4e..8bf0078a6 100644 --- a/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs @@ -170,7 +170,7 @@ await _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId( FaxNumber = request.Customer.GetUserFieldFromEntity(SystemCustomerFieldNames.Fax) }; - if (await IsAddressValid(defaultAddress)) + if (await IsAddressValid(defaultAddress, request.Store?.Id)) { //set default address request.Customer.Addresses.Add(defaultAddress); @@ -197,8 +197,9 @@ await _messageProviderService.SendCustomerRegisteredMessage(request.Customer, re /// Gets a value indicating whether address is valid (can be saved) /// /// Address to validate + /// Store identifier /// Result - private async Task IsAddressValid(Address address) + private async Task IsAddressValid(Address address, string storeId) { ArgumentNullException.ThrowIfNull(address); @@ -276,7 +277,7 @@ private async Task IsAddressValid(Address address) string.IsNullOrWhiteSpace(address.FaxNumber)) return false; - var attributes = await _addressAttributeService.GetAllAddressAttributes(); + var attributes = await _addressAttributeService.GetAllAddressAttributes(storeId ?? string.Empty); return !attributes.Any(x => x.IsRequired); } } \ No newline at end of file diff --git a/src/Web/Grand.Web/Features/Handlers/Common/GetAddressModelHandler.cs b/src/Web/Grand.Web/Features/Handlers/Common/GetAddressModelHandler.cs index 48dc80747..66a7e8bca 100644 --- a/src/Web/Grand.Web/Features/Handlers/Common/GetAddressModelHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Common/GetAddressModelHandler.cs @@ -50,7 +50,7 @@ await PrepareAddressModel(model, request.Address, request.ExcludeProperties, //customer attribute services await PrepareCustomAddressAttributes(model, request.Address, request.Language, - request.OverrideAttributes); + request.OverrideAttributes, request.Store?.Id); if (request.Address != null) model.FormattedCustomAddressAttributes = @@ -173,9 +173,9 @@ private async Task PrepareAddressModel(AddressModel model, } private async Task PrepareCustomAddressAttributes(AddressModel model, Address address, - Language language, IList overrideAttributes) + Language language, IList overrideAttributes, string storeId) { - var attributes = await _addressAttributeService.GetAllAddressAttributes(); + var attributes = await _addressAttributeService.GetAllAddressAttributes(storeId ?? string.Empty); foreach (var attribute in attributes) { var attributeModel = new AddressAttributeModel { diff --git a/src/Web/Grand.Web/Features/Handlers/Common/GetParseCustomAddressAttributesHandler.cs b/src/Web/Grand.Web/Features/Handlers/Common/GetParseCustomAddressAttributesHandler.cs index 5f1a8e25b..933b38933 100644 --- a/src/Web/Grand.Web/Features/Handlers/Common/GetParseCustomAddressAttributesHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Common/GetParseCustomAddressAttributesHandler.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Common.Addresses; using Grand.Domain.Catalog; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Web.Features.Models.Common; using MediatR; @@ -11,13 +12,16 @@ public class { private readonly IAddressAttributeParser _addressAttributeParser; private readonly IAddressAttributeService _addressAttributeService; + private readonly IContextAccessor _contextAccessor; public GetParseCustomAddressAttributesHandler( IAddressAttributeService addressAttributeService, - IAddressAttributeParser addressAttributeParser) + IAddressAttributeParser addressAttributeParser, + IContextAccessor contextAccessor) { _addressAttributeService = addressAttributeService; _addressAttributeParser = addressAttributeParser; + _contextAccessor = contextAccessor; } public async Task> Handle(GetParseCustomAddressAttributes request, @@ -26,7 +30,8 @@ public async Task> Handle(GetParseCustomAddressAttributes ArgumentNullException.ThrowIfNull(request.SelectedAttributes); var customAttributes = new List(); - var attributes = await _addressAttributeService.GetAllAddressAttributes(); + var attributes = + await _addressAttributeService.GetAllAddressAttributes(_contextAccessor.StoreContext.CurrentStore.Id); foreach (var attribute in attributes) switch (attribute.AttributeControlType) { diff --git a/src/Web/Grand.Web/Features/Handlers/Customers/GetCustomAttributesHandler.cs b/src/Web/Grand.Web/Features/Handlers/Customers/GetCustomAttributesHandler.cs index 155fa067a..dbd46195c 100644 --- a/src/Web/Grand.Web/Features/Handlers/Customers/GetCustomAttributesHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Customers/GetCustomAttributesHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Customers; using Grand.Domain.Catalog; using Grand.Domain.Customers; +using Grand.Infrastructure; using Grand.Web.Features.Models.Customers; using Grand.Web.Models.Customer; using MediatR; @@ -12,12 +13,15 @@ public class GetCustomAttributesHandler : IRequestHandler> Handle(GetCustomAttributes request, @@ -25,7 +29,8 @@ public async Task> Handle(GetCustomAttributes requ { var result = new List(); - var customerAttributes = await _customerAttributeService.GetAllCustomerAttributes(); + var customerAttributes = + await _customerAttributeService.GetAllCustomerAttributes(_contextAccessor.StoreContext.CurrentStore.Id); foreach (var attribute in customerAttributes) { var attributeModel = new CustomerAttributeModel { diff --git a/src/Web/Grand.Web/Features/Handlers/Customers/GetParseCustomAttributesHandler.cs b/src/Web/Grand.Web/Features/Handlers/Customers/GetParseCustomAttributesHandler.cs index 58dbf36f6..5e3a7b546 100644 --- a/src/Web/Grand.Web/Features/Handlers/Customers/GetParseCustomAttributesHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Customers/GetParseCustomAttributesHandler.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Customers; using Grand.Domain.Catalog; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Web.Features.Models.Customers; using MediatR; @@ -10,19 +11,23 @@ public class GetParseCustomAttributesHandler : IRequestHandler> Handle(GetParseCustomAttributes request, CancellationToken cancellationToken) { var customAttributes = new List(); - var attributes = await _customerAttributeService.GetAllCustomerAttributes(); + var attributes = + await _customerAttributeService.GetAllCustomerAttributes(_contextAccessor.StoreContext.CurrentStore.Id); foreach (var attribute in attributes) { if (attribute.IsReadOnly) diff --git a/src/Web/Grand.Web/Validators/Common/AddressValidator.cs b/src/Web/Grand.Web/Validators/Common/AddressValidator.cs index 7557b8895..9d972e025 100644 --- a/src/Web/Grand.Web/Validators/Common/AddressValidator.cs +++ b/src/Web/Grand.Web/Validators/Common/AddressValidator.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Infrastructure.Validators; using Grand.Web.Features.Models.Common; using Grand.Web.Models.Common; @@ -17,7 +18,8 @@ public AddressValidator( IMediator mediator, IAddressAttributeParser addressAttributeParser, ITranslationService translationService, ICountryService countryService, - AddressSettings addressSettings) + AddressSettings addressSettings, + IContextAccessor contextAccessor) : base(validators) { RuleFor(x => x.FirstName) @@ -79,7 +81,8 @@ public AddressValidator( { var customAttributes = await mediator.Send(new GetParseCustomAddressAttributes { SelectedAttributes = x.SelectedAttributes }); - var customAttributeWarnings = await addressAttributeParser.GetAttributeWarnings(customAttributes); + var customAttributeWarnings = await addressAttributeParser.GetAttributeWarnings(customAttributes, + contextAccessor.StoreContext.CurrentStore.Id); foreach (var error in customAttributeWarnings) context.AddFailure(error); }); } diff --git a/src/Web/Grand.Web/Validators/Customer/CheckoutBillingAddressValidator.cs b/src/Web/Grand.Web/Validators/Customer/CheckoutBillingAddressValidator.cs index cdd38adae..243906f4b 100644 --- a/src/Web/Grand.Web/Validators/Customer/CheckoutBillingAddressValidator.cs +++ b/src/Web/Grand.Web/Validators/Customer/CheckoutBillingAddressValidator.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Infrastructure.Validators; using Grand.Web.Models.Checkout; using Grand.Web.Models.Common; @@ -18,10 +19,11 @@ public CheckoutBillingAddressValidator( IMediator mediator, IAddressAttributeParser addressAttributeParser, ITranslationService translationService, ICountryService countryService, - AddressSettings addressSettings) + AddressSettings addressSettings, + IContextAccessor contextAccessor) : base(validators) { RuleFor(x => x.BillingNewAddress).SetValidator(new AddressValidator(addressValidators, mediator, - addressAttributeParser, translationService, countryService, addressSettings)); + addressAttributeParser, translationService, countryService, addressSettings, contextAccessor)); } } \ No newline at end of file diff --git a/src/Web/Grand.Web/Validators/Customer/CheckoutShippingAddressValidator.cs b/src/Web/Grand.Web/Validators/Customer/CheckoutShippingAddressValidator.cs index 83a81fc4a..a47274e2b 100644 --- a/src/Web/Grand.Web/Validators/Customer/CheckoutShippingAddressValidator.cs +++ b/src/Web/Grand.Web/Validators/Customer/CheckoutShippingAddressValidator.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Infrastructure.Validators; using Grand.Web.Models.Checkout; using Grand.Web.Models.Common; @@ -18,10 +19,11 @@ public CheckoutShippingAddressValidator( IMediator mediator, IAddressAttributeParser addressAttributeParser, ITranslationService translationService, ICountryService countryService, - AddressSettings addressSettings) + AddressSettings addressSettings, + IContextAccessor contextAccessor) : base(validators) { RuleFor(x => x.ShippingNewAddress).SetValidator(new AddressValidator(addressValidators, mediator, - addressAttributeParser, translationService, countryService, addressSettings)); + addressAttributeParser, translationService, countryService, addressSettings, contextAccessor)); } } \ No newline at end of file diff --git a/src/Web/Grand.Web/Validators/Customer/CustomerAddressEditValidator.cs b/src/Web/Grand.Web/Validators/Customer/CustomerAddressEditValidator.cs index 4cef431f9..78c6d63df 100644 --- a/src/Web/Grand.Web/Validators/Customer/CustomerAddressEditValidator.cs +++ b/src/Web/Grand.Web/Validators/Customer/CustomerAddressEditValidator.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Common; +using Grand.Infrastructure; using Grand.Infrastructure.Validators; using Grand.Web.Models.Common; using Grand.Web.Models.Customer; @@ -18,10 +19,11 @@ public CustomerAddressEditValidator( IMediator mediator, IAddressAttributeParser addressAttributeParser, ITranslationService translationService, ICountryService countryService, - AddressSettings addressSettings) + AddressSettings addressSettings, + IContextAccessor contextAccessor) : base(validators) { RuleFor(x => x.Address).SetValidator(new AddressValidator(addressValidators, mediator, addressAttributeParser, - translationService, countryService, addressSettings)); + translationService, countryService, addressSettings, contextAccessor)); } } \ No newline at end of file diff --git a/src/Web/Grand.Web/Validators/Customer/CustomerInfoValidator.cs b/src/Web/Grand.Web/Validators/Customer/CustomerInfoValidator.cs index 214f5fea5..e302bff96 100644 --- a/src/Web/Grand.Web/Validators/Customer/CustomerInfoValidator.cs +++ b/src/Web/Grand.Web/Validators/Customer/CustomerInfoValidator.cs @@ -122,7 +122,8 @@ public CustomerInfoValidator( { var customerAttributes = await mediator.Send(new GetParseCustomAttributes { SelectedAttributes = x.SelectedAttributes }, _); - var customerAttributeWarnings = await customerAttributeParser.GetAttributeWarnings(customerAttributes); + var customerAttributeWarnings = await customerAttributeParser.GetAttributeWarnings(customerAttributes, + contextAccessor.StoreContext.CurrentStore.Id); foreach (var error in customerAttributeWarnings) context.AddFailure(error); if (contextAccessor.WorkContext.CurrentCustomer.Email != x.Email.ToLower() && customerSettings.AllowUsersToChangeEmail) diff --git a/src/Web/Grand.Web/Validators/Customer/RegisterValidator.cs b/src/Web/Grand.Web/Validators/Customer/RegisterValidator.cs index ff41ee029..bc088eee9 100644 --- a/src/Web/Grand.Web/Validators/Customer/RegisterValidator.cs +++ b/src/Web/Grand.Web/Validators/Customer/RegisterValidator.cs @@ -137,7 +137,8 @@ CustomerConfig customerConfig RuleFor(x => x).CustomAsync(async (x, context, _) => { var customerAttributes = await mediator.Send(new GetParseCustomAttributes { SelectedAttributes = x.SelectedAttributes }, _); - var customerAttributeWarnings = await customerAttributeParser.GetAttributeWarnings(customerAttributes); + var customerAttributeWarnings = await customerAttributeParser.GetAttributeWarnings(customerAttributes, + contextAccessor.StoreContext.CurrentStore.Id); foreach (var error in customerAttributeWarnings) context.AddFailure(error); if (await groupService.IsRegistered(contextAccessor.WorkContext.CurrentCustomer)) diff --git a/src/Web/Grand.Web/Validators/Orders/MerchandiseReturnValidator.cs b/src/Web/Grand.Web/Validators/Orders/MerchandiseReturnValidator.cs index fc5d441a7..ed1533238 100644 --- a/src/Web/Grand.Web/Validators/Orders/MerchandiseReturnValidator.cs +++ b/src/Web/Grand.Web/Validators/Orders/MerchandiseReturnValidator.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Common.Addresses; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Domain.Orders; +using Grand.Infrastructure; using Grand.Infrastructure.Validators; using Grand.Web.Features.Models.Common; using Grand.Web.Models.Orders; @@ -17,7 +18,8 @@ public MerchandiseReturnValidator( IEnumerable> validators, OrderSettings orderSettings, IOrderService orderService, IProductService productService, IMediator mediator, IAddressAttributeParser addressAttributeParser, - ITranslationService translationService) + ITranslationService translationService, + IContextAccessor contextAccessor) : base(validators) { RuleFor(x => x).CustomAsync(async (x, context, _) => @@ -29,7 +31,8 @@ public MerchandiseReturnValidator( await mediator.Send( new GetParseCustomAddressAttributes { SelectedAttributes = x.MerchandiseReturnNewAddress.SelectedAttributes }, _); - var customAttributeWarnings = await addressAttributeParser.GetAttributeWarnings(customAttributes); + var customAttributeWarnings = await addressAttributeParser.GetAttributeWarnings(customAttributes, + contextAccessor.StoreContext.CurrentStore.Id); foreach (var error in customAttributeWarnings) context.AddFailure(error); if (!x.Items.Any(x => x.Quantity > 0))