using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing; using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse; using Admin.NET.Plugin.AiDOP.Infrastructure; namespace Admin.NET.Plugin.AiDOP.Service.S8; /// /// S8 主数据下拉:优先复用 S0 已落库主数据语义(部门等),无则返回空列表,不把「下拉查询」误建成独立业务表。 /// /// S8-P0-3-MASTERDATA-TRUSTED-SCOPE-1 作用域合同:三个入口的工厂边界 /// **一律由 从认证身份解析**, /// 客户端传入的 factoryRefId 只被视为筛选意图,**当前实现直接忽略、不承担授权作用**。 /// /// 修复前(实测可被普通租户账号利用):GetEmployeesAsync 是 /// ClearFilter() + FactoryRefId == 客户端传入值, /// 于是 GET /master-data/employees?factoryRefId=1000 会返回 500 条**其它租户**员工的 /// 工号与真实姓名。 /// public class S8MasterDataAdapter : ITransient { private readonly SqlSugarRepository _deptRep; private readonly SqlSugarRepository _empRep; private readonly SqlSugarRepository _lineRep; private readonly SqlSugarRepository _sysUserRep; public S8MasterDataAdapter( SqlSugarRepository deptRep, SqlSugarRepository empRep, SqlSugarRepository lineRep, SqlSugarRepository sysUserRep) { _deptRep = deptRep; _empRep = empRep; _lineRep = lineRep; _sysUserRep = sysUserRep; } public async Task GetDepartmentsAsync(S8TrustedScope scope) => await _deptRep.AsQueryable() .Where(x => x.FactoryRefId == scope.FactoryId) .Take(500) .Select(x => new { id = x.Id, code = x.Department, name = x.Descr ?? x.Department }) .ToListAsync(); public async Task GetEmployeesAsync(S8TrustedScope scope) { // S8 选员工下拉:EmployeeMaster.tenant_id 与 SysUser.TenantId 历史错位(业务租户 ≠ 系统租户), // 走全局 multi-tenant filter 会全过滤为空,故必须 ClearFilter; // 但边界**只能**来自可信作用域的 factoryId + IsActive,绝不接受调用方传入的 factoryRefId。 var emps = await _empRep.AsQueryable().ClearFilter() .Where(x => x.FactoryRefId == scope.FactoryId && x.IsActive) .Take(500) .Select(x => new { x.Id, x.Name, x.Employee, x.SysUserId }) .ToListAsync(); var sysUserIds = emps.Where(e => e.SysUserId.HasValue && e.SysUserId.Value > 0) .Select(e => e.SysUserId!.Value).Distinct().ToList(); Dictionary userMap = new(); if (sysUserIds.Count > 0) { // 姓名水合:主键集合 + 可信租户双重边界(原实现只有主键集合)。 var users = await _sysUserRep.AsQueryable().ClearFilter() .Where(u => sysUserIds.Contains(u.Id) && u.TenantId == scope.TenantId) .Select(u => new { u.Id, u.RealName, u.Account }) .ToListAsync(); userMap = users.ToDictionary(u => u.Id, u => ((string?)u.RealName, (string?)u.Account)); } return emps.Select(e => { var bound = e.SysUserId.HasValue && e.SysUserId.Value > 0 && userMap.ContainsKey(e.SysUserId.Value); string? sysUserName = null; if (bound) { var (realName, account) = userMap[e.SysUserId!.Value]; sysUserName = !string.IsNullOrWhiteSpace(realName) ? realName : account; } return new { id = e.Id, name = e.Name ?? e.Employee, empCode = e.Employee, sysUserId = e.SysUserId, sysUserName, bindStatus = bound ? "BOUND" : "UNBOUND" }; }).ToList(); } public async Task GetLinesAsync(S8TrustedScope scope) => await _lineRep.AsQueryable() .Where(x => x.FactoryRefId == scope.FactoryId) .Take(500) .Select(x => new { id = x.Id, code = x.Line, name = x.Describe ?? x.Line }) .ToListAsync(); }