namespace Admin.NET.Plugin.AiDOP.DataPlatform.Executors; /// /// 入站抽数(PULL)的工厂作用域语义 —— 与 严格同源。 /// /// 背景:部分源表(b_examine_result / b_bom_child_examine / srm_pr_main / ic_demandschedule 等) /// 把 factory_id 写成了 tenant_id(见 Order/ResourceCheckResultWriter:entry.FactoryId ?? order.FactoryId ?? entry.TenantId), /// 这类值并不是真实工厂,只是"没有工厂"的另一种写法。写入层 早已按 /// NormalizeFactoryId 把 factory_id==tenant_id / 0 / NULL 统一归一成"无工厂"→回落到默认工厂 1; /// 若拉取层不做同样的归一,就会出现"拉取层判定不匹配→0 行,写入层却认为匹配"的口径分裂, /// 表现为长期 rows_read=0 但 status=SUCCESS 的静默空跑。 /// /// 因此本类不新造口径,只把写入层已有的归一规则翻译成等价 SQL,保证两层永远一致。 /// /// 与 的区别(勿混用): /// 后者服务于 DWD/KPI **读取**侧,对 factory_id==tenant_id 的行采取"排除"策略 /// (谓词里带 col <> @tenantId),因为那是脏数据不应计入运营事实; /// 而入站 PULL 侧必须"归一"而非"排除",否则整张源表会被丢光。两者语义方向相反,不可互相替换。 /// internal static class MdpFactoryScope { /// 历史/无工厂数据统一回落到的默认工厂号(与 MdpStagingWriter 的 resolvedFactory 回落一致)。 internal const long LegacyDefaultFactoryId = 1; /// /// 把源行的 factory_id 归一成可比较的工厂号: /// NULL / <=0 / 等于 tenant_id 一律视为"无真实工厂",回落 。 /// internal static long Resolve(long tenantId, long? rowFactoryId) => MdpStagingWriter.NormalizeFactoryId(tenantId, rowFactoryId) ?? LegacyDefaultFactoryId; /// 源行是否落在 (tenantId, scopeFactoryId) 作用域内(工厂维度)。 internal static bool Matches(long tenantId, long scopeFactoryId, long? rowFactoryId) => Resolve(tenantId, rowFactoryId) == scopeFactoryId; /// /// 的等价 SQL(MySQL / SQL Server 通用)。 /// 需已按目标方言转义。 /// CASE WHEN col > 0 THEN col END 而非 NULLIF(col,0) 是刻意的: /// NormalizeFactoryId 对负数同样返回 null,用 CASE 才能与之逐值等价。 /// internal static string ResolveSqlExpression(string quotedColumn, string tenantParam) => $"COALESCE(NULLIF(CASE WHEN {quotedColumn} > 0 THEN {quotedColumn} END, {tenantParam}), {LegacyDefaultFactoryId})"; /// 工厂作用域谓词。注意:引用了 ,调用方必须保证该参数已绑定。 internal static string SqlPredicate(string quotedColumn, string tenantParam, string factoryParam) => $"{ResolveSqlExpression(quotedColumn, tenantParam)} = {factoryParam}"; }