Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

CustomerController.cs

using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Forums;
using Nop.Core.Domain.Gdpr;
using Nop.Core.Domain.Messages;
using Nop.Core.Domain.Tax;
using Nop.Core.Events;
using Nop.Services.Attributes;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.ExportImport;
using Nop.Services.Forums;
using Nop.Services.Gdpr;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Security;
using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Web.Areas.Admin.Models.Customers;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;

namespace Nop.Web.Areas.Admin.Controllers;

public partial class CustomerController : BaseAdminController
{
    #region Fields

    protected readonly CustomerSettings _customerSettings;
    protected readonly DateTimeSettings _dateTimeSettings;
    protected readonly EmailAccountSettings _emailAccountSettings;
    protected readonly ForumSettings _forumSettings;
    protected readonly GdprSettings _gdprSettings;
    protected readonly IAddressService _addressService;
    protected readonly IAttributeParser _addressAttributeParser;
    protected readonly IAttributeParser _customerAttributeParser;
    protected readonly IAttributeService _customerAttributeService;
    protected readonly ICustomerActivityService _customerActivityService;
    protected readonly ICustomerModelFactory _customerModelFactory;
    protected readonly ICustomerRegistrationService _customerRegistrationService;
    protected readonly ICustomerService _customerService;
    protected readonly IDateTimeHelper _dateTimeHelper;
    protected readonly IEmailAccountService _emailAccountService;
    protected readonly IEventPublisher _eventPublisher;
    protected readonly IExportManager _exportManager;
    protected readonly IForumService _forumService;
    protected readonly IGdprService _gdprService;
    protected readonly IGenericAttributeService _genericAttributeService;
    protected readonly IImportManager _importManager;
    protected readonly ILocalizationService _localizationService;
    protected readonly INewsLetterSubscriptionService _newsLetterSubscriptionService;
    protected readonly INotificationService _notificationService;
    protected readonly IPermissionService _permissionService;
    protected readonly IQueuedEmailService _queuedEmailService;
    protected readonly IRewardPointService _rewardPointService;
    protected readonly IStoreContext _storeContext;
    protected readonly IStoreService _storeService;
    protected readonly ITaxService _taxService;
    protected readonly IWorkContext _workContext;
    protected readonly IWorkflowMessageService _workflowMessageService;
    protected readonly TaxSettings _taxSettings;
    private static readonly char[] _separator = [','];

    #endregion

    #region Ctor

    public CustomerController(CustomerSettings customerSettings,
        DateTimeSettings dateTimeSettings,
        EmailAccountSettings emailAccountSettings,
        ForumSettings forumSettings,
        GdprSettings gdprSettings,
        IAddressService addressService,
        IAttributeParser addressAttributeParser,
        IAttributeParser customerAttributeParser,
        IAttributeService customerAttributeService,
        ICustomerActivityService customerActivityService,
        ICustomerModelFactory customerModelFactory,
        ICustomerRegistrationService customerRegistrationService,
        ICustomerService customerService,
        IDateTimeHelper dateTimeHelper,
        IEmailAccountService emailAccountService,
        IEventPublisher eventPublisher,
        IExportManager exportManager,
        IForumService forumService,
        IGdprService gdprService,
        IGenericAttributeService genericAttributeService,
        IImportManager importManager,
        ILocalizationService localizationService,
        INewsLetterSubscriptionService newsLetterSubscriptionService,
        INotificationService notificationService,
        IPermissionService permissionService,
        IQueuedEmailService queuedEmailService,
        IRewardPointService rewardPointService,
        IStoreContext storeContext,
        IStoreService storeService,
        ITaxService taxService,
        IWorkContext workContext,
        IWorkflowMessageService workflowMessageService,
        TaxSettings taxSettings)
    {
        _customerSettings = customerSettings;
        _dateTimeSettings = dateTimeSettings;
        _emailAccountSettings = emailAccountSettings;
        _forumSettings = forumSettings;
        _gdprSettings = gdprSettings;
        _addressService = addressService;
        _addressAttributeParser = addressAttributeParser;
        _customerAttributeParser = customerAttributeParser;
        _customerAttributeService = customerAttributeService;
        _customerActivityService = customerActivityService;
        _customerModelFactory = customerModelFactory;
        _customerRegistrationService = customerRegistrationService;
        _customerService = customerService;
        _dateTimeHelper = dateTimeHelper;
        _emailAccountService = emailAccountService;
        _eventPublisher = eventPublisher;
        _exportManager = exportManager;
        _forumService = forumService;
        _gdprService = gdprService;
        _genericAttributeService = genericAttributeService;
        _importManager = importManager;
        _localizationService = localizationService;
        _newsLetterSubscriptionService = newsLetterSubscriptionService;
        _notificationService = notificationService;
        _permissionService = permissionService;
        _queuedEmailService = queuedEmailService;
        _rewardPointService = rewardPointService;
        _storeContext = storeContext;
        _storeService = storeService;
        _taxService = taxService;
        _workContext = workContext;
        _workflowMessageService = workflowMessageService;
        _taxSettings = taxSettings;
    }

    #endregion

    #region Utilities

