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