using Admin.NET.Core;
namespace Admin.NET.Plugin.AiDOP.Infrastructure;
public sealed record SmartOpsTrustedScope(long TenantId, long FactoryId, long UserId);
///
/// 智慧诊断与改善闭环的可信作用域:只信登录上下文,工厂由租户关系解析。
///
public sealed class SmartOpsTrustedScopeResolver : ITransient
{
public const long PlatformTenantId = SqlSugarConst.DefaultTenantId;
private readonly ISqlSugarClient _db;
private readonly UserManager _userManager;
public SmartOpsTrustedScopeResolver(ISqlSugarClient db, UserManager userManager)
{
_db = db;
_userManager = userManager;
}
public async Task ResolveAsync(long? candidateFactoryId = null)
{
var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
SmartOpsTrustedScopeRules.EnsureBusinessTenant(tenantId);
var orgId = await _db.Queryable()
.ClearFilter()
.Where(x => x.Id == tenantId)
.Select(x => x.OrgId)
.FirstAsync();
var factoryId = SmartOpsTrustedScopeRules.ResolveFactory(tenantId, orgId, candidateFactoryId);
var userId = _userManager.UserId;
if (userId <= 0)
throw Oops.Oh("无法确定当前用户,请重新登录");
return new SmartOpsTrustedScope(tenantId, factoryId, userId);
}
}
public static class SmartOpsTrustedScopeRules
{
public const long PlatformTenantId = SqlSugarConst.DefaultTenantId;
public static void EnsureBusinessTenant(long tenantId)
{
if (tenantId <= 0 || tenantId == 1 || tenantId == PlatformTenantId)
throw new InvalidOperationException("平台租户不得作为业务事实作用域");
}
///
/// 客户端工厂只是筛选建议。未授权或不存在时回落到租户 OrgId,不得使用外租户工厂。
///
///
/// 只判 是否为空。
/// 不得再用 orgId == tenantId 判「未配置」:Admin.NET 种子/SQL 脚本建的租户
/// 其根组织 Id 与租户 Id 同号是官方约定(见 SysTenantSeedData「Id=OrgId=DefaultTenantId」
/// 与 SysOrgSeedData 根组织),只有界面 AddTenant 建的租户才另发雪花号。
/// 旧判据会把系统默认 / Ai-DOP 演示公司等种子租户全部误判为「未配置有效工厂」而拒绝服务。
///
public static long ResolveFactory(long tenantId, long orgId, long? candidateFactoryId)
{
// 文案面向使用者:说清「是什么状态、该找谁、去哪里配」,不暴露表名/列名。
if (orgId <= 0)
throw new InvalidOperationException("当前租户尚未配置所属机构,请联系管理员在「租户管理」中完成配置");
if (candidateFactoryId is > 0
&& candidateFactoryId.Value != tenantId
&& candidateFactoryId.Value == orgId)
return candidateFactoryId.Value;
return orgId;
}
public static bool IsAuthorizedFactory(long tenantId, long orgId, long factoryId) =>
factoryId > 0 && factoryId != tenantId && factoryId == orgId;
}
public static class SmartOpsImprovementPlanNo
{
public static string BuildPrefix(DateTime now) => $"SOP-{now:yyyyMMdd}-";
public static string Next(string prefix, IEnumerable existing)
{
var max = 0;
foreach (var planNo in existing)
{
if (string.IsNullOrWhiteSpace(planNo) || !planNo.StartsWith(prefix, StringComparison.Ordinal))
continue;
if (int.TryParse(planNo.AsSpan(prefix.Length), out var n))
max = Math.Max(max, n);
}
return $"{prefix}{max + 1:000}";
}
}