namespace Admin.NET.Plugin.AiDOP.Manufacturing; /// /// S6 模工具台账(MoldToolList)删除前的下游引用检查(FUNC-S6-004)。 /// /// 刻意**不复用** Infrastructure/AdoS0ReferenceChecker:那是 S0 共享基础设施, /// 改它需抢提交锁且会把 S6 逻辑混入 S0 模块边界(CLAUDE.md 第九节)。 /// /// 引用口径一律「租户内 + 按业务编码 MoldTypeCode 匹配」,与两张下游表的实际存储一致 /// (下游存的是编码字符串,不是 MoldToolList.id)。 /// /// 覆盖范围(2026-09-01 实测): /// MUST ProdLineDetail.MoldTypeCode —— S0 线边物料/产线物料在用,活跃写入路径, /// 同租户命中 896 行(另有 369 行历史孤儿,非本批修复范围) /// SHOULD ScheduleResultOpMaster.MoldTypeCode —— 排产结果;当前生成逻辑未写该列(命中 0), /// 但 ExecutableDailyPlanService 已按该列做筛选消费, /// 属已声明且已被消费的引用位,一并纳入以免将来漏检 /// 不纳入:LineMaterial.MoldTypeCode —— 空表、无任何 controller/service 读写路径,仅 CodeFirst 注册 /// /// 只读:本类只做 SELECT,不修改、不清空、不级联删除任何下游数据。 /// public sealed class MoldToolReferenceChecker : ITransient { private readonly ISqlSugarClient _db; public MoldToolReferenceChecker(ISqlSugarClient db) { _db = db; } /// /// 租户内检查该模具类型编码是否被下游引用。命中返回(条数, 中文表述),未命中返回 null。 /// public async Task<(int Count, string Table)?> ReferencesAsync(long tenantId, string? moldTypeCode) { if (string.IsNullOrWhiteSpace(moldTypeCode)) return null; var code = moldTypeCode.Trim(); var prodLine = await CountAsync( "SELECT COUNT(1) FROM ProdLineDetail WHERE tenant_id = @TenantId AND MoldTypeCode = @Code", tenantId, code); if (prodLine > 0) return (prodLine, "线边物料/产线物料配置 (ProdLineDetail.MoldTypeCode)"); var schedule = await CountAsync( "SELECT COUNT(1) FROM ScheduleResultOpMaster WHERE tenant_id = @TenantId AND MoldTypeCode = @Code", tenantId, code); if (schedule > 0) return (schedule, "排产结果工序 (ScheduleResultOpMaster.MoldTypeCode)"); return null; } private Task CountAsync(string sql, long tenantId, string code) => _db.Ado.GetIntAsync(sql, new List { new("@TenantId", tenantId), new("@Code", code) }); }