using System.Linq.Expressions; using Fake.Application; using Fake.Rbac.Application.Dtos.Role; using Fake.Rbac.Application.Dtos.User; using Fake.Rbac.Application.Services; using Fake.Rbac.Infrastructure; using Fake.Security.Claims; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Oms.Application.Dtos.Customers; using Oms.Application.Dtos.Shared; using Oms.Application.Rbac; using Oms.Application.Shared; using Oms.Domain.CustomersAggregate.Entities; using Oms.Domain.CustomersAggregate.Enums; using Oms.Domain.CustomersAggregate.Repositories; namespace Oms.Application.Services.Customers; /// /// 客户 API 服务。 /// [ApiExplorerSettings(GroupName = "OMS-Customer")] public class CustomerService : ApplicationService { private const string CustomerRoleCode = "Customer"; private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService(); private UserService UserService => LazyServiceProvider.GetRequiredService(); private RoleService RoleService => LazyServiceProvider.GetRequiredService(); private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService(); private OmsRbacUserDisplayNameResolver UserDisplayNameResolver => LazyServiceProvider.GetRequiredService(); private CustomerServiceAdvisorResolver ServiceAdvisorResolver => LazyServiceProvider.GetRequiredService(); /// /// 查询客户列表。 /// /// 入参:客户分页查询条件。 /// 客户分页结果,包含列表数据和总记录数。 [HttpGet("/customer/list")] public async Task> GetPaginatedListAsync([FromQuery] CustomerPaginatedInput input) { var predicate = BuildPaginatedPredicate(input); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var entities = await CustomerRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false }); var totalCount = await CustomerRepository.CountAsync(predicate); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync( entities.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId })); var items = entities.Select(entity => { var output = ObjectMapper.Map(entity); output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap); output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap); output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime); return output; }).ToList(); return new OmsPageResult { Items = items, Total = totalCount }; } /// /// 查询客户详情。 /// /// 入参:目标主键。 /// 客户详情。 [HttpGet("/customer/get")] public async Task GetByIdAsync([FromQuery] int id) { var entity = await GetCustomerOrThrowAsync(id); var output = ObjectMapper.Map(entity); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync( [entity.CreateUserId, entity.UpdateUserId]); output.CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.CreateUserId, auditUserNameMap); output.UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(entity.UpdateUserId, auditUserNameMap); output.UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime); return output; } /// /// 查询当前登录客户所属顾问信息。 /// /// 专属顾问姓名、微信号、微信二维码。 [HttpGet("/customer/service-advisor")] public async Task GetServiceAdvisorAsync(CancellationToken cancellationToken = default) { var userId = CurrentUser.GetId(); if (userId <= 0) { return new CustomerServiceAdvisorOutput(); } var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.UserId == userId, cancellationToken); if (customer == null || string.IsNullOrWhiteSpace(customer.CustomerService)) { return new CustomerServiceAdvisorOutput(); } return await ServiceAdvisorResolver.ResolveAsync(customer.CustomerService, cancellationToken); } /// /// 新增客户。 /// /// 入参:客户创建参数。 /// 无返回体,仅完成新增客户。 [HttpPost("/customer/create")] public async Task CreateAsync(CreateCustomerInput input) { var normalized = await NormalizeCreateCustomerInputAsync(input, "客户编号", "客户名称"); await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email); var customerRoleId = await GetCustomerRoleIdOrThrowAsync(); var user = await CreateRbacUserAsync(input, normalized); try { await UserService.AssignRolesAsync(user.Id, [customerRoleId]); var userId = await GetRbacUserMapIdOrThrowAsync(user.Id); var entity = BuildCustomerEntity(input, normalized, null, userId); await CustomerRepository.InsertAsync(entity); } catch { await TryRollbackRbacUserAsync(user.Id); throw; } } /// /// 创建子客户。 /// /// 入参:子客户基础资料、登录密码和授权权限编码列表。 /// 无返回体,仅完成子客户创建和RBAC用户、角色绑定。 [HttpPost("/customer/sub/create")] public async Task CreateSubCustomerAsync([FromBody] CreateSubCustomerInput input) { var mainCustomer = await GetCurrentMainCustomerOrThrowAsync(); ValidateCustomerStatus(mainCustomer); var normalized = await NormalizeCreateCustomerInputAsync(input, "子客户编号", "子客户名称"); var permissions = await NormalizeAndValidateSubCustomerPermissionsAsync(input.Permissions); await EnsureCustomerIdentityUniqueAsync(normalized.CustomerCode, normalized.Phone, normalized.Email); var user = await CreateRbacUserAsync(input, normalized); Guid? roleId = null; try { var userId = await GetRbacUserMapIdOrThrowAsync(user.Id); roleId = await CreateSubCustomerRoleAsync(userId, normalized.CustomerName, permissions); await UserService.AssignRolesAsync(user.Id, [roleId.Value]); var entity = BuildCustomerEntity(input, normalized, mainCustomer.Id, userId); await CustomerRepository.InsertAsync(entity); } catch { if (roleId.HasValue) { await TryRollbackRbacRoleAsync(roleId.Value); } await TryRollbackRbacUserAsync(user.Id); throw; } } /// /// 更新客户。 /// /// 入参:客户更新参数。 /// 无返回体,仅完成更新客户。 [HttpPost("/customer/update")] public async Task UpdateAsync(UpdateCustomerInput input) { var entity = await GetCustomerOrThrowAsync(input.Id); var customerCode = NormalizeRequiredText(input.CustomerCode, "客户编号"); var customerName = NormalizeRequiredText(input.CustomerName, "客户名称"); var phone = NormalizeRequiredText(input.Phone, "手机号"); var email = NormalizeOptionalText(input.Email); await EnsureCustomerIdentityUniqueForUpdateAsync(entity.Id, customerCode, phone, email); entity.CustomerCode = customerCode; entity.CustomerName = customerName; entity.Phone = phone; entity.Email = email; entity.WechatAvatar = NormalizeOptionalText(input.WechatAvatar); entity.WechatNickname = NormalizeOptionalText(input.WechatNickname); entity.Balance = input.Balance; entity.TotalQuota = input.TotalQuota; entity.UsedQuota = input.UsedQuota; entity.Status = input.Status; entity.Remark = NormalizeOptionalText(input.Remark); entity.Salesman = NormalizeOptionalText(input.Salesman); entity.CustomerService = NormalizeOptionalText(input.CustomerService); entity.CustomerGroup = input.CustomerGroup; await CustomerRepository.UpdateAsync(entity); } /// /// 删除客户。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除客户。 [HttpPost("/customer/delete")] public async Task DeleteAsync(IdInput input) { var entity = await GetCustomerOrThrowAsync(input.Id); if (!entity.ParentCustomerId.HasValue && await CustomerRepository.AnyAsync(x => x.ParentCustomerId == entity.Id)) { throw new BusinessException("存在子客户,请先删除子客户"); } await CustomerRepository.DeleteAsync(entity); await DeleteLinkedRbacUserAsync(entity); } private async Task DeleteLinkedRbacUserAsync(Customer entity) { var userGuid = await GetRbacUserGuidByMapIdAsync(entity.UserId); if (userGuid.HasValue && userGuid.Value != Guid.Empty) { await UserService.DeleteAsync(userGuid.Value); } if (entity.ParentCustomerId.HasValue) { await TryDeleteSubCustomerRoleAsync(entity.UserId); } } private async Task GetRbacUserGuidByMapIdAsync(int mapId) { return await RbacDbContext.Database.SqlQuery( $"select `Id` as `Value` from `user` where `userid` = {mapId} and `IsDeleted` = 0") .FirstOrDefaultAsync(); } private async Task TryDeleteSubCustomerRoleAsync(int userId) { try { var roles = await RoleService.GetAllRolesAsync(); var role = roles.FirstOrDefault(x => string.Equals(x.Code, $"CustomerSub_{userId}", StringComparison.OrdinalIgnoreCase)); if (role != null) { await RoleService.DeleteAsync(role.Id); } } catch { // 子客户角色清理失败不阻断客户删除 } } /// /// 重置客户密码。 /// /// 入参:客户密码重置参数。 /// 无返回体,仅完成重置客户密码。 [HttpPost("/customer/reset-password")] public async Task ResetPasswordAsync(ResetCustomerPasswordInput input) { var entity = await GetCustomerOrThrowAsync(input.Id); entity.Password = OmsPasswordHelper.ToMd5(input.NewPassword); await CustomerRepository.UpdateAsync(entity); } private Expression> BuildPaginatedPredicate(CustomerPaginatedInput input) { var keyword = input.Keyword?.Trim(); var salesman = input.Salesman?.Trim(); var customerService = input.CustomerService?.Trim(); var hasStatusFilter = input.Status.HasValue; var statusValue = input.Status.GetValueOrDefault(); var hasCustomerGroupFilter = input.CustomerGroup.HasValue; var customerGroupValue = input.CustomerGroup.GetValueOrDefault(); return customer => (string.IsNullOrWhiteSpace(keyword) || customer.CustomerCode.Contains(keyword) || customer.CustomerName.Contains(keyword) || customer.Phone.Contains(keyword) || (customer.Email ?? string.Empty).Contains(keyword)) && (!hasStatusFilter || customer.Status == statusValue) && (!hasCustomerGroupFilter || customer.CustomerGroup == customerGroupValue) && (string.IsNullOrWhiteSpace(salesman) || customer.Salesman == salesman) && (string.IsNullOrWhiteSpace(customerService) || customer.CustomerService == customerService); } private async Task GetCustomerOrThrowAsync(int id) { var entity = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == id); if (entity == null) { throw new BusinessException("客户不存在"); } return entity; } private async Task GetRbacUserMapIdOrThrowAsync(Guid id) { var userId = await RbacDbContext.Database.SqlQuery( $"select `userid` as `Value` from `user` where `Id` = {id}") .FirstOrDefaultAsync(); if (!userId.HasValue || userId.Value <= 0) { throw new BusinessException("用户编号创建失败"); } return userId.Value; } private async Task GetCustomerRoleIdOrThrowAsync() { var roles = await RoleService.GetAllRolesAsync(); var role = roles.FirstOrDefault(x => string.Equals(x.Code, CustomerRoleCode, StringComparison.OrdinalIgnoreCase)); if (role == null) { throw new BusinessException("客户角色未初始化"); } return role.Id; } private async Task GetCurrentMainCustomerOrThrowAsync() { var userId = CurrentUser.GetId(); if (userId <= 0) { throw new BusinessException("客户未登录"); } var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.UserId == userId && x.ParentCustomerId == null); if (customer == null) { throw new BusinessException("仅主客户可以管理子客户"); } return customer; } private async Task EnsureCustomerIdentityUniqueAsync(string customerCode, string phone, string email) { if (await CustomerRepository.GetByCustomerCodeAsync(customerCode) != null) { throw new BusinessException("客户编号已存在"); } if (await CustomerRepository.GetByPhoneAsync(phone) != null) { throw new BusinessException("手机号已存在"); } if (await CustomerRepository.GetByEmailAsync(email) != null) { throw new BusinessException("邮箱已存在"); } } private async Task EnsureCustomerIdentityUniqueForUpdateAsync(int id, string customerCode, string phone, string? email) { var duplicatedCode = await CustomerRepository.GetByCustomerCodeAsync(customerCode); if (duplicatedCode != null && duplicatedCode.Id != id) { throw new BusinessException("客户编号已存在"); } var duplicatedPhone = await CustomerRepository.GetByPhoneAsync(phone); if (duplicatedPhone != null && duplicatedPhone.Id != id) { throw new BusinessException("手机号已存在"); } if (email == null) { return; } var duplicatedEmail = await CustomerRepository.GetByEmailAsync(email); if (duplicatedEmail != null && duplicatedEmail.Id != id) { throw new BusinessException("邮箱已存在"); } } private async Task CreateRbacUserAsync(CreateCustomerInput input, NormalizedCustomerInput normalized) { return await UserService.CreateAsync(new UserCreateDto { Name = normalized.CustomerName, Account = normalized.Phone, Password = input.Password, Email = normalized.Email, Avatar = NormalizeOptionalText(input.WechatAvatar), RoleIds = [] }); } private static Customer BuildCustomerEntity( CreateCustomerInput input, NormalizedCustomerInput normalized, int? parentCustomerId, int userId) { return new Customer { ParentCustomerId = parentCustomerId, UserId = userId, CustomerCode = normalized.CustomerCode, CustomerName = normalized.CustomerName, WechatAvatar = NormalizeOptionalText(input.WechatAvatar), WechatNickname = NormalizeOptionalText(input.WechatNickname), Phone = normalized.Phone, Email = normalized.Email, Password = OmsPasswordHelper.ToMd5(input.Password), Balance = input.Balance, TotalQuota = input.TotalQuota, UsedQuota = input.UsedQuota, Status = input.Status, Remark = NormalizeOptionalText(input.Remark), Salesman = NormalizeOptionalText(input.Salesman), CustomerService = NormalizeOptionalText(input.CustomerService), CustomerGroup = input.CustomerGroup }; } private async Task NormalizeCreateCustomerInputAsync( CreateCustomerInput input, string codeFieldName, string nameFieldName) { var customerCode = string.IsNullOrWhiteSpace(input.CustomerCode) ? await CustomerCodeGenerator.GenerateNextAsync(CustomerRepository) : NormalizeRequiredText(input.CustomerCode, codeFieldName); return new NormalizedCustomerInput( customerCode, NormalizeRequiredText(input.CustomerName, nameFieldName), NormalizeRequiredText(input.Phone, "手机号"), NormalizeRequiredText(input.Email, "邮箱")); } private async Task> NormalizeAndValidateSubCustomerPermissionsAsync(IEnumerable? permissions) { var requestedPermissions = (permissions ?? []) .Select(NormalizeOptionalText) .Where(x => x != null) .Select(x => x!) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); var currentUserId = CurrentUser.Id ?? throw new BusinessException("客户未登录"); var currentPermissions = await UserService.GetUserPermissionsAsync(currentUserId); var currentPermissionSet = currentPermissions.ToHashSet(StringComparer.OrdinalIgnoreCase); if (requestedPermissions.Any(x => !currentPermissionSet.Contains(x))) { throw new BusinessException("子客户权限不能超出当前客户权限"); } return requestedPermissions; } private async Task CreateSubCustomerRoleAsync(int userId, string customerName, List permissions) { var role = await RoleService.CreateAsync(new RoleCreateDto { Code = $"CustomerSub_{userId}", Name = $"子客户-{customerName}", Permissions = permissions }); return role.Id; } private async Task TryRollbackRbacUserAsync(Guid userId) { try { await UserService.DeleteAsync(userId); } catch { } } private async Task TryRollbackRbacRoleAsync(Guid roleId) { try { await RoleService.DeleteAsync(roleId); } catch { } } private static void ValidateCustomerStatus(Customer customer) { if (customer.Status == CustomerStatus.Disabled) { throw new BusinessException("客户已禁用"); } if (customer.Status == CustomerStatus.Frozen) { throw new BusinessException("客户已冻结"); } } private static string NormalizeRequiredText(string? value, string fieldName) { var normalized = value?.Trim(); if (string.IsNullOrWhiteSpace(normalized)) { throw new BusinessException($"{fieldName}不能为空"); } return normalized; } private static string? NormalizeOptionalText(string? value) { var normalized = value?.Trim(); return string.IsNullOrWhiteSpace(normalized) ? null : normalized; } private sealed record NormalizedCustomerInput( string CustomerCode, string CustomerName, string Phone, string Email); }