using FoodLabeling.Application.Helpers; using FoodLabeling.Application.Contracts.Dtos.Common; using FoodLabeling.Application.Contracts.Dtos.TeamMember; using FoodLabeling.Application.Contracts.IServices; using FoodLabeling.Application.Services.DbModels; using FoodLabeling.Domain.Entities; using SqlSugar; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Entities; using Volo.Abp.Guids; using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.Rbac.Domain.Managers; using Yi.Framework.SqlSugarCore.Abstractions; namespace FoodLabeling.Application.Services; /// /// 成员(Team Member/Manager)服务,对外仅在 food-labeling-us 暴露 /// public class TeamMemberAppService : ApplicationService, ITeamMemberAppService { private readonly ISqlSugarRepository _userRepository; private readonly UserManager _userManager; private readonly ISqlSugarDbContext _dbContext; private readonly IGuidGenerator _guidGenerator; public TeamMemberAppService( ISqlSugarRepository userRepository, UserManager userManager, ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator) { _userRepository = userRepository; _userManager = userManager; _dbContext = dbContext; _guidGenerator = guidGenerator; } /// /// 成员分页列表(含角色与已分配门店) /// public async Task> GetListAsync(TeamMemberGetListInputVo input) { var pageIndex = PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount); var pageSize = input.MaxResultCount; var keyword = input.Keyword?.Trim(); RefAsync total = 0; // 先按 user 表筛选分页,再批量补齐角色与门店 var users = await _userRepository._DbQueryable .Where(u => !u.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(keyword), u => (u.Name != null && u.Name.Contains(keyword!)) || u.UserName.Contains(keyword!) || (u.Email != null && u.Email.Contains(keyword!)) || (u.Phone != null && u.Phone.ToString()!.Contains(keyword!))) .WhereIF(input.State != null, u => u.State == input.State) .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) .OrderByDescending(u => u.CreationTime) .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); var userIds = users.Select(x => x.Id).ToList(); var userIdStrings = userIds.Select(x => x.ToString()).ToList(); // user-role: 仅取第一个角色(原型表格展示单角色) var userRolePairs = await _dbContext.SqlSugarClient.Queryable((ur, r) => ur.RoleId == r.Id) .Where(ur => userIds.Contains(ur.UserId)) .Select((ur, r) => new { ur.UserId, r.Id, r.RoleName }) .ToListAsync(); var roleMap = userRolePairs .GroupBy(x => x.UserId) .ToDictionary(g => g.Key, g => g.FirstOrDefault()); // user-location var userLocations = await _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(input.LocationId), x => x.LocationId == input.LocationId) .Where(x => userIdStrings.Contains(x.UserId)) .ToListAsync(); // 如果按 LocationId 过滤,需要反向过滤掉无关联的 user if (!string.IsNullOrWhiteSpace(input.LocationId)) { var allowedUserIds = userLocations.Select(x => x.UserId).ToHashSet(); users = users.Where(u => allowedUserIds.Contains(u.Id.ToString())).ToList(); } var locationIds = userLocations.Select(x => x.LocationId).Distinct().ToList(); var locations = await _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .WhereIF(locationIds.Count > 0, x => locationIds.Contains(x.Id.ToString())) .Select(x => new { x.Id, x.LocationCode, x.LocationName }) .ToListAsync(); var locationMap = locations.ToDictionary(x => x.Id.ToString(), x => x); var assignedMap = userLocations .GroupBy(x => x.UserId) .ToDictionary( g => g.Key, g => g.Select(x => { if (locationMap.TryGetValue(x.LocationId, out var loc)) { return new TeamMemberAssignedLocationDto { Id = loc.Id.ToString(), LocationCode = loc.LocationCode, LocationName = loc.LocationName }; } return null; }).Where(x => x != null).Cast().ToList()); var items = users.Select(u => { roleMap.TryGetValue(u.Id, out var role); assignedMap.TryGetValue(u.Id.ToString(), out var assigned); return new TeamMemberGetListOutputDto { Id = u.Id, FullName = u.Name ?? string.Empty, UserName = u.UserName, Email = u.Email, Phone = u.Phone, State = u.State, RoleId = role?.Id, RoleName = role?.RoleName, AssignedLocations = assigned ?? new List() }; }).ToList(); var totalCount = (long)total; return new PagedResultWithPageDto { PageIndex = pageIndex, PageSize = pageSize, TotalCount = totalCount, TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize), Items = items }; } /// /// 成员详情(带门店ID列表) /// public async Task GetAsync(Guid id) { var user = await _userRepository.GetByIdAsync(id); if (user is null || user.IsDeleted) { throw new UserFriendlyException("成员不存在"); } var userIdString = id.ToString(); var links = await _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted && x.UserId == userIdString) .ToListAsync(); var locationIds = links.Select(x => x.LocationId).Distinct().ToList(); var locations = await _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .WhereIF(locationIds.Count > 0, x => locationIds.Contains(x.Id.ToString())) .Select(x => new { x.Id, x.LocationCode, x.LocationName }) .ToListAsync(); var assigned = locations.Select(x => new TeamMemberAssignedLocationDto { Id = x.Id.ToString(), LocationCode = x.LocationCode, LocationName = x.LocationName }).ToList(); var role = await _dbContext.SqlSugarClient.Queryable().FirstAsync(x => x.UserId == id); return new TeamMemberGetOutputDto { Id = user.Id, FullName = user.Name ?? string.Empty, UserName = user.UserName, Email = user.Email, Phone = user.Phone, State = user.State, RoleId = role?.RoleId, LocationIds = locationIds, AssignedLocations = assigned }; } /// /// 新增成员(同步设置角色与门店) /// public async Task CreateAsync(TeamMemberCreateInputVo input) { if (input.LocationIds is null || input.LocationIds.Count == 0) { throw new UserFriendlyException("成员必须至少分配一个门店"); } var user = new UserAggregateRoot(input.UserName.Trim(), input.Password, input.Phone, input.FullName.Trim()) { Name = input.FullName.Trim(), Email = input.Email?.Trim(), State = input.State }; EntityHelper.TrySetId(user, _guidGenerator.Create); user.BuildPassword(); await _userManager.CreateAsync(user); if (input.RoleId != null) { await _userManager.GiveUserSetRoleAsync(new List { user.Id }, new List { input.RoleId.Value }); } await UpsertUserLocationsAsync(user.Id, input.LocationIds); return await GetAsync(user.Id); } /// /// 编辑成员(同步设置角色与门店) /// public async Task UpdateAsync(Guid id, TeamMemberUpdateInputVo input) { if (input.LocationIds is null || input.LocationIds.Count == 0) { throw new UserFriendlyException("成员必须至少分配一个门店"); } var user = await _userRepository.GetByIdAsync(id); if (user is null || user.IsDeleted) { throw new UserFriendlyException("成员不存在"); } user.Name = input.FullName.Trim(); user.UserName = input.UserName.Trim(); user.Email = input.Email?.Trim(); user.Phone = input.Phone; user.State = input.State; if (!string.IsNullOrWhiteSpace(input.Password)) { user.EncryPassword.Password = input.Password; user.BuildPassword(); } await _userRepository.UpdateAsync(user); // 角色:覆盖式设置(只保留一个) if (input.RoleId != null) { await _userManager.GiveUserSetRoleAsync(new List { id }, new List { input.RoleId.Value }); } else { await _userManager.GiveUserSetRoleAsync(new List { id }, new List()); } await UpsertUserLocationsAsync(id, input.LocationIds); return await GetAsync(id); } /// /// 删除成员(逻辑删除 user;并逻辑删除关联表) /// public async Task DeleteAsync(Guid id) { var user = await _userRepository.GetByIdAsync(id); if (user is null || user.IsDeleted) { return; } user.IsDeleted = true; await _userRepository.UpdateAsync(user); var userIdString = id.ToString(); var currentUserId = CurrentUser?.Id?.ToString(); await _dbContext.SqlSugarClient.Updateable() .SetColumns(x => new UserLocationDbEntity { IsDeleted = true, LastModificationTime = DateTime.Now, LastModifierId = currentUserId }) .Where(x => x.UserId == userIdString && !x.IsDeleted) .ExecuteCommandAsync(); } private async Task UpsertUserLocationsAsync(Guid userId, List locationIds) { var now = DateTime.Now; var userIdString = userId.ToString(); var wanted = locationIds.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct().ToList(); var currentUserId = CurrentUser?.Id?.ToString(); // 校验门店存在且未删除 var validCount = await _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .Where(x => wanted.Contains(x.Id.ToString())) .CountAsync(); if (validCount != wanted.Count) { throw new UserFriendlyException("存在无效门店,请刷新后重试"); } var existing = await _dbContext.SqlSugarClient.Queryable() .Where(x => x.UserId == userIdString) .ToListAsync(); var existingActive = existing.Where(x => !x.IsDeleted).ToList(); var existingActiveSet = existingActive.Select(x => x.LocationId).ToHashSet(); // 需要删除的(逻辑删除) var toDelete = existingActive.Where(x => !wanted.Contains(x.LocationId)).ToList(); if (toDelete.Count > 0) { var ids = toDelete.Select(x => x.Id).ToList(); await _dbContext.SqlSugarClient.Updateable() .SetColumns(x => new UserLocationDbEntity { IsDeleted = true, LastModificationTime = now, LastModifierId = currentUserId }) .Where(x => ids.Contains(x.Id)) .ExecuteCommandAsync(); } // 需要新增的 var toInsert = wanted.Where(x => !existingActiveSet.Contains(x)).ToList(); if (toInsert.Count > 0) { var rows = toInsert.Select(locationId => new UserLocationDbEntity { Id = _guidGenerator.Create().ToString(), IsDeleted = false, CreationTime = now, CreatorId = currentUserId, UserId = userIdString, LocationId = locationId, ConcurrencyStamp = string.Empty }).ToList(); await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); } } }