    protected virtual async Task ValidateCustomerRolesAsync(IList customerRoles, IList existingCustomerRoles)
    {
        ArgumentNullException.ThrowIfNull(customerRoles);

        ArgumentNullException.ThrowIfNull(existingCustomerRoles);

        //check ACL permission to manage customer roles
        var rolesToAdd = customerRoles.Except(existingCustomerRoles, new CustomerRoleComparerByName());
        var rolesToDelete = existingCustomerRoles.Except(customerRoles, new CustomerRoleComparerByName());
        if (rolesToAdd.Any(role => role.SystemName != NopCustomerDefaults.RegisteredRoleName) || rolesToDelete.Any())
        {
            if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageAcl))
                return await _localizationService.GetResourceAsync("Admin.Customers.Customers.CustomerRolesManagingError");
        }

        //ensure a customer is not added to both 'Guests' and 'Registered' customer roles
        //ensure that a customer is in at least one required role ('Guests' and 'Registered')
        var isInGuestsRole = customerRoles.FirstOrDefault(cr => cr.SystemName == NopCustomerDefaults.GuestsRoleName) != null;
        var isInRegisteredRole = customerRoles.FirstOrDefault(cr => cr.SystemName == NopCustomerDefaults.RegisteredRoleName) != null;
        if (isInGuestsRole && isInRegisteredRole)
            return await _localizationService.GetResourceAsync("Admin.Customers.Customers.GuestsAndRegisteredRolesError");
        if (!isInGuestsRole && !isInRegisteredRole)
            return await _localizationService.GetResourceAsync("Admin.Customers.Customers.AddCustomerToGuestsOrRegisteredRoleError");

        //no errors
        return string.Empty;
    }

    protected virtual async Task ParseCustomCustomerAttributesAsync(IFormCollection form)
    {
        ArgumentNullException.ThrowIfNull(form);

        var attributesXml = string.Empty;
        var customerAttributes = await _customerAttributeService.GetAllAttributesAsync();
        foreach (var attribute in customerAttributes)
        {
            var controlId = $"{NopCustomerServicesDefaults.CustomerAttributePrefix}{attribute.Id}";
            StringValues ctrlAttributes;

            switch (attribute.AttributeControlType)
            {
                case AttributeControlType.DropdownList:
                case AttributeControlType.RadioList:
                    ctrlAttributes = form[controlId];
                    if (!StringValues.IsNullOrEmpty(ctrlAttributes))
                    {
                        var selectedAttributeId = int.Parse(ctrlAttributes);
                        if (selectedAttributeId > 0)
                            attributesXml = _customerAttributeParser.AddAttribute(attributesXml,
                                attribute, selectedAttributeId.ToString());
                    }

                    break;
                case AttributeControlType.Checkboxes:
                    var cblAttributes = form[controlId];
                    if (!StringValues.IsNullOrEmpty(cblAttributes))
                    {
                        foreach (var item in cblAttributes.ToString()
                                     .Split(_separator, StringSplitOptions.RemoveEmptyEntries))
                        {
                            var selectedAttributeId = int.Parse(item);
                            if (selectedAttributeId > 0)
                                attributesXml = _customerAttributeParser.AddAttribute(attributesXml,
                                    attribute, selectedAttributeId.ToString());
                        }
                    }

                    break;
                case AttributeControlType.ReadonlyCheckboxes:
                    //load read-only (already server-side selected) values
                    var attributeValues = await _customerAttributeService.GetAttributeValuesAsync(attribute.Id);
                    foreach (var selectedAttributeId in attributeValues
                                 .Where(v => v.IsPreSelected)
                                 .Select(v => v.Id)
                                 .ToList())
                    {
                        attributesXml = _customerAttributeParser.AddAttribute(attributesXml,
                            attribute, selectedAttributeId.ToString());
                    }

                    break;
                case AttributeControlType.TextBox:
                case AttributeControlType.MultilineTextbox:
                    ctrlAttributes = form[controlId];
                    if (!StringValues.IsNullOrEmpty(ctrlAttributes))
                    {
                        var enteredText = ctrlAttributes.ToString().Trim();
                        attributesXml = _customerAttributeParser.AddAttribute(attributesXml,
                            attribute, enteredText);
                    }

                    break;
                case AttributeControlType.Datepicker:
                case AttributeControlType.ColorSquares:
                case AttributeControlType.ImageSquares:
                case AttributeControlType.FileUpload:
                //not supported customer attributes
                default:
                    break;
            }
        }

        return attributesXml;
    }

    protected virtual async Task SecondAdminAccountExistsAsync(Customer customer)
    {
        var customers = await _customerService.GetAllCustomersAsync(customerRoleIds: [(await _customerService.GetCustomerRoleBySystemNameAsync(NopCustomerDefaults.AdministratorsRoleName)).Id]);

        return customers.Any(c => c.Active && c.Id != customer.Id);
    }

    #endregion

    #region Customers

    public virtual IActionResult Index()
    {
        return RedirectToAction("List");
    }

    public virtual async Task List()
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerSearchModelAsync(new CustomerSearchModel());

        return View(model);
    }

    [HttpPost]
    public virtual async Task CustomerList(CustomerSearchModel searchModel)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return await AccessDeniedDataTablesJson();

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerListModelAsync(searchModel);

        return Json(model);
    }

    public virtual async Task Create()
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerModelAsync(new CustomerModel(), null);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [FormValueRequired("save", "save-continue")]
    public virtual async Task Create(CustomerModel model, bool continueEditing, IFormCollection form)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        if (!string.IsNullOrWhiteSpace(model.Email) && await _customerService.GetCustomerByEmailAsync(model.Email) != null)
            ModelState.AddModelError(string.Empty, "Email is already registered");

        if (!string.IsNullOrWhiteSpace(model.Username) && _customerSettings.UsernamesEnabled &&
            await _customerService.GetCustomerByUsernameAsync(model.Username) != null)
        {
            ModelState.AddModelError(string.Empty, "Username is already registered");
        }

        //validate customer roles
        var allCustomerRoles = await _customerService.GetAllCustomerRolesAsync(true);
        var newCustomerRoles = new List();
        foreach (var customerRole in allCustomerRoles)
            if (model.SelectedCustomerRoleIds.Contains(customerRole.Id))
                newCustomerRoles.Add(customerRole);
        var customerRolesError = await ValidateCustomerRolesAsync(newCustomerRoles, new List());
        if (!string.IsNullOrEmpty(customerRolesError))
        {
            ModelState.AddModelError(string.Empty, customerRolesError);
            _notificationService.ErrorNotification(customerRolesError);
        }

        // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address
        if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == NopCustomerDefaults.RegisteredRoleName) != null &&
            !CommonHelper.IsValidEmail(model.Email))
        {
            ModelState.AddModelError(string.Empty, await _localizationService.GetResourceAsync("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"));

            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"));
        }

        //custom customer attributes
        var customerAttributesXml = await ParseCustomCustomerAttributesAsync(form);
        if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == NopCustomerDefaults.RegisteredRoleName) != null)
        {
            var customerAttributeWarnings = await _customerAttributeParser.GetAttributeWarningsAsync(customerAttributesXml);
            foreach (var error in customerAttributeWarnings)
            {
                ModelState.AddModelError(string.Empty, error);
            }
        }

        if (ModelState.IsValid)
        {
            //fill entity from model
            var customer = model.ToEntity();
            var currentStore = await _storeContext.GetCurrentStoreAsync();

            customer.CustomerGuid = Guid.NewGuid();
            customer.CreatedOnUtc = DateTime.UtcNow;
            customer.LastActivityDateUtc = DateTime.UtcNow;
            customer.RegisteredInStoreId = currentStore.Id;

            //form fields
            if (_dateTimeSettings.AllowCustomersToSetTimeZone)
                customer.TimeZoneId = model.TimeZoneId;
            if (_customerSettings.GenderEnabled)
                customer.Gender = model.Gender;
            if (_customerSettings.FirstNameEnabled)
                customer.FirstName = model.FirstName;
            if (_customerSettings.LastNameEnabled)
                customer.LastName = model.LastName;
            if (_customerSettings.DateOfBirthEnabled)
                customer.DateOfBirth = model.DateOfBirth;
            if (_customerSettings.CompanyEnabled)
                customer.Company = model.Company;
            if (_customerSettings.StreetAddressEnabled)
                customer.StreetAddress = model.StreetAddress;
            if (_customerSettings.StreetAddress2Enabled)
                customer.StreetAddress2 = model.StreetAddress2;
            if (_customerSettings.ZipPostalCodeEnabled)
                customer.ZipPostalCode = model.ZipPostalCode;
            if (_customerSettings.CityEnabled)
                customer.City = model.City;
            if (_customerSettings.CountyEnabled)
                customer.County = model.County;
            if (_customerSettings.CountryEnabled)
                customer.CountryId = model.CountryId;
            if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled)
                customer.StateProvinceId = model.StateProvinceId;
            if (_customerSettings.PhoneEnabled)
                customer.Phone = model.Phone;
            if (_customerSettings.FaxEnabled)
                customer.Fax = model.Fax;
            customer.CustomCustomerAttributesXML = customerAttributesXml;

            await _customerService.InsertCustomerAsync(customer);

            //newsletter subscriptions
            if (!string.IsNullOrEmpty(customer.Email))
            {
                var allStores = await _storeService.GetAllStoresAsync();
                foreach (var store in allStores)
                {
                    var newsletterSubscription = await _newsLetterSubscriptionService
                        .GetNewsLetterSubscriptionByEmailAndStoreIdAsync(customer.Email, store.Id);
                    if (model.SelectedNewsletterSubscriptionStoreIds != null &&
                        model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id))
                    {
                        //subscribed
                        if (newsletterSubscription == null)
                        {
                            await _newsLetterSubscriptionService.InsertNewsLetterSubscriptionAsync(new NewsLetterSubscription
                            {
                                NewsLetterSubscriptionGuid = Guid.NewGuid(),
                                Email = customer.Email,
                                Active = true,
                                StoreId = store.Id,
                                LanguageId = customer.LanguageId ?? store.DefaultLanguageId,
                                CreatedOnUtc = DateTime.UtcNow
                            });
                        }
                    }
                    else
                    {
                        //not subscribed
                        if (newsletterSubscription != null)
                        {
                            await _newsLetterSubscriptionService.DeleteNewsLetterSubscriptionAsync(newsletterSubscription);
                        }
                    }
                }
            }

            //password
            if (!string.IsNullOrWhiteSpace(model.Password))
            {
                var changePassRequest = new ChangePasswordRequest(model.Email, false, _customerSettings.DefaultPasswordFormat, model.Password);
                var changePassResult = await _customerRegistrationService.ChangePasswordAsync(changePassRequest);
                if (!changePassResult.Success)
                {
                    foreach (var changePassError in changePassResult.Errors)
                        _notificationService.ErrorNotification(changePassError);
                }
            }

            //customer roles
            foreach (var customerRole in newCustomerRoles)
            {
                //ensure that the current customer cannot add to "Administrators" system role if he's not an admin himself
                if (customerRole.SystemName == NopCustomerDefaults.AdministratorsRoleName && !await _customerService.IsAdminAsync(await _workContext.GetCurrentCustomerAsync()))
                    continue;

                await _customerService.AddCustomerRoleMappingAsync(new CustomerCustomerRoleMapping { CustomerId = customer.Id, CustomerRoleId = customerRole.Id });
            }

            await _customerService.UpdateCustomerAsync(customer);

            //ensure that a customer with a vendor associated is not in "Administrators" role
            //otherwise, he won't have access to other functionality in admin area
            if (await _customerService.IsAdminAsync(customer) && customer.VendorId > 0)
            {
                customer.VendorId = 0;
                await _customerService.UpdateCustomerAsync(customer);

                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminCouldNotbeVendor"));
            }

            //ensure that a customer in the Vendors role has a vendor account associated.
            //otherwise, he will have access to ALL products
            if (await _customerService.IsVendorAsync(customer) && customer.VendorId == 0)
            {
                var vendorRole = await _customerService.GetCustomerRoleBySystemNameAsync(NopCustomerDefaults.VendorsRoleName);
                await _customerService.RemoveCustomerRoleMappingAsync(customer, vendorRole);

                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated"));
            }

            //activity log
            await _customerActivityService.InsertActivityAsync("AddNewCustomer",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.AddNewCustomer"), customer.Id), customer);
            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Added"));

            if (!continueEditing)
                return RedirectToAction("List");

            return RedirectToAction("Edit", new { id = customer.Id });
        }

        //prepare model
        model = await _customerModelFactory.PrepareCustomerModelAsync(model, null, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    public virtual async Task Edit(int id)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(id);
        if (customer == null || customer.Deleted)
            return RedirectToAction("List");

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerModelAsync(null, customer);

        return View(model);
    }

    [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
    [FormValueRequired("save", "save-continue")]
    public virtual async Task Edit(CustomerModel model, bool continueEditing, IFormCollection form)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null || customer.Deleted)
            return RedirectToAction("List");

        //validate customer roles
        var allCustomerRoles = await _customerService.GetAllCustomerRolesAsync(true);
        var newCustomerRoles = new List();
        foreach (var customerRole in allCustomerRoles)
            if (model.SelectedCustomerRoleIds.Contains(customerRole.Id))
                newCustomerRoles.Add(customerRole);

        var customerRolesError = await ValidateCustomerRolesAsync(newCustomerRoles, await _customerService.GetCustomerRolesAsync(customer));

        if (!string.IsNullOrEmpty(customerRolesError))
        {
            ModelState.AddModelError(string.Empty, customerRolesError);
            _notificationService.ErrorNotification(customerRolesError);
        }

        // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address
        if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == NopCustomerDefaults.RegisteredRoleName) != null &&
            !CommonHelper.IsValidEmail(model.Email))
        {
            ModelState.AddModelError(string.Empty, await _localizationService.GetResourceAsync("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"));
            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"));
        }

        //custom customer attributes
        var customerAttributesXml = await ParseCustomCustomerAttributesAsync(form);
        if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == NopCustomerDefaults.RegisteredRoleName) != null)
        {
            var customerAttributeWarnings = await _customerAttributeParser.GetAttributeWarningsAsync(customerAttributesXml);
            foreach (var error in customerAttributeWarnings)
            {
                ModelState.AddModelError(string.Empty, error);
            }
        }

        if (ModelState.IsValid)
        {
            try
            {
                customer.AdminComment = model.AdminComment;
                customer.IsTaxExempt = model.IsTaxExempt;

                //prevent deactivation of the last active administrator
                if (!await _customerService.IsAdminAsync(customer) || model.Active || await SecondAdminAccountExistsAsync(customer))
                    customer.Active = model.Active;
                else
                    _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminAccountShouldExists.Deactivate"));

                //email
                if (!string.IsNullOrWhiteSpace(model.Email))
                    await _customerRegistrationService.SetEmailAsync(customer, model.Email, false);
                else
                    customer.Email = model.Email;

                //username
                if (_customerSettings.UsernamesEnabled)
                {
                    if (!string.IsNullOrWhiteSpace(model.Username))
                        await _customerRegistrationService.SetUsernameAsync(customer, model.Username);
                    else
                        customer.Username = model.Username;
                }

                //VAT number
                if (_taxSettings.EuVatEnabled)
                {
                    var prevVatNumber = customer.VatNumber;

                    customer.VatNumber = model.VatNumber;
                    //set VAT number status
                    if (!string.IsNullOrEmpty(model.VatNumber))
                    {
                        if (!model.VatNumber.Equals(prevVatNumber, StringComparison.InvariantCultureIgnoreCase))
                        {
                            customer.VatNumberStatusId = (int)(await _taxService.GetVatNumberStatusAsync(model.VatNumber)).vatNumberStatus;
                        }
                    }
                    else
                        customer.VatNumberStatusId = (int)VatNumberStatus.Empty;
                }

                //vendor
                customer.VendorId = model.VendorId;

                //form fields
                if (_dateTimeSettings.AllowCustomersToSetTimeZone)
                    customer.TimeZoneId = model.TimeZoneId;
                if (_customerSettings.GenderEnabled)
                    customer.Gender = model.Gender;
                if (_customerSettings.FirstNameEnabled)
                    customer.FirstName = model.FirstName;
                if (_customerSettings.LastNameEnabled)
                    customer.LastName = model.LastName;
                if (_customerSettings.DateOfBirthEnabled)
                    customer.DateOfBirth = model.DateOfBirth;
                if (_customerSettings.CompanyEnabled)
                    customer.Company = model.Company;
                if (_customerSettings.StreetAddressEnabled)
                    customer.StreetAddress = model.StreetAddress;
                if (_customerSettings.StreetAddress2Enabled)
                    customer.StreetAddress2 = model.StreetAddress2;
                if (_customerSettings.ZipPostalCodeEnabled)
                    customer.ZipPostalCode = model.ZipPostalCode;
                if (_customerSettings.CityEnabled)
                    customer.City = model.City;
                if (_customerSettings.CountyEnabled)
                    customer.County = model.County;
                if (_customerSettings.CountryEnabled)
                    customer.CountryId = model.CountryId;
                if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled)
                    customer.StateProvinceId = model.StateProvinceId;
                if (_customerSettings.PhoneEnabled)
                    customer.Phone = model.Phone;
                if (_customerSettings.FaxEnabled)
                    customer.Fax = model.Fax;

                //custom customer attributes
                customer.CustomCustomerAttributesXML = customerAttributesXml;

                //newsletter subscriptions
                if (!string.IsNullOrEmpty(customer.Email))
                {
                    var allStores = await _storeService.GetAllStoresAsync();
                    foreach (var store in allStores)
                    {
                        var newsletterSubscription = await _newsLetterSubscriptionService
                            .GetNewsLetterSubscriptionByEmailAndStoreIdAsync(customer.Email, store.Id);
                        if (model.SelectedNewsletterSubscriptionStoreIds != null &&
                            model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id))
                        {
                            //subscribed
                            if (newsletterSubscription == null)
                            {
                                await _newsLetterSubscriptionService.InsertNewsLetterSubscriptionAsync(new NewsLetterSubscription
                                {
                                    NewsLetterSubscriptionGuid = Guid.NewGuid(),
                                    Email = customer.Email,
                                    Active = true,
                                    StoreId = store.Id,
                                    LanguageId = customer.LanguageId ?? store.DefaultLanguageId,
                                    CreatedOnUtc = DateTime.UtcNow
                                });
                            }
                        }
                        else
                        {
                            //not subscribed
                            if (newsletterSubscription != null)
                            {
                                await _newsLetterSubscriptionService.DeleteNewsLetterSubscriptionAsync(newsletterSubscription);
                            }
                        }
                    }
                }

                var currentCustomerRoleIds = await _customerService.GetCustomerRoleIdsAsync(customer, true);

                //customer roles
                foreach (var customerRole in allCustomerRoles)
                {
                    //ensure that the current customer cannot add/remove to/from "Administrators" system role
                    //if he's not an admin himself
                    if (customerRole.SystemName == NopCustomerDefaults.AdministratorsRoleName &&
                        !await _customerService.IsAdminAsync(await _workContext.GetCurrentCustomerAsync()))
                        continue;

                    if (model.SelectedCustomerRoleIds.Contains(customerRole.Id))
                    {
                        //new role
                        if (currentCustomerRoleIds.All(roleId => roleId != customerRole.Id))
                            await _customerService.AddCustomerRoleMappingAsync(new CustomerCustomerRoleMapping { CustomerId = customer.Id, CustomerRoleId = customerRole.Id });
                    }
                    else
                    {
                        //prevent attempts to delete the administrator role from the user, if the user is the last active administrator
                        if (customerRole.SystemName == NopCustomerDefaults.AdministratorsRoleName && !await SecondAdminAccountExistsAsync(customer))
                        {
                            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminAccountShouldExists.DeleteRole"));
                            continue;
                        }

                        //remove role
                        if (currentCustomerRoleIds.Any(roleId => roleId == customerRole.Id))
                            await _customerService.RemoveCustomerRoleMappingAsync(customer, customerRole);
                    }
                }

                await _customerService.UpdateCustomerAsync(customer);

                //ensure that a customer with a vendor associated is not in "Administrators" role
                //otherwise, he won't have access to the other functionality in admin area
                if (await _customerService.IsAdminAsync(customer) && customer.VendorId > 0)
                {
                    customer.VendorId = 0;
                    await _customerService.UpdateCustomerAsync(customer);
                    _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminCouldNotbeVendor"));
                }

                //ensure that a customer in the Vendors role has a vendor account associated.
                //otherwise, he will have access to ALL products
                if (await _customerService.IsVendorAsync(customer) && customer.VendorId == 0)
                {
                    var vendorRole = await _customerService.GetCustomerRoleBySystemNameAsync(NopCustomerDefaults.VendorsRoleName);
                    await _customerService.RemoveCustomerRoleMappingAsync(customer, vendorRole);

                    _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated"));
                }

                //activity log
                await _customerActivityService.InsertActivityAsync("EditCustomer",
                    string.Format(await _localizationService.GetResourceAsync("ActivityLog.EditCustomer"), customer.Id), customer);

                _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Updated"));

                if (!continueEditing)
                    return RedirectToAction("List");

                return RedirectToAction("Edit", new { id = customer.Id });
            }
            catch (Exception exc)
            {
                _notificationService.ErrorNotification(exc.Message);
            }
        }

        //prepare model
        model = await _customerModelFactory.PrepareCustomerModelAsync(model, customer, true);

        //if we got this far, something failed, redisplay form
        return View(model);
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("changepassword")]
    public virtual async Task ChangePassword(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        //ensure that the current customer cannot change passwords of "Administrators" if he's not an admin himself
        if (await _customerService.IsAdminAsync(customer) && !await _customerService.IsAdminAsync(await _workContext.GetCurrentCustomerAsync()))
        {
            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.OnlyAdminCanChangePassword"));
            return RedirectToAction("Edit", new { id = customer.Id });
        }

        var changePassRequest = new ChangePasswordRequest(customer.Email,
            false, _customerSettings.DefaultPasswordFormat, model.Password);
        var changePassResult = await _customerRegistrationService.ChangePasswordAsync(changePassRequest);
        if (changePassResult.Success)
            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.PasswordChanged"));
        else
            foreach (var error in changePassResult.Errors)
                _notificationService.ErrorNotification(error);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("markVatNumberAsValid")]
    public virtual async Task MarkVatNumberAsValid(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        customer.VatNumberStatusId = (int)VatNumberStatus.Valid;
        await _customerService.UpdateCustomerAsync(customer);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("markVatNumberAsInvalid")]
    public virtual async Task MarkVatNumberAsInvalid(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        customer.VatNumberStatusId = (int)VatNumberStatus.Invalid;
        await _customerService.UpdateCustomerAsync(customer);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("remove-affiliate")]
    public virtual async Task RemoveAffiliate(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        customer.AffiliateId = 0;
        await _customerService.UpdateCustomerAsync(customer);

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost]
    public virtual async Task RemoveBindMFA(int id)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(id);
        if (customer == null)
            return RedirectToAction("List");

        await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.SelectedMultiFactorAuthenticationProviderAttribute, string.Empty);

        //raise event       
        await _eventPublisher.PublishAsync(new CustomerChangeMultiFactorAuthenticationProviderEvent(customer));

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.UnbindMFAProvider"));

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost]
    public virtual async Task Delete(int id)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(id);
        if (customer == null)
            return RedirectToAction("List");

        try
        {
            //prevent attempts to delete the user, if it is the last active administrator
            if (await _customerService.IsAdminAsync(customer) && !await SecondAdminAccountExistsAsync(customer))
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminAccountShouldExists.DeleteAdministrator"));
                return RedirectToAction("Edit", new { id = customer.Id });
            }

            //ensure that the current customer cannot delete "Administrators" if he's not an admin himself
            if (await _customerService.IsAdminAsync(customer) && !await _customerService.IsAdminAsync(await _workContext.GetCurrentCustomerAsync()))
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.OnlyAdminCanDeleteAdmin"));
                return RedirectToAction("Edit", new { id = customer.Id });
            }

            //delete
            await _customerService.DeleteCustomerAsync(customer);

            //remove newsletter subscription (if exists)
            foreach (var store in await _storeService.GetAllStoresAsync())
            {
                var subscription = await _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreIdAsync(customer.Email, store.Id);
                if (subscription != null)
                    await _newsLetterSubscriptionService.DeleteNewsLetterSubscriptionAsync(subscription);
            }

            //activity log
            await _customerActivityService.InsertActivityAsync("DeleteCustomer",
                string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteCustomer"), customer.Id), customer);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Deleted"));

            return RedirectToAction("List");
        }
        catch (Exception exc)
        {
            _notificationService.ErrorNotification(exc.Message);
            return RedirectToAction("Edit", new { id = customer.Id });
        }
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("impersonate")]
    public virtual async Task Impersonate(int id)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.AllowCustomerImpersonation))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(id);
        if (customer == null)
            return RedirectToAction("List");

        if (!customer.Active)
        {
            _notificationService.WarningNotification(
                await _localizationService.GetResourceAsync("Admin.Customers.Customers.Impersonate.Inactive"));
            return RedirectToAction("Edit", customer.Id);
        }

        //ensure that a non-admin user cannot impersonate as an administrator
        //otherwise, that user can simply impersonate as an administrator and gain additional administrative privileges
        var currentCustomer = await _workContext.GetCurrentCustomerAsync();
        if (!await _customerService.IsAdminAsync(currentCustomer) && await _customerService.IsAdminAsync(customer))
        {
            _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.NonAdminNotImpersonateAsAdminError"));
            return RedirectToAction("Edit", customer.Id);
        }

        //activity log
        await _customerActivityService.InsertActivityAsync("Impersonation.Started",
            string.Format(await _localizationService.GetResourceAsync("ActivityLog.Impersonation.Started.StoreOwner"), customer.Email, customer.Id), customer);
        await _customerActivityService.InsertActivityAsync(customer, "Impersonation.Started",
            string.Format(await _localizationService.GetResourceAsync("ActivityLog.Impersonation.Started.Customer"), currentCustomer.Email, currentCustomer.Id), currentCustomer);

        //ensure login is not required
        customer.RequireReLogin = false;
        await _customerService.UpdateCustomerAsync(customer);
        await _genericAttributeService.SaveAttributeAsync(currentCustomer, NopCustomerDefaults.ImpersonatedCustomerIdAttribute, customer.Id);

        return RedirectToAction("Index", "Home", new { area = string.Empty });
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("send-welcome-message")]
    public virtual async Task SendWelcomeMessage(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        await _workflowMessageService.SendCustomerWelcomeMessageAsync(customer, (await _workContext.GetWorkingLanguageAsync()).Id);

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.SendWelcomeMessage.Success"));

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    [HttpPost, ActionName("Edit")]
    [FormValueRequired("resend-activation-message")]
    public virtual async Task ReSendActivationMessage(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        //email validation message
        await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.AccountActivationTokenAttribute, Guid.NewGuid().ToString());
        await _workflowMessageService.SendCustomerEmailValidationMessageAsync(customer, (await _workContext.GetWorkingLanguageAsync()).Id);

        _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.ReSendActivationMessage.Success"));

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    public virtual async Task SendEmail(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        try
        {
            if (string.IsNullOrWhiteSpace(customer.Email))
                throw new NopException("Customer email is empty");
            if (!CommonHelper.IsValidEmail(customer.Email))
                throw new NopException("Customer email is not valid");
            if (string.IsNullOrWhiteSpace(model.SendEmail.Subject))
                throw new NopException("Email subject is empty");
            if (string.IsNullOrWhiteSpace(model.SendEmail.Body))
                throw new NopException("Email body is empty");

            var emailAccount = (await _emailAccountService.GetEmailAccountByIdAsync(_emailAccountSettings.DefaultEmailAccountId)
                ?? (await _emailAccountService.GetAllEmailAccountsAsync()).FirstOrDefault())
                ?? throw new NopException("Email account can't be loaded");
            var email = new QueuedEmail
            {
                Priority = QueuedEmailPriority.High,
                EmailAccountId = emailAccount.Id,
                FromName = emailAccount.DisplayName,
                From = emailAccount.Email,
                ToName = await _customerService.GetCustomerFullNameAsync(customer),
                To = customer.Email,
                Subject = model.SendEmail.Subject,
                Body = model.SendEmail.Body,
                CreatedOnUtc = DateTime.UtcNow,
                DontSendBeforeDateUtc = model.SendEmail.SendImmediately || !model.SendEmail.DontSendBeforeDate.HasValue ?
                    null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SendEmail.DontSendBeforeDate.Value)
            };
            await _queuedEmailService.InsertQueuedEmailAsync(email);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.SendEmail.Queued"));
        }
        catch (Exception exc)
        {
            _notificationService.ErrorNotification(exc.Message);
        }

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    public virtual async Task SendPm(CustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.Id);
        if (customer == null)
            return RedirectToAction("List");

        try
        {
            if (!_forumSettings.AllowPrivateMessages)
                throw new NopException("Private messages are disabled");
            if (await _customerService.IsGuestAsync(customer))
                throw new NopException("Customer should be registered");
            if (string.IsNullOrWhiteSpace(model.SendPm.Subject))
                throw new NopException(await _localizationService.GetResourceAsync("PrivateMessages.SubjectCannotBeEmpty"));
            if (string.IsNullOrWhiteSpace(model.SendPm.Message))
                throw new NopException(await _localizationService.GetResourceAsync("PrivateMessages.MessageCannotBeEmpty"));

            var store = await _storeContext.GetCurrentStoreAsync();

            var privateMessage = new PrivateMessage
            {
                StoreId = store.Id,
                ToCustomerId = customer.Id,
                FromCustomerId = customer.Id,
                Subject = model.SendPm.Subject,
                Text = model.SendPm.Message,
                IsDeletedByAuthor = false,
                IsDeletedByRecipient = false,
                IsRead = false,
                CreatedOnUtc = DateTime.UtcNow
            };

            await _forumService.InsertPrivateMessageAsync(privateMessage);

            _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.SendPM.Sent"));
        }
        catch (Exception exc)
        {
            _notificationService.ErrorNotification(exc.Message);
        }

        return RedirectToAction("Edit", new { id = customer.Id });
    }

    #endregion

    #region Reward points history

    [HttpPost]
    public virtual async Task RewardPointsHistorySelect(CustomerRewardPointsSearchModel searchModel)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return await AccessDeniedDataTablesJson();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId)
            ?? throw new ArgumentException("No customer found with the specified id");

        //prepare model
        var model = await _customerModelFactory.PrepareRewardPointsListModelAsync(searchModel, customer);

        return Json(model);
    }

    public virtual async Task RewardPointsHistoryAdd(AddRewardPointsToCustomerModel model)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return await AccessDeniedDataTablesJson();

        //prevent adding a new row with zero value
        if (model.Points == 0)
            return ErrorJson(await _localizationService.GetResourceAsync("Admin.Customers.Customers.RewardPoints.AddingZeroValueNotAllowed"));

        //prevent adding negative point validity for point reduction
        if (model.Points < 0 && model.PointsValidity.HasValue)
            return ErrorJson(await _localizationService.GetResourceAsync("Admin.Customers.Customers.RewardPoints.Fields.AddNegativePointsValidity"));

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.CustomerId);
        if (customer == null)
            return ErrorJson("Customer cannot be loaded");

        //check whether delay is set
        DateTime? activatingDate = null;
        if (!model.ActivatePointsImmediately && model.ActivationDelay > 0)
        {
            var delayPeriod = (RewardPointsActivatingDelayPeriod)model.ActivationDelayPeriodId;
            var delayInHours = delayPeriod.ToHours(model.ActivationDelay);
            activatingDate = DateTime.UtcNow.AddHours(delayInHours);
        }

        //whether points validity is set
        DateTime? endDate = null;
        if (model.PointsValidity > 0)
            endDate = (activatingDate ?? DateTime.UtcNow).AddDays(model.PointsValidity.Value);

        //add reward points
        await _rewardPointService.AddRewardPointsHistoryEntryAsync(customer, model.Points, model.StoreId, model.Message,
            activatingDate: activatingDate, endDate: endDate);

        return Json(new { Result = true });
    }

    #endregion

    #region Addresses

    [HttpPost]
    public virtual async Task AddressesSelect(CustomerAddressSearchModel searchModel)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return await AccessDeniedDataTablesJson();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId)
            ?? throw new ArgumentException("No customer found with the specified id");

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerAddressListModelAsync(searchModel, customer);

        return Json(model);
    }

    [HttpPost]
    public virtual async Task AddressDelete(int id, int customerId)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return await AccessDeniedDataTablesJson();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(customerId)
            ?? throw new ArgumentException("No customer found with the specified id", nameof(customerId));

        //try to get an address with the specified id
        var address = await _customerService.GetCustomerAddressAsync(customer.Id, id);

        if (address == null)
            return Content("No address found with the specified id");

        await _customerService.RemoveCustomerAddressAsync(customer, address);
        await _customerService.UpdateCustomerAsync(customer);

        //now delete the address record
        await _addressService.DeleteAddressAsync(address);

        return new NullJsonResult();
    }

    public virtual async Task AddressCreate(int customerId)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(customerId);
        if (customer == null)
            return RedirectToAction("List");

        //prepare model
        var model = await _customerModelFactory.PrepareCustomerAddressModelAsync(new CustomerAddressModel(), customer, null);

        return View(model);
    }

    [HttpPost]
    public virtual async Task AddressCreate(CustomerAddressModel model, IFormCollection form)
    {
        if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers))
            return AccessDeniedView();

        //try to get a customer with the specified id
        var customer = await _customerService.GetCustomerByIdAsync(model.CustomerId);
        if (customer == null)
            return RedirectToAction("List");

        //custom address attributes
        var customAttributes = await _addressAttributeParser.ParseCustomAttributesAsync(form, NopCommonDefaults.AddressAttributeControlName);
        var customAttributeWarnings = await _addressAttributeParser.GetAttributeWarningsAsync(customAttributes);
        foreach (var error in customAttributeWarnings)
        {
            ModelState.AddModelError(string.Empty, error);
        }

        if (ModelState.IsValid)
        {
            var address = model.Address.ToEntity
(); address.CustomAttributes = customAttributes; address.CreatedOnUtc = DateTime.UtcNow; //some validation if (address.CountryId == 0) address.CountryId = null; if (address.StateProvinceId == 0) address.StateProvinceId = null; await _addressService.InsertAddressAsync(address); await _customerService.InsertCustomerAddressAsync(customer, address); _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Addresses.Added")); return RedirectToAction("AddressEdit", new { addressId = address.Id, customerId = model.CustomerId }); } //prepare model model = await _customerModelFactory.PrepareCustomerAddressModelAsync(model, customer, null, true); //if we got this far, something failed, redisplay form return View(model); } public virtual async Task AddressEdit(int addressId, int customerId) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(customerId); if (customer == null) return RedirectToAction("List"); //try to get an address with the specified id var address = await _addressService.GetAddressByIdAsync(addressId); if (address == null) return RedirectToAction("Edit", new { id = customer.Id }); //prepare model var model = await _customerModelFactory.PrepareCustomerAddressModelAsync(null, customer, address); return View(model); } [HttpPost] public virtual async Task AddressEdit(CustomerAddressModel model, IFormCollection form) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(model.CustomerId); if (customer == null) return RedirectToAction("List"); //try to get an address with the specified id var address = await _addressService.GetAddressByIdAsync(model.Address.Id); if (address == null) return RedirectToAction("Edit", new { id = customer.Id }); //custom address attributes var customAttributes = await _addressAttributeParser.ParseCustomAttributesAsync(form, NopCommonDefaults.AddressAttributeControlName); var customAttributeWarnings = await _addressAttributeParser.GetAttributeWarningsAsync(customAttributes); foreach (var error in customAttributeWarnings) { ModelState.AddModelError(string.Empty, error); } if (ModelState.IsValid) { address = model.Address.ToEntity(address); address.CustomAttributes = customAttributes; await _addressService.UpdateAddressAsync(address); _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Addresses.Updated")); return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, customerId = model.CustomerId }); } //prepare model model = await _customerModelFactory.PrepareCustomerAddressModelAsync(model, customer, address, true); //if we got this far, something failed, redisplay form return View(model); } #endregion #region Orders [HttpPost] public virtual async Task OrderList(CustomerOrderSearchModel searchModel) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return await AccessDeniedDataTablesJson(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId) ?? throw new ArgumentException("No customer found with the specified id"); //prepare model var model = await _customerModelFactory.PrepareCustomerOrderListModelAsync(searchModel, customer); return Json(model); } #endregion #region Customer public virtual async Task LoadCustomerStatistics(string period) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return Content(string.Empty); var result = new List(); var nowDt = await _dateTimeHelper.ConvertToUserTimeAsync(DateTime.Now); var timeZone = await _dateTimeHelper.GetCurrentTimeZoneAsync(); var searchCustomerRoleIds = new[] { (await _customerService.GetCustomerRoleBySystemNameAsync(NopCustomerDefaults.RegisteredRoleName)).Id }; var culture = new CultureInfo((await _workContext.GetWorkingLanguageAsync()).LanguageCulture); switch (period) { case "year": //year statistics var yearAgoDt = nowDt.AddYears(-1).AddMonths(1); var searchYearDateUser = new DateTime(yearAgoDt.Year, yearAgoDt.Month, 1); for (var i = 0; i <= 12; i++) { result.Add(new { date = searchYearDateUser.Date.ToString("Y", culture), value = (await _customerService.GetAllCustomersAsync( createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser, timeZone), createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser.AddMonths(1), timeZone), customerRoleIds: searchCustomerRoleIds, pageIndex: 0, pageSize: 1, getOnlyTotalCount: true)).TotalCount.ToString() }); searchYearDateUser = searchYearDateUser.AddMonths(1); } break; case "month": //month statistics var monthAgoDt = nowDt.AddDays(-30); var searchMonthDateUser = new DateTime(monthAgoDt.Year, monthAgoDt.Month, monthAgoDt.Day); for (var i = 0; i <= 30; i++) { result.Add(new { date = searchMonthDateUser.Date.ToString("M", culture), value = (await _customerService.GetAllCustomersAsync( createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser, timeZone), createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser.AddDays(1), timeZone), customerRoleIds: searchCustomerRoleIds, pageIndex: 0, pageSize: 1, getOnlyTotalCount: true)).TotalCount.ToString() }); searchMonthDateUser = searchMonthDateUser.AddDays(1); } break; case "week": default: //week statistics var weekAgoDt = nowDt.AddDays(-7); var searchWeekDateUser = new DateTime(weekAgoDt.Year, weekAgoDt.Month, weekAgoDt.Day); for (var i = 0; i <= 7; i++) { result.Add(new { date = searchWeekDateUser.Date.ToString("d dddd", culture), value = (await _customerService.GetAllCustomersAsync( createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser, timeZone), createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser.AddDays(1), timeZone), customerRoleIds: searchCustomerRoleIds, pageIndex: 0, pageSize: 1, getOnlyTotalCount: true)).TotalCount.ToString() }); searchWeekDateUser = searchWeekDateUser.AddDays(1); } break; } return Json(result); } #endregion #region Current shopping cart/ wishlist [HttpPost] public virtual async Task GetCartList(CustomerShoppingCartSearchModel searchModel) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return await AccessDeniedDataTablesJson(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId) ?? throw new ArgumentException("No customer found with the specified id"); //prepare model var model = await _customerModelFactory.PrepareCustomerShoppingCartListModelAsync(searchModel, customer); return Json(model); } #endregion #region Activity log [HttpPost] public virtual async Task ListActivityLog(CustomerActivityLogSearchModel searchModel) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return await AccessDeniedDataTablesJson(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId) ?? throw new ArgumentException("No customer found with the specified id"); //prepare model var model = await _customerModelFactory.PrepareCustomerActivityLogListModelAsync(searchModel, customer); return Json(model); } #endregion #region Back in stock subscriptions [HttpPost] public virtual async Task BackInStockSubscriptionList(CustomerBackInStockSubscriptionSearchModel searchModel) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return await AccessDeniedDataTablesJson(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(searchModel.CustomerId) ?? throw new ArgumentException("No customer found with the specified id"); //prepare model var model = await _customerModelFactory.PrepareCustomerBackInStockSubscriptionListModelAsync(searchModel, customer); return Json(model); } #endregion #region GDPR public virtual async Task GdprLog() { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); //prepare model var model = await _customerModelFactory.PrepareGdprLogSearchModelAsync(new GdprLogSearchModel()); return View(model); } [HttpPost] public virtual async Task GdprLogList(GdprLogSearchModel searchModel) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return await AccessDeniedDataTablesJson(); //prepare model var model = await _customerModelFactory.PrepareGdprLogListModelAsync(searchModel); return Json(model); } [HttpPost] public virtual async Task GdprDelete(int id) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(id); if (customer == null) return RedirectToAction("List"); if (!_gdprSettings.GdprEnabled) return RedirectToAction("List"); try { //prevent attempts to delete the user, if it is the last active administrator if (await _customerService.IsAdminAsync(customer) && !await SecondAdminAccountExistsAsync(customer)) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.AdminAccountShouldExists.DeleteAdministrator")); return RedirectToAction("Edit", new { id = customer.Id }); } //ensure that the current customer cannot delete "Administrators" if he's not an admin himself if (await _customerService.IsAdminAsync(customer) && !await _customerService.IsAdminAsync(await _workContext.GetCurrentCustomerAsync())) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.OnlyAdminCanDeleteAdmin")); return RedirectToAction("Edit", new { id = customer.Id }); } //delete await _gdprService.PermanentDeleteCustomerAsync(customer); //activity log await _customerActivityService.InsertActivityAsync("DeleteCustomer", string.Format(await _localizationService.GetResourceAsync("ActivityLog.DeleteCustomer"), customer.Id), customer); _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Deleted")); return RedirectToAction("List"); } catch (Exception exc) { _notificationService.ErrorNotification(exc.Message); return RedirectToAction("Edit", new { id = customer.Id }); } } public virtual async Task GdprExport(int id) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); //try to get a customer with the specified id var customer = await _customerService.GetCustomerByIdAsync(id); if (customer == null) return RedirectToAction("List"); try { //log //_gdprService.InsertLog(customer, 0, GdprRequestType.ExportData, await _localizationService.GetResource("Gdpr.Exported")); //export //export var store = await _storeContext.GetCurrentStoreAsync(); var bytes = await _exportManager.ExportCustomerGdprInfoToXlsxAsync(customer, store.Id); return File(bytes, MimeTypes.TextXlsx, $"customerdata-{customer.Id}.xlsx"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("Edit", new { id = customer.Id }); } } #endregion #region Export / Import [HttpPost, ActionName("ExportExcel")] [FormValueRequired("exportexcel-all")] public virtual async Task ExportExcelAll(CustomerSearchModel model) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); var customers = await _customerService.GetAllCustomersAsync(customerRoleIds: model.SelectedCustomerRoleIds.ToArray(), email: model.SearchEmail, username: model.SearchUsername, firstName: model.SearchFirstName, lastName: model.SearchLastName, dayOfBirth: int.TryParse(model.SearchDayOfBirth, out var dayOfBirth) ? dayOfBirth : 0, monthOfBirth: int.TryParse(model.SearchMonthOfBirth, out var monthOfBirth) ? monthOfBirth : 0, company: model.SearchCompany, phone: model.SearchPhone, zipPostalCode: model.SearchZipPostalCode); try { var bytes = await _exportManager.ExportCustomersToXlsxAsync(customers); return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] public virtual async Task ExportExcelSelected(string selectedIds) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); var customers = new List(); if (selectedIds != null) { var ids = selectedIds .Split(_separator, StringSplitOptions.RemoveEmptyEntries) .Select(x => Convert.ToInt32(x)) .ToArray(); customers.AddRange(await _customerService.GetCustomersByIdsAsync(ids)); } try { var bytes = await _exportManager.ExportCustomersToXlsxAsync(customers); return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost, ActionName("ExportXML")] [FormValueRequired("exportxml-all")] public virtual async Task ExportXmlAll(CustomerSearchModel model) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); var customers = await _customerService.GetAllCustomersAsync(customerRoleIds: model.SelectedCustomerRoleIds.ToArray(), email: model.SearchEmail, username: model.SearchUsername, firstName: model.SearchFirstName, lastName: model.SearchLastName, dayOfBirth: int.TryParse(model.SearchDayOfBirth, out var dayOfBirth) ? dayOfBirth : 0, monthOfBirth: int.TryParse(model.SearchMonthOfBirth, out var monthOfBirth) ? monthOfBirth : 0, company: model.SearchCompany, phone: model.SearchPhone, zipPostalCode: model.SearchZipPostalCode); try { var xml = await _exportManager.ExportCustomersToXmlAsync(customers); return File(Encoding.UTF8.GetBytes(xml), "application/xml", "customers.xml"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] public virtual async Task ExportXmlSelected(string selectedIds) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); var customers = new List(); if (selectedIds != null) { var ids = selectedIds .Split(_separator, StringSplitOptions.RemoveEmptyEntries) .Select(x => Convert.ToInt32(x)) .ToArray(); customers.AddRange(await _customerService.GetCustomersByIdsAsync(ids)); } try { var xml = await _exportManager.ExportCustomersToXmlAsync(customers); return File(Encoding.UTF8.GetBytes(xml), "application/xml", "customers.xml"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } [HttpPost] public virtual async Task ImportExcel(IFormFile importexcelfile) { if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); if (await _workContext.GetCurrentVendorAsync() != null) //a vendor can not import customer return AccessDeniedView(); try { if ((importexcelfile?.Length ?? 0) > 0) await _importManager.ImportCustomersFromXlsxAsync(importexcelfile.OpenReadStream()); else { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.UploadFile")); return RedirectToAction("List"); } _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Customers.Customers.Imported")); return RedirectToAction("List"); } catch (Exception exc) { await _notificationService.ErrorNotificationAsync(exc); return RedirectToAction("List"); } } #endregion }