diff --git a/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs b/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs
index f73d8046c..ebb2254ec 100644
--- a/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs
+++ b/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs
@@ -7,16 +7,16 @@
using Grand.Business.Core.Interfaces.Customers;
using Grand.Business.Core.Interfaces.ExportImport;
using Grand.Business.Core.Interfaces.Messages;
-using Grand.Domain.Permissions;
using Grand.Business.Core.Utilities.Customers;
using Grand.Domain.Catalog;
using Grand.Domain.Common;
using Grand.Domain.Customers;
+using Grand.Domain.Permissions;
using Grand.Domain.Tax;
using Grand.Infrastructure;
using Grand.SharedKernel;
using Grand.SharedKernel.Extensions;
-using Grand.Web.Admin.Extensions;
+using Grand.Web.AdminShared.Extensions;
using Grand.Web.AdminShared.Interfaces;
using Grand.Web.AdminShared.Models.Catalog;
using Grand.Web.AdminShared.Models.Customers;
@@ -26,7 +26,6 @@
using Grand.Web.Common.Models;
using Grand.Web.Common.Security.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Grand.Web.AdminShared.Extensions;
namespace Grand.Web.Admin.Controllers;
@@ -88,46 +87,46 @@ protected virtual async Task> ParseCustomCustomerAttribut
{
case AttributeControlType.DropdownList:
case AttributeControlType.RadioList:
- {
- var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
- if (!string.IsNullOrEmpty(ctrlAttributes))
- customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
- attribute, ctrlAttributes).ToList();
- }
+ {
+ var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
+ if (!string.IsNullOrEmpty(ctrlAttributes))
+ customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
+ attribute, ctrlAttributes).ToList();
+ }
break;
case AttributeControlType.Checkboxes:
- {
- var cblAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
- if (!string.IsNullOrEmpty(cblAttributes))
- foreach (var item in cblAttributes.Split(','))
- if (!string.IsNullOrEmpty(item))
- customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
- attribute, item).ToList();
- }
+ {
+ var cblAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
+ if (!string.IsNullOrEmpty(cblAttributes))
+ foreach (var item in cblAttributes.Split(','))
+ if (!string.IsNullOrEmpty(item))
+ customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
+ attribute, item).ToList();
+ }
break;
case AttributeControlType.ReadonlyCheckboxes:
- {
- //load read-only (already server-side selected) values
- var attributeValues = attribute.CustomerAttributeValues;
- foreach (var selectedAttributeId in attributeValues
- .Where(v => v.IsPreSelected)
- .Select(v => v.Id)
- .ToList())
- customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
- attribute, selectedAttributeId).ToList();
- }
+ {
+ //load read-only (already server-side selected) values
+ var attributeValues = attribute.CustomerAttributeValues;
+ foreach (var selectedAttributeId in attributeValues
+ .Where(v => v.IsPreSelected)
+ .Select(v => v.Id)
+ .ToList())
+ customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
+ attribute, selectedAttributeId).ToList();
+ }
break;
case AttributeControlType.TextBox:
case AttributeControlType.MultilineTextbox:
- {
- var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
- if (!string.IsNullOrEmpty(ctrlAttributes))
{
- var enteredText = ctrlAttributes.Trim();
- customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
- attribute, enteredText).ToList();
+ var ctrlAttributes = model.FirstOrDefault(x => x.Key == attribute.Id)?.Value;
+ if (!string.IsNullOrEmpty(ctrlAttributes))
+ {
+ var enteredText = ctrlAttributes.Trim();
+ customAttributes = _customerAttributeParser.AddCustomerAttribute(customAttributes,
+ attribute, enteredText).ToList();
+ }
}
- }
break;
case AttributeControlType.Datepicker:
case AttributeControlType.ColorSquares:
diff --git a/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerModel.cs b/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerModel.cs
index e1f3a7d37..d866dce47 100644
--- a/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerModel.cs
+++ b/src/Web/Grand.Web.AdminShared/Models/Customers/CustomerModel.cs
@@ -50,6 +50,9 @@ public class CustomerModel : BaseEntityModel
[GrandResourceDisplayName("Admin.Customers.Customers.Fields.StaffStore")]
public string StaffStoreId { get; set; }
+ [GrandResourceDisplayName("Admin.Customers.Customers.Fields.Store")]
+ public string StoreId { get; set; }
+
public IList AvailableStores { get; set; } = new List();
//form fields & properties
diff --git a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
index 7a9f4243d..a9d339eb5 100644
--- a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
+++ b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
@@ -190,6 +190,7 @@ public virtual async Task PrepareCustomerModel(CustomerModel model, Customer cus
model.Username = customer.Username;
model.VendorId = customer.VendorId;
model.StaffStoreId = customer.StaffStoreId;
+ model.StoreId = customer.StoreId;
model.SeId = customer.SeId;
model.AdminComment = customer.AdminComment;
model.IsTaxExempt = customer.IsTaxExempt;
@@ -264,6 +265,10 @@ public virtual async Task PrepareCustomerModel(CustomerModel model, Customer cus
else
{
model.SeId = _contextAccessor.WorkContext.CurrentCustomer.SeId;
+
+ //a store manager can only create customers for his own store - preset it
+ if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer))
+ model.StoreId = _contextAccessor.StoreContext.CurrentStore.Id;
}
model.UsernamesEnabled = _customerSettings.UsernamesEnabled;
@@ -385,7 +390,7 @@ public virtual async Task InsertCustomerModel(CustomerModel model)
IsTaxExempt = model.IsTaxExempt,
FreeShipping = model.FreeShipping,
Active = model.Active,
- StoreId = _contextAccessor.StoreContext.CurrentStore.Id,
+ StoreId = model.StoreId,
OwnerId = ownerId,
Attributes = model.Attributes,
LastActivityDateUtc = DateTime.UtcNow
@@ -545,6 +550,9 @@ await _customerService.UpdateUserField(customer,
//staff store
customer.StaffStoreId = model.StaffStoreId;
+ //store
+ customer.StoreId = model.StoreId;
+
//sales employee
customer.SeId = model.SeId;
diff --git a/src/Web/Grand.Web.AdminShared/Validators/Customers/CustomerValidator.cs b/src/Web/Grand.Web.AdminShared/Validators/Customers/CustomerValidator.cs
index 0559575ca..2b321ea24 100644
--- a/src/Web/Grand.Web.AdminShared/Validators/Customers/CustomerValidator.cs
+++ b/src/Web/Grand.Web.AdminShared/Validators/Customers/CustomerValidator.cs
@@ -30,6 +30,17 @@ public CustomerValidator(
RuleFor(x => x.Email).NotEmpty().EmailAddress()
.WithMessage(translationService.GetResource("Admin.Customers.Customers.Fields.Email.Required"));
+ //store
+ RuleFor(x => x.StoreId).NotEmpty()
+ .WithMessage(translationService.GetResource("Admin.Customers.Customers.Fields.Store.Required"));
+
+ //a store manager can only assign a customer to his own store
+ RuleFor(x => x.StoreId).MustAsync(async (storeId, _) =>
+ !await groupService.IsStoreManager(contextAccessor.WorkContext.CurrentCustomer) ||
+ storeId == contextAccessor.StoreContext.CurrentStore.Id)
+ .WithMessage(
+ translationService.GetResource("Admin.Customers.Customers.Fields.Store.MustBeCurrentStore"));
+
//form fields
if (customerSettings.CountryEnabled && customerSettings.CountryRequired)
RuleFor(x => x.CountryId)
diff --git a/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml b/src/Web/Grand.Web/App_Data/Resources/DefaultLanguage.xml
index c60e745e3..0b432bbbc 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