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();
}
}
}