using SqlSugar;
namespace Admin.NET.Plugin.AiDOP.Infrastructure;
///
/// Domain ↔ Ai-DOP tenant 解析器。
/// 直接查询 sys_tenant 系统租户表验证租户有效性,无需手动维护映射表。
/// Domain 一律按**当前租户自身主数据**解析:优先 GeneralizedCodeMaster.SystemConfig.CompanyCode,
/// 缺失时回落到该租户自己的 LocationMaster 唯一 Domain;两者都不可得或不唯一 → fail closed。
/// **禁止任何硬编码 Domain 兜底**(历史上的 "8010" 兜底会把租户串到别人的 Domain)。
///
public sealed class SourceDomainTenantResolver : ITransient
{
private readonly ISqlSugarClient _db;
public SourceDomainTenantResolver(ISqlSugarClient db) => _db = db;
///
/// 根据 sourceCode + domain 解析**源归属租户**(source owner)。
///
/// 语义:一个 (source, domain) 组合物理上属于哪一个 DOP 租户的贴源落地区(stg)。
/// 唯一依据是显式登记表 ado_source_domain_tenant_map;未登记即 fail closed。
///
///
/// ⚠️ 本方法**不回答**「某条库存数据业务上属于哪个租户」——同一个 domain 可被多个租户
/// 按各自库位范围共享(如 8010 同时被 AIDOP 与 UATTEST_CHL 使用),
/// 业务归属由标准层物化时按各租户 LocationMaster 库位范围投影决定。
///
///
/// 历史缺陷:本方法曾忽略 domain、恒取 SysTenant WHERE Status=1 ORDER BY Id LIMIT 1
/// (即默认租户),导致整份 domain 快照被塞进与业务无关的租户。已删除该行为。
///
///
public async Task ResolveTenantIdAsync(
string sourceCode,
string domain,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(sourceCode) || string.IsNullOrWhiteSpace(domain))
throw new InvalidOperationException("sourceCode/domain 不能为空");
var code = sourceCode.Trim();
var dom = domain.Trim();
var rows = await _db.Ado.SqlQueryAsync(
"""
SELECT DISTINCT m.tenant_id AS TenantId
FROM ado_source_domain_tenant_map m
JOIN SysTenant t ON t.Id = m.tenant_id AND t.Status = 1
WHERE m.source_code = @SourceCode
AND m.domain = @Domain
AND m.status = 1
""",
new List
{
new("@SourceCode", code),
new("@Domain", dom)
});
// fail closed:既不猜第一个租户,也不回落默认租户
if (rows.Count == 0)
throw new InvalidOperationException(
$"源归属租户未登记:source={code}, domain={dom}(请在 ado_source_domain_tenant_map 显式登记)");
if (rows.Count > 1)
throw new InvalidOperationException(
$"源归属租户不唯一:source={code}, domain={dom}, tenants=[{string.Join(",", rows.Select(x => x.TenantId))}]");
if (rows[0].TenantId <= 0)
throw new InvalidOperationException($"源归属租户 Id 非法:source={code}, domain={dom}");
return rows[0].TenantId;
}
///
/// 根据 sourceCode + tenantId 解析 domain。
/// 先在 sys_tenant 验证租户存在且启用,再**按该租户自身主数据**解析 Domain:
/// ① GeneralizedCodeMaster.SystemConfig.CompanyCode(tenant 限定);
/// ② 缺失时回落该租户 LocationMaster 的唯一 Domain。
/// 两者都取不到、或取到多个无法判定 → 抛异常 fail closed,绝不兜底到任何固定 Domain。
///
public async Task ResolveDomainAsync(
string sourceCode,
long tenantId,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(sourceCode) || tenantId <= 0)
throw new InvalidOperationException("sourceCode/tenantId 非法");
// 验证租户在 sys_tenant 中存在且启用
var tenantExists = await _db.Ado.GetIntAsync(
"SELECT COUNT(1) FROM SysTenant WHERE Id = @TenantId AND Status = 1",
new List { new("@TenantId", tenantId) });
if (tenantExists <= 0)
throw new InvalidOperationException($"租户不存在或未启用:tenant={tenantId}");
// ① 主口径:当前租户自己配置的公司代码
var configured = await _db.Ado.SqlQueryAsync(
"""
SELECT DISTINCT Domain
FROM GeneralizedCodeMaster
WHERE tenant_id = @TenantId
AND FldName = 'SystemConfig' AND Val = 'CompanyCode'
AND IFNULL(Domain,'') <> ''
""",
new List { new("@TenantId", tenantId) });
var domain = PickSingleDomain(configured, tenantId, "GeneralizedCodeMaster.SystemConfig.CompanyCode");
if (domain is not null) return domain;
// ② 次口径:当前租户自己的库位主数据(同样 tenant 限定,且必须唯一)
var derived = await _db.Ado.SqlQueryAsync(
"""
SELECT DISTINCT Domain
FROM LocationMaster
WHERE tenant_id = @TenantId AND IFNULL(Domain,'') <> ''
""",
new List { new("@TenantId", tenantId) });
domain = PickSingleDomain(derived, tenantId, "LocationMaster");
if (domain is not null) return domain;
// ③ fail closed:宁可查询失败,也不把租户串到别人的 Domain
throw new InvalidOperationException(
$"租户未配置 Domain 映射:tenant={tenantId}(GeneralizedCodeMaster.SystemConfig.CompanyCode 缺失," +
"且该租户 LocationMaster 无法唯一推导 Domain)");
}
/// 取唯一 Domain:0 条返回 null 交由下一口径;多条直接 fail closed,不猜。
private static string? PickSingleDomain(List rows, long tenantId, string source)
{
var domains = rows
.Select(x => x.Domain?.Trim())
.Where(x => !string.IsNullOrEmpty(x))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (domains.Count == 0) return null;
if (domains.Count > 1)
throw new InvalidOperationException(
$"租户 Domain 映射不唯一:tenant={tenantId}, source={source}, domains=[{string.Join(",", domains)}]");
return domains[0];
}
private sealed class MapRow
{
public long TenantId { get; set; }
}
private sealed class DomainRow
{
public string? Domain { get; set; }
}
}