S8DepartmentScopeValidator.cs 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  2. namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  3. /// <summary>
  4. /// S8-RULE-READINESS-1:「这个部门 Id 在当前租户下是否可用」的<b>唯一</b>判定实现。
  5. ///
  6. /// <para><b>为什么必须只有一份</b>:同一个判据现在有两个调用方 ——
  7. /// 启用前的就绪门禁(<c>S8RuleReadinessGate</c>)与运行时的自动建单
  8. /// (<c>S8ManualReportService.ResolveAutoWatchDepartmentsAsync</c>)。
  9. /// 两者一旦各写一份,就会出现最坏的那种结局:<b>启用检查通过、建单却失败</b>,
  10. /// 而管理员在页面上只看到「已启用 / 最近结果:成功 / 异常 0 条」,没有任何线索。
  11. /// 这正是本轮要消灭的静默失败,所以判据必须物理同源,不是"两处写成一样"。</para>
  12. ///
  13. /// <para>判据三条,缺一不可:主键命中 · 属于该租户 · <c>IsActive</c>。
  14. /// 工厂<b>不参与</b> —— 真库 <c>factory_ref_id = 1000</c> 横跨两个租户
  15. /// (824585161322565 / 797403760988229),按工厂校验会把别家的部门判为合法。</para>
  16. ///
  17. /// <para><c>ClearFilter()</c>:边界由本方法显式声明,不依赖全局 multi-tenant AOP 的当前行为,
  18. /// 结果与是否开启该过滤器无关(两者判据同为 tenant_id == 传入租户)。</para>
  19. /// </summary>
  20. public interface IS8DepartmentScopeValidator
  21. {
  22. /// <summary>部门是否存在于该租户且启用。</summary>
  23. Task<bool> ExistsInTenantAsync(long? deptId, long tenantId);
  24. }
  25. /// <summary>
  26. /// 正式实现。<b>生产代码里只有这一份</b> —— 接口的存在是为了给测试留 seam,
  27. /// 不是为了允许出现第二种判据。
  28. /// </summary>
  29. public sealed class S8DepartmentScopeValidator : IS8DepartmentScopeValidator, ITransient
  30. {
  31. private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
  32. public S8DepartmentScopeValidator(SqlSugarRepository<AdoS0DepartmentMaster> deptRep) => _deptRep = deptRep;
  33. /// <summary>
  34. /// 部门是否存在于该租户且启用。<c>deptId</c> 为空 / 非正数、或 <c>tenantId</c> 非正数一律返回 false
  35. /// —— 调用方需要区分「没配」与「配了但非法」时,请在调用前自行判空,本方法不承担该语义。
  36. /// </summary>
  37. public async Task<bool> ExistsInTenantAsync(long? deptId, long tenantId)
  38. {
  39. if (!deptId.HasValue || deptId.Value <= 0 || tenantId <= 0) return false;
  40. return await _deptRep.AsQueryable().ClearFilter()
  41. .Where(x => x.Id == deptId.Value && x.TenantId == tenantId && x.IsActive)
  42. .AnyAsync();
  43. }
  44. }