S8MasterDataAdapter.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. namespace Admin.NET.Plugin.AiDOP.Service.S8;
  5. /// <summary>
  6. /// S8 主数据下拉:优先复用 S0 已落库主数据语义(部门等),无则返回空列表,不把「下拉查询」误建成独立业务表。
  7. ///
  8. /// <para><b>S8-P0-3-MASTERDATA-TRUSTED-SCOPE-1 作用域合同</b>:三个入口的工厂边界
  9. /// **一律由 <see cref="S8TrustedScopeResolver"/> 从认证身份解析**,
  10. /// 客户端传入的 <c>factoryRefId</c> 只被视为筛选意图,**当前实现直接忽略、不承担授权作用**。</para>
  11. ///
  12. /// <para>修复前(实测可被普通租户账号利用):<c>GetEmployeesAsync</c> 是
  13. /// <c>ClearFilter()</c> + <c>FactoryRefId == 客户端传入值</c>,
  14. /// 于是 <c>GET /master-data/employees?factoryRefId=1000</c> 会返回 500 条**其它租户**员工的
  15. /// 工号与真实姓名。</para>
  16. /// </summary>
  17. public class S8MasterDataAdapter : ITransient
  18. {
  19. private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
  20. private readonly SqlSugarRepository<AdoS0EmployeeMaster> _empRep;
  21. private readonly SqlSugarRepository<AdoS0LineMaster> _lineRep;
  22. private readonly SqlSugarRepository<SysUser> _sysUserRep;
  23. public S8MasterDataAdapter(
  24. SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
  25. SqlSugarRepository<AdoS0EmployeeMaster> empRep,
  26. SqlSugarRepository<AdoS0LineMaster> lineRep,
  27. SqlSugarRepository<SysUser> sysUserRep)
  28. {
  29. _deptRep = deptRep;
  30. _empRep = empRep;
  31. _lineRep = lineRep;
  32. _sysUserRep = sysUserRep;
  33. }
  34. /// <summary>
  35. /// S8-TENANT-ONLY-BATCH6:主数据下拉的边界由 <c>factory_ref_id</c> 改为 <c>tenant_id</c>。
  36. ///
  37. /// <para><b>这不是等价改写,是修一个真实越权。</b>真库取证(aidopdev,只读):
  38. /// <c>factory_ref_id = 1000</c> 同时属于租户 <c>824585161322565</c> 与 <c>797403760988229</c>——
  39. /// 按工厂过滤会把另一租户的 613 名员工 / 210 个部门直接列进本租户的下拉框。
  40. /// 工厂号从来就不是隔离维度,之前只是没人对着数据看过。</para>
  41. ///
  42. /// <para>可用性同样已验证:三张表 <c>tenant_id</c> 100% 非空
  43. /// (EmployeeMaster 1105 行 / DepartmentMaster 239 行 / LineMaster 169 行,NULL 均为 0),
  44. /// 每个租户都有非空主数据,改判据不会把下拉打空。</para>
  45. /// </summary>
  46. public async Task<object> GetDepartmentsAsync(S8TrustedScope scope) =>
  47. await _deptRep.AsQueryable()
  48. .Where(x => x.TenantId == scope.TenantId)
  49. .Take(500)
  50. .Select(x => new { id = x.Id, code = x.Department, name = x.Descr ?? x.Department })
  51. .ToListAsync();
  52. public async Task<object> GetEmployeesAsync(S8TrustedScope scope)
  53. {
  54. // ClearFilter 保留:不依赖全局 multi-tenant AOP 的当前行为,边界改由本方法显式声明,
  55. // 结果与是否开启 AOP 过滤无关(两者判据同为 tenant_id == 可信租户)。
  56. // 边界**只能**来自可信作用域的 TenantId + IsActive,绝不接受调用方传入的 factoryRefId。
  57. var emps = await _empRep.AsQueryable().ClearFilter()
  58. .Where(x => x.TenantId == scope.TenantId && x.IsActive)
  59. .Take(500)
  60. .Select(x => new { x.Id, x.Name, x.Employee, x.SysUserId })
  61. .ToListAsync();
  62. var sysUserIds = emps.Where(e => e.SysUserId.HasValue && e.SysUserId.Value > 0)
  63. .Select(e => e.SysUserId!.Value).Distinct().ToList();
  64. Dictionary<long, (string? RealName, string? Account)> userMap = new();
  65. if (sysUserIds.Count > 0)
  66. {
  67. // 姓名水合:主键集合 + 可信租户双重边界(原实现只有主键集合)。
  68. var users = await _sysUserRep.AsQueryable().ClearFilter()
  69. .Where(u => sysUserIds.Contains(u.Id) && u.TenantId == scope.TenantId)
  70. .Select(u => new { u.Id, u.RealName, u.Account })
  71. .ToListAsync();
  72. userMap = users.ToDictionary(u => u.Id, u => ((string?)u.RealName, (string?)u.Account));
  73. }
  74. return emps.Select(e =>
  75. {
  76. var bound = e.SysUserId.HasValue && e.SysUserId.Value > 0 && userMap.ContainsKey(e.SysUserId.Value);
  77. string? sysUserName = null;
  78. if (bound)
  79. {
  80. var (realName, account) = userMap[e.SysUserId!.Value];
  81. sysUserName = !string.IsNullOrWhiteSpace(realName) ? realName : account;
  82. }
  83. return new
  84. {
  85. id = e.Id,
  86. name = e.Name ?? e.Employee,
  87. empCode = e.Employee,
  88. sysUserId = e.SysUserId,
  89. sysUserName,
  90. bindStatus = bound ? "BOUND" : "UNBOUND"
  91. };
  92. }).ToList();
  93. }
  94. /// <summary>产线下拉,边界同 <see cref="GetDepartmentsAsync"/>。</summary>
  95. public async Task<object> GetLinesAsync(S8TrustedScope scope) =>
  96. await _lineRep.AsQueryable()
  97. .Where(x => x.TenantId == scope.TenantId)
  98. .Take(500)
  99. .Select(x => new { id = x.Id, code = x.Line, name = x.Describe ?? x.Line })
  100. .ToListAsync();
  101. }