using Admin.NET.Plugin.AiDOP.Dto.S8;
using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
namespace Admin.NET.Plugin.AiDOP.Service.S8;
///
/// S8 配置页:操作员(员工)↔ 系统账号绑定。
/// 仅维护 EmployeeMaster.SysUserId 的 1:1 弱关联;不动 SysUser/SysRole/SysUserRole。
///
public class S8OperatorBindingService : ITransient
{
private readonly SqlSugarRepository _empRep;
private readonly SqlSugarRepository _sysUserRep;
private readonly UserManager _userManager;
public S8OperatorBindingService(
SqlSugarRepository empRep,
SqlSugarRepository sysUserRep,
UserManager userManager)
{
_empRep = empRep;
_sysUserRep = sysUserRep;
_userManager = userManager;
}
public async Task> ListAsync(long? factoryRefId, string? bindStatus, string? keyword)
{
// ClearFilter:EmployeeMaster.tenant_id 属 S0 域租户,与登录 token TenantId 不一致(同 BUG-S8-EMPLOYEES-TENANT-FILTER-001 第 3 处)。
// 安全边界:保留 factoryRefId 显式过滤;仅放开租户全局过滤器;不影响 SysUser 查询。
var emps = await _empRep.AsQueryable().ClearFilter()
.WhereIF(factoryRefId.HasValue, x => x.FactoryRefId == factoryRefId!.Value)
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
x => x.Employee.Contains(keyword!) || (x.Name != null && x.Name.Contains(keyword!)))
.Take(500)
.Select(x => new { x.Id, x.Name, x.Employee, x.FactoryRefId, x.SysUserId })
.ToListAsync();
var sysUserIds = emps.Where(e => e.SysUserId.HasValue && e.SysUserId.Value > 0)
.Select(e => e.SysUserId!.Value).Distinct().ToList();
var userMap = sysUserIds.Count == 0
? new Dictionary()
: (await _sysUserRep.AsQueryable()
.Where(u => sysUserIds.Contains(u.Id) && u.TenantId == _userManager.TenantId)
.Select(u => new { u.Id, u.RealName, u.Account })
.ToListAsync())
.ToDictionary(u => u.Id, u => string.IsNullOrWhiteSpace(u.RealName) ? u.Account : u.RealName);
var rows = emps.Select(e =>
{
var bound = e.SysUserId.HasValue && e.SysUserId.Value > 0 && userMap.ContainsKey(e.SysUserId.Value);
return new AdoS8OperatorBindingRowDto
{
EmployeeId = e.Id,
EmpCode = e.Employee,
Name = e.Name,
FactoryRefId = e.FactoryRefId,
SysUserId = e.SysUserId,
SysUserName = bound ? userMap[e.SysUserId!.Value] : null,
BindStatus = bound ? "BOUND" : "UNBOUND"
};
});
if (!string.IsNullOrWhiteSpace(bindStatus) && (bindStatus == "BOUND" || bindStatus == "UNBOUND"))
rows = rows.Where(r => r.BindStatus == bindStatus);
return rows
.OrderBy(r => r.BindStatus == "BOUND" ? 0 : 1)
.ThenBy(r => r.EmpCode)
.ToList();
}
public async Task> ListSysUsersAsync(string? keyword, long? excludeEmployeeId)
{
var tenantId = _userManager.TenantId;
var users = await _sysUserRep.AsQueryable()
.Where(u => u.TenantId == tenantId)
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
u => u.Account.Contains(keyword!) || u.RealName.Contains(keyword!))
.Take(200)
.Select(u => new { u.Id, u.Account, u.RealName, u.Status })
.ToListAsync();
if (users.Count == 0) return new();
// 标注哪些 sysUser 已被其它 employee 绑定(用于前端禁选/隐藏);
// 当前 employee 自己绑定的 sysUser 在 alreadyBoundEmployeeId == excludeEmployeeId 时会被前端放行。
var ids = users.Select(u => u.Id).ToList();
var bound = await _empRep.AsQueryable()
.Where(e => e.SysUserId.HasValue && ids.Contains(e.SysUserId!.Value))
.Select(e => new { e.Id, e.SysUserId })
.ToListAsync();
var boundMap = bound.GroupBy(b => b.SysUserId!.Value)
.ToDictionary(g => g.Key, g => g.First().Id);
return users.Select(u => new AdoS8ConfigSysUserRowDto
{
Id = u.Id,
Account = u.Account,
RealName = u.RealName,
Status = (int)u.Status,
AlreadyBoundEmployeeId = boundMap.TryGetValue(u.Id, out var empId) ? empId : null
}).ToList();
}
public async Task BindAsync(AdoS8OperatorBindingCreateDto dto)
{
if (dto.EmployeeId <= 0) throw new S8BizException("员工 ID 不能为空");
if (dto.SysUserId <= 0) throw new S8BizException("系统账号 ID 不能为空");
var emp = await _empRep.GetFirstAsync(x => x.Id == dto.EmployeeId)
?? throw new S8BizException("员工不存在");
var tenantId = _userManager.TenantId;
var user = await _sysUserRep.GetFirstAsync(x => x.Id == dto.SysUserId && x.TenantId == tenantId)
?? throw new S8BizException("系统账号不存在或不在当前租户");
// 同一 sysUser 不允许绑定到另一个 employee
var conflict = await _empRep.GetFirstAsync(x => x.SysUserId == dto.SysUserId && x.Id != dto.EmployeeId);
if (conflict != null)
throw new S8BizException($"系统账号 {user.Account} 已绑定至其它员工 {conflict.Name ?? conflict.Employee}");
emp.SysUserId = dto.SysUserId;
emp.UpdateTime = DateTime.Now;
emp.UpdateUser = _userManager.Account;
await _empRep.AsUpdateable(emp)
.UpdateColumns(it => new { it.SysUserId, it.UpdateTime, it.UpdateUser })
.ExecuteCommandAsync();
return new AdoS8OperatorBindingRowDto
{
EmployeeId = emp.Id,
EmpCode = emp.Employee,
Name = emp.Name,
FactoryRefId = emp.FactoryRefId,
SysUserId = dto.SysUserId,
SysUserName = string.IsNullOrWhiteSpace(user.RealName) ? user.Account : user.RealName,
BindStatus = "BOUND"
};
}
public async Task UnbindAsync(long employeeId)
{
if (employeeId <= 0) throw new S8BizException("员工 ID 不能为空");
var emp = await _empRep.GetFirstAsync(x => x.Id == employeeId)
?? throw new S8BizException("员工不存在");
if (!emp.SysUserId.HasValue) return;
emp.SysUserId = null;
emp.UpdateTime = DateTime.Now;
emp.UpdateUser = _userManager.Account;
await _empRep.AsUpdateable(emp)
.UpdateColumns(it => new { it.SysUserId, it.UpdateTime, it.UpdateUser })
.ExecuteCommandAsync();
}
}