浏览代码

feat(s0): seed coherent UAT demo master data

为 UATTEST_CHL 租户(838257186181189)补齐 S0 演示主数据,形成"小而完整"
的 Golden Master Dataset,并修正两处历史悬空外键。DATA ONLY。

新增 151 行 / 就地修正 9 行:
- Employee +23(AIDOP 797 复制 19 + PMC/研发 UAT_DEMO_SEED 补位 4),
  7 个部门 7/7 全覆盖,5 名 login-bound assignee 就位
- Supplier +12(含 srm_purchase 实际引用的 10003232/10000015/10001597)
- NbrType +57 / Shelf +20 / CostCenter +8 / EmpWorkDuty +15
- ReviewCycle 族 +16(eto/mto/mts 等真实业务口径,拒收 AI_SEED 占位)
- 修正 WorkCtrMaster 7 条悬空 Department(RQRB50xx → 生产)
- 补正 WB8-QLA → 质量、WB8-EXA → 工程(仅 Department,不动 sys_user_id)

闭环结果:srm_purchase→Supplier 与 WorkCenter→Department 悬空归零;
NbrControl→NbrType 保留 42 条已知无源缺口,本批不造数。

边界:SCHEMA CHANGE = 0 / 后端业务代码 = 0 / 前端 = 0 / SysOrg = 0 /
DELETE = 0 / 源租户写入 = 0(PRE/POST 快照校验 11 表 cnt+sum_pk+checksum 全等)。

1.0.402.verify.sql 提供 59 条断言,随 AutoVersionUpdate 自动校验;
脚本幂等,重放 affected_rows = 0。

chore: bump version server 1.0.402(Web 不变,无前端改动)
YY968XX 2 天之前
父节点
当前提交
18f009c304

+ 9 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.401</AssemblyVersion>
-    <FileVersion>1.0.401</FileVersion>
-    <Version>1.0.401</Version>
+    <AssemblyVersion>1.0.402</AssemblyVersion>
+    <FileVersion>1.0.402</FileVersion>
+    <Version>1.0.402</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -547,6 +547,12 @@
     <None Update="UpdateScripts\1.0.400.sql">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="UpdateScripts\1.0.402.sql">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Update="UpdateScripts\1.0.402.verify.sql">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 
   <ItemGroup>

+ 397 - 0
server/Admin.NET.Web.Entry/UpdateScripts/1.0.402.sql

@@ -0,0 +1,397 @@
+-- ============================================================================
+-- S0-UAT-DEMO-MASTER-DATA-CLOSURE-1
+-- 为 UATTEST_CHL 租户补齐 S0 演示主数据(DATA ONLY)
+--
+-- 批次边界(经 CTO 三轮评审确认):
+--   SCHEMA CHANGE = 0 / BACKEND CODE = 0 / FRONTEND CHANGE = 0
+--   SYSORG CHANGE = 0(另开 S0-ORG-SCOPE-UI-DECOUPLE-1)
+--   DELETE = 0 / 源租户写入 = 0
+--
+-- 预期影响:INSERT 151 行 · UPDATE 9 行 · DELETE 0
+--
+-- 不纳入:ContractReviewCycle · default tenant cleanup · Routing legacy 36
+--         Location scope normalization · 2410/8010 全局统一 · Skill 族
+--
+-- 幂等:全部 INSERT 带 NOT EXISTS(按各表真实 UK);全部 UPDATE 带脏值集合限定。
+--       第二次执行 INSERT=0 / UPDATE=0。
+--
+-- ⚠️ 本文件为 WIP,不占正式版本号,AutoVersionUpdate 不会自动应用。
+--    提交时 rename 为 UpdateScripts/1.0.<next>.sql 并登记 csproj Copy 条目。
+-- ============================================================================
+
+-- ---------------------------------------------------------------------------
+-- 0. 作用域解析(一律按 SysOrg.Code 解析,禁止硬编码租户/组织 ID)
+-- ---------------------------------------------------------------------------
+SET @src := (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id = t.OrgId
+             WHERE o.Code = 'AIDOP' AND t.Status = 1 LIMIT 1);
+SET @dst := (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id = t.OrgId
+             WHERE o.Code = 'UATTEST_CHL' AND t.Status = 1 LIMIT 1);
+
+-- 作用域锚:取目标租户 DepartmentMaster 既有口径(838257186320453 / 838257186320453 / '8010')
+-- 刻意不从 LocationMaster(1000/1000) 或 srm_purchase(0/0) 取锚——那是 legacy 脏值,不可传播。
+SET @cid := (SELECT company_ref_id FROM DepartmentMaster WHERE tenant_id = @dst LIMIT 1);
+SET @fid := (SELECT factory_ref_id  FROM DepartmentMaster WHERE tenant_id = @dst LIMIT 1);
+SET @dom := (SELECT Domain          FROM DepartmentMaster WHERE tenant_id = @dst LIMIT 1);
+
+-- NbrTypeMaster 域口径:D3 修正 8011 → 8010(EVIDENCE-DRIVEN)
+--   NbrTypeReferencesAsync 只按 NbrType 匹配、不使用 Domain;
+--   ApplyDeptDescrAsync 需 NbrType.Domain 与 DepartmentMaster.Domain 对齐(= '8010')。
+SET @dom_nbr := @dom;
+
+-- 任一变量为 NULL 时,下方每条语句的 guard 条件使全脚本降为空操作。
+
+
+-- ===========================================================================
+-- P0-1  EmployeeMaster ← AIDOP 797:显式枚举 19 人           预计 INSERT 19
+--       仓储6 / 生产6 / 质量4 / 采购部2 / 工程1(后二者为源池真实上限)
+--       login-bound:lj08→UATPurchaseA,G00308→UATWarehouseA(随 INSERT 写入)
+-- ===========================================================================
+INSERT INTO EmployeeMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, Employee, Name, Sex, Phone, Email,
+     BirthDate, Department, DateEmployed, EmploymentStatus, MaritalStatus, JobTitle,
+     WorkCtr, CarId, DefaultWorkLocation, IsActive, sys_user_id, CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom,
+       s.Employee, s.Name, s.Sex, s.Phone, s.Email,
+       s.BirthDate, s.Department, s.DateEmployed, s.EmploymentStatus, s.MaritalStatus, s.JobTitle,
+       s.WorkCtr, s.CarId, s.DefaultWorkLocation, 1,
+       CASE s.Employee
+           WHEN 'lj08'   THEN (SELECT u.Id FROM SysUser u
+                               WHERE u.TenantId = @dst AND u.Account = 'UATPurchaseA'  AND u.Status = 1 LIMIT 1)
+           WHEN 'G00308' THEN (SELECT u.Id FROM SysUser u
+                               WHERE u.TenantId = @dst AND u.Account = 'UATWarehouseA' AND u.Status = 1 LIMIT 1)
+           ELSE NULL
+       END,
+       s.CreateUser, NOW()
+FROM EmployeeMaster s
+WHERE s.tenant_id = @src
+  AND s.Employee IN ('G00308','G00305','G00309','G00310','G00302','G00304',
+                     'RQTJ993','RQTJ987','RQTJ940','RQTJ939','RQTJ033','RQTJ022',
+                     'G00397','G00517','G00518','G00520',
+                     'lj08','qm2',
+                     'ZHR01')
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  -- 逻辑 FK 自守:目标租户必须已有同名部门,否则不插(防止造出悬空 Department)
+  AND EXISTS (SELECT 1 FROM DepartmentMaster d
+              WHERE d.tenant_id = @dst AND d.Domain = @dom AND d.Department = s.Department)
+  -- 幂等:UK(tenant_id, Domain, Employee)
+  AND NOT EXISTS (SELECT 1 FROM EmployeeMaster x
+                  WHERE x.tenant_id = @dst AND x.Domain = @dom AND x.Employee = s.Employee);
+
+
+-- ===========================================================================
+-- P0-2  EmployeeMaster ← UAT_DEMO_SEED:PMC / 研发 补位 4 人   预计 INSERT 4
+--       所有候选源租户(797 / 824 / AIDOP-Demo)这两个部门均为 0 人,
+--       故按「真实模板不存在 → 最小明确 Demo Seed」原则补位,provenance 显式标记。
+--       业务键前缀 UATD- 与源体系(G00xxx / RQTJxxx)刻意不同形,可一眼辨来源。
+--       login-bound:UATD-PMC01 → UATPlanA
+-- ===========================================================================
+INSERT INTO EmployeeMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, Employee, Name, Department,
+     JobTitle, EmploymentStatus, IsActive, sys_user_id, CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom,
+       s.Employee, s.Name, s.Department, s.JobTitle, 'AC', 1,
+       CASE s.Employee
+           WHEN 'UATD-PMC01' THEN (SELECT u.Id FROM SysUser u
+                                   WHERE u.TenantId = @dst AND u.Account = 'UATPlanA' AND u.Status = 1 LIMIT 1)
+           ELSE NULL
+       END,
+       'UAT_DEMO_SEED', NOW()
+FROM (
+        SELECT 'UATD-PMC01' AS Employee, 'PMC计划专员01' AS Name, 'PMC'  AS Department, '计划专员'   AS JobTitle
+  UNION ALL SELECT 'UATD-PMC02',          'PMC计划专员02',          'PMC',                '计划专员'
+  UNION ALL SELECT 'UATD-RD01',           '研发工程师01',            '研发',               '研发工程师'
+  UNION ALL SELECT 'UATD-RD02',           '研发工程师02',            '研发',               '研发工程师'
+) s
+WHERE @dst IS NOT NULL AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND EXISTS (SELECT 1 FROM DepartmentMaster d
+              WHERE d.tenant_id = @dst AND d.Domain = @dom AND d.Department = s.Department)
+  AND NOT EXISTS (SELECT 1 FROM EmployeeMaster x
+                  WHERE x.tenant_id = @dst AND x.Domain = @dom AND x.Employee = s.Employee);
+
+
+-- ===========================================================================
+-- P0-3  WB8 fixture 部门补正                                  预计 UPDATE 1 + 1
+--       只改 Department;禁止触碰 sys_user_id / SysUser / Role / Org / 其它字段。
+--       WB8-UNB 不动(其 sys_user_id IS NULL 是 WP-WB8「未绑定应仅告警」用例的刻意 fixture)。
+--       WP-WB8 回归安全:run_wp_wb8_api_loop.py 仅用 RecID 作 assigneeId、断言 sys_user_id 绑定态,
+--       从不读写 Department。
+-- ===========================================================================
+-- WB8-QLA → 质量:绑定 UATQualityA,其 10 个角色全部为质量检验角色(ROLE_QC / S5-IQC / S6-IPQC / S7-FQC)
+UPDATE EmployeeMaster
+SET Department = '质量', UpdateUser = 'UAT_DEMO_SEED', UpdateTime = NOW()
+WHERE tenant_id = @dst AND Employee = 'WB8-QLA'
+  AND (Department IS NULL OR Department = '')
+  AND @dst IS NOT NULL
+  AND EXISTS (SELECT 1 FROM DepartmentMaster d
+              WHERE d.tenant_id = @dst AND d.Department = '质量');
+
+-- WB8-EXA → 工程:绑定 UATExceptionA,角色 ROLE_EQUIP_MAINT(设备维修员);
+--   UAT A 七部门中无「设备部」,工程(艺工程)为唯一合理归属。
+UPDATE EmployeeMaster
+SET Department = '工程', UpdateUser = 'UAT_DEMO_SEED', UpdateTime = NOW()
+WHERE tenant_id = @dst AND Employee = 'WB8-EXA'
+  AND (Department IS NULL OR Department = '')
+  AND @dst IS NOT NULL
+  AND EXISTS (SELECT 1 FROM DepartmentMaster d
+              WHERE d.tenant_id = @dst AND d.Department = '工程');
+
+
+-- ===========================================================================
+-- P0-4  SuppMaster ← AIDOP 797:显式枚举 12 家                预计 INSERT 12
+--       前 3 家是 UAT A 现有 srm_purchase 实际引用但本租户缺失的供应商(闭合 0/3 → 3/3),
+--       后 9 家按源租户采购单量 Top 且中文名可读。
+-- ===========================================================================
+INSERT INTO SuppMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, Supp, SortName, PurContact,
+     CrTerms, Curr, Type, TaxIn, TaxClass, APStartDay1, APEndDay1, APStartDay2, APEndDay2,
+     Remark, IsActive, address, contact, position, contact_info, certification_level,
+     CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom,
+       s.Supp, s.SortName, s.PurContact,
+       s.CrTerms, s.Curr, s.Type, s.TaxIn, s.TaxClass, s.APStartDay1, s.APEndDay1, s.APStartDay2, s.APEndDay2,
+       s.Remark, 1, s.address, s.contact, s.position, s.contact_info, s.certification_level,
+       s.CreateUser, NOW()
+FROM SuppMaster s
+WHERE s.tenant_id = @src
+  AND s.Supp IN ('10003232','10000015','10001597',
+                 '10000094','10000047','10001001','10000018','10000090',
+                 '10001351','10001468','10000131','10004594')
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  -- 幂等:UK(tenant_id, factory_ref_id, Supp)
+  AND NOT EXISTS (SELECT 1 FROM SuppMaster x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid AND x.Supp = s.Supp);
+
+
+-- ===========================================================================
+-- P0-5  WorkCtrMaster 悬空部门修正                             预计 UPDATE 7
+--       UAT A 现有 7 条工作中心复制自 AIDOPB 824,Department 沿用其不可读部门码
+--       RQRB50xx,在本租户 DepartmentMaster 中 7/7 悬空。
+--       双重显式限定(业务键集合 ∩ 已审计脏值集合),不使用「凡悬空即改」的宽条件,
+--       不误伤未来新增的其它 WorkCenter。Domain 保持 '2410' 原样,本批不做域统一。
+-- ===========================================================================
+UPDATE WorkCtrMaster
+SET Department = '生产', UpdateUser = 'UAT_DEMO_SEED', UpdateTime = NOW()
+WHERE tenant_id = @dst
+  AND @dst IS NOT NULL
+  AND WorkCtr IN ('WC000005','WC000006','WC000007','WC000008','WC000009','WC000010','WC000011')
+  AND Department IN ('RQRB5009','RQRB5010','RQRB5011','RQRB5012','RQRB5013','RQRB5014','RQRB5015')
+  AND EXISTS (SELECT 1 FROM DepartmentMaster d
+              WHERE d.tenant_id = @dst AND d.Department = '生产');
+
+
+-- ===========================================================================
+-- P0-6  EmpWorkDutyMaster 构造                                预计 INSERT 15
+--       源侧 797 的 10 条全部属同一员工 G00308 且 ItemNum1 闭合 0/10(悬空),
+--       故不复制源行,改为按源 Duty 词表构造:5 名仓储员工 × 3 个真实库位。
+--       Duty 取值全部来自 797 真实数据,非杜撰。
+-- ===========================================================================
+INSERT INTO EmpWorkDutyMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, Employee, ItemNum1, Location,
+     Duty, EmpType, CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom, e.Employee, l.item_num, l.loc, l.duty, 'loc', 'UAT_DEMO_SEED', NOW()
+FROM (
+        SELECT 'G00302' AS Employee
+  UNION ALL SELECT 'G00304'
+  UNION ALL SELECT 'G00305'
+  UNION ALL SELECT 'G00308'
+  UNION ALL SELECT 'G00309'
+) e
+CROSS JOIN (
+        SELECT '1000' AS loc, 'rct-unp;IQC'             AS duty, '91C0D2C'  AS item_num  -- 待检库
+  UNION ALL SELECT '1001',     'iss-po;iss-wo;Up-Shelf',      '91C0D2C'                  -- 合格库
+  UNION ALL SELECT '8001',     'rct-wo;packing;Up-Shelf',     '91HC0497'                 -- 成品合格库
+) l
+WHERE @dst IS NOT NULL AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  -- 逻辑 FK 自守:员工 / 库位 / 物料必须已存在于本租户
+  AND EXISTS (SELECT 1 FROM EmployeeMaster x
+              WHERE x.tenant_id = @dst AND x.Domain = @dom AND x.Employee = e.Employee)
+  AND EXISTS (SELECT 1 FROM LocationMaster m
+              WHERE m.tenant_id = @dst AND m.location = l.loc)
+  AND EXISTS (SELECT 1 FROM ItemMaster i
+              WHERE i.tenant_id = @dst AND i.ItemNum = l.item_num)
+  -- 幂等:UK(tenant_id, Domain, Employee, Duty, Location)
+  AND NOT EXISTS (SELECT 1 FROM EmpWorkDutyMaster d
+                  WHERE d.tenant_id = @dst AND d.Domain = @dom
+                    AND d.Employee = e.Employee AND d.Duty = l.duty AND d.Location = l.loc);
+
+
+-- ===========================================================================
+-- P0-7  S0OrderReviewCycle ← AIDOP 797                        预计 INSERT 3
+--       eto=24h / mto=8h / mts=4h,真实 ETO/MTO/MTS 业务口径。
+--       AIDOP-Demo 侧同表为 AI-S0-CYCLE-00x(create_user='AI_SEED')合成占位,已拒收。
+-- ===========================================================================
+INSERT INTO S0OrderReviewCycle
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, order_type, std_hours,
+     remarks, is_active, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, s.order_type, s.std_hours,
+       s.remarks, s.is_active, 'UAT_DEMO_SEED', NOW()
+FROM S0OrderReviewCycle s
+WHERE s.tenant_id = @src
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM S0OrderReviewCycle x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid AND x.order_type = s.order_type);
+
+
+-- ===========================================================================
+-- P1-8  NbrTypeMaster ← AIDOP 797                             预计 INSERT 57
+--       仅复制 UAT A 的 NbrControl 实际使用、且源侧有定义的单号类型。
+--       57 行 / 49 个 distinct NbrType(UK 含 NbrClass,同 NbrType 可挂多个 NbrClass)。
+--       覆盖 UAT A 现有 91 个 NbrControl 类型中的 49 个;其余 42 个全库无定义(NO_SOURCE_AVAILABLE)。
+--       Domain = '8010'(D3 修正,见文件头 @dom_nbr)。
+-- ===========================================================================
+INSERT INTO NbrTypeMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, NbrClass, NbrType,
+     Descr1, Descr2, Acct, SubAcct, Dept, Name, Start, Nbr, UserID, Confirm,
+     Tran1, Tran2, Tran3, Tran4, Tran5, Page1, Page2, Page3, Page4, Page5,
+     IsActive, IsConfirm, CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom_nbr, s.NbrClass, s.NbrType,
+       s.Descr1, s.Descr2, s.Acct, s.SubAcct, s.Dept, s.Name, s.Start, s.Nbr, s.UserID, s.Confirm,
+       s.Tran1, s.Tran2, s.Tran3, s.Tran4, s.Tran5, s.Page1, s.Page2, s.Page3, s.Page4, s.Page5,
+       s.IsActive, s.IsConfirm, 'UAT_DEMO_SEED', NOW()
+FROM NbrTypeMaster s
+WHERE s.tenant_id = @src
+  AND s.NbrType IN (SELECT DISTINCT n.NbrType FROM NbrControl n WHERE n.tenant_id = @dst)
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom_nbr IS NOT NULL
+  -- 幂等:UK(tenant_id, Domain, NbrClass, NbrType);<=> 容 NULL 比较
+  AND NOT EXISTS (SELECT 1 FROM NbrTypeMaster x
+                  WHERE x.tenant_id = @dst AND x.Domain = @dom_nbr
+                    AND x.NbrClass <=> s.NbrClass AND x.NbrType = s.NbrType);
+
+
+-- ===========================================================================
+-- P1-9  LocationShelfMaster 构造                              预计 INSERT 20
+--       沿用 797 的 S0DEMO_BATCH1 货架命名模式(A01/A02),套用到 UAT A 自有 10 个库位。
+--       797 源表另有 2 条 LOC_TEST_* / INVALID_LOC_* 测试行,已拒收。
+--       ⚠️ domain_code 必须 = LocationMaster.Domain('8010')——
+--          AdoS0LocationShelvesController.ApplyLocationDescrAsync 按
+--          l.DomainCode(=LocationMaster.Domain) == shelf.DomainCode && l.Location == shelf.Location 关联,
+--          写错则库位说明与「库位:货架」拼接全空。
+-- ===========================================================================
+INSERT INTO LocationShelfMaster
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, location, inv_shelf,
+     descr, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, l.loc, s.shelf, 'S0DEMO_UAT_A', 'UAT_DEMO_SEED', NOW()
+FROM (
+        SELECT '1000' AS loc      -- 材料待检库
+  UNION ALL SELECT '1001'          -- 材料合格库
+  UNION ALL SELECT '1002'          -- 材料不合格库
+  UNION ALL SELECT '1005'          -- 辅料库
+  UNION ALL SELECT '5007'          -- 线边库
+  UNION ALL SELECT '5008'          -- 成品线边库
+  UNION ALL SELECT '8000'          -- 成品待检库
+  UNION ALL SELECT '8001'          -- 成品合格库
+  UNION ALL SELECT '8002'          -- 成品不合格库
+  UNION ALL SELECT '8005'          -- 研发成品库
+) l
+CROSS JOIN (SELECT 'A01' AS shelf UNION ALL SELECT 'A02') s
+WHERE @dst IS NOT NULL AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND EXISTS (SELECT 1 FROM LocationMaster m
+              WHERE m.tenant_id = @dst AND m.Domain = @dom AND m.location = l.loc)
+  -- 幂等:UK(tenant_id, domain_code, location, inv_shelf)
+  AND NOT EXISTS (SELECT 1 FROM LocationShelfMaster x
+                  WHERE x.tenant_id = @dst AND x.domain_code = @dom
+                    AND x.location = l.loc AND x.inv_shelf = s.shelf);
+
+
+-- ===========================================================================
+-- P1-10 CostCtrMaster ← AIDOP 797:显式枚举 8 个业务键        预计 INSERT 8
+--       源表 143 行中 56 行 Descr 为 NULL、部分为英文 HQ-* 串,演示无价值;
+--       本批固定 8 个中文可读成本中心,保证 Golden Dataset 长期确定性
+--       (不使用 ORDER BY ... LIMIT 8 这类会随数据漂移的选法)。
+--       CostCenter 经代码审计确认仅被自身页面引用,无跨表业务引用。
+-- ===========================================================================
+INSERT INTO CostCtrMaster
+    (company_ref_id, factory_ref_id, tenant_id, Domain, CostCtr, Descr, Ufld1, EffTime,
+     CreateUser, CreateTime)
+SELECT @cid, @fid, @dst, @dom, s.CostCtr, s.Descr, s.Ufld1, s.EffTime, 'UAT_DEMO_SEED', NOW()
+FROM CostCtrMaster s
+WHERE s.tenant_id = @src
+  AND s.CostCtr IN ('0000001001',   -- 调整成本中心-无业务范围
+                    '8010102010',   -- 厂务
+                    '8010301020',   -- IT 部门
+                    '8010401012',   -- 战略发展部
+                    '8010401060',   -- 合规部
+                    '8010501067',   -- 质量部-质量检验
+                    '8010602021',   -- 生产一部
+                    '8010602022')   -- 生产二部
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  -- 幂等:UK(tenant_id, Domain, CostCtr)
+  AND NOT EXISTS (SELECT 1 FROM CostCtrMaster x
+                  WHERE x.tenant_id = @dst AND x.Domain = @dom AND x.CostCtr = s.CostCtr);
+
+
+-- ===========================================================================
+-- P1-11 S0ProductDesignCycle ← AIDOP 797                      预计 INSERT 3
+--       RS12/standard=72h · RS30/non_standard=120h · RS50/standard=240h
+--       三个 item_type 在 UAT A 的 ItemMaster 中均真实存在,天然 FK 闭合。
+-- ===========================================================================
+INSERT INTO S0ProductDesignCycle
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, item_type, owner_application,
+     std_hours, is_active, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, s.item_type, s.owner_application,
+       s.std_hours, s.is_active, 'UAT_DEMO_SEED', NOW()
+FROM S0ProductDesignCycle s
+WHERE s.tenant_id = @src
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM S0ProductDesignCycle x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid
+                    AND x.item_type = s.item_type AND x.owner_application = s.owner_application);
+
+
+-- ===========================================================================
+-- P1-12 S0MaterialPlanCycle ← AIDOP 797                       预计 INSERT 3
+--       mto=48h · mts=24h · plan=72h
+-- ===========================================================================
+INSERT INTO S0MaterialPlanCycle
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, order_type, std_hours,
+     remarks, is_active, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, s.order_type, s.std_hours,
+       s.remarks, s.is_active, 'UAT_DEMO_SEED', NOW()
+FROM S0MaterialPlanCycle s
+WHERE s.tenant_id = @src
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM S0MaterialPlanCycle x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid AND x.order_type = s.order_type);
+
+
+-- ===========================================================================
+-- P1-13 S0OrderScheduleCycle ← AIDOP 797                      预计 INSERT 3
+--       mto=16h · mts=8h · non_mass=24h
+-- ===========================================================================
+INSERT INTO S0OrderScheduleCycle
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, order_type, std_hours,
+     remarks, is_active, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, s.order_type, s.std_hours,
+       s.remarks, s.is_active, 'UAT_DEMO_SEED', NOW()
+FROM S0OrderScheduleCycle s
+WHERE s.tenant_id = @src
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM S0OrderScheduleCycle x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid AND x.order_type = s.order_type);
+
+
+-- ===========================================================================
+-- P1-14 S0CategoryLeadTime ← AIDOP 797                        预计 INSERT 4
+--       CAT-CHIP/芯片=60d · CAT-SEMI/半导体=45d · CAT-SHEET/钣金件=21d · CAT-WIRE/线材=14d
+-- ===========================================================================
+INSERT INTO S0CategoryLeadTime
+    (company_ref_id, factory_ref_id, tenant_id, domain_code, category_code, category_name,
+     owner_application, lead_time_days, remarks, is_active, create_user, create_time)
+SELECT @cid, @fid, @dst, @dom, s.category_code, s.category_name,
+       s.owner_application, s.lead_time_days, s.remarks, s.is_active, 'UAT_DEMO_SEED', NOW()
+FROM S0CategoryLeadTime s
+WHERE s.tenant_id = @src
+  AND @src IS NOT NULL AND @dst IS NOT NULL
+  AND @cid IS NOT NULL AND @fid IS NOT NULL AND @dom IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM S0CategoryLeadTime x
+                  WHERE x.tenant_id = @dst AND x.factory_ref_id = @fid
+                    AND x.category_code = s.category_code
+                    AND x.owner_application = s.owner_application);

+ 141 - 0
server/Admin.NET.Web.Entry/UpdateScripts/1.0.402.verify.sql

@@ -0,0 +1,141 @@
+-- ============================================================================
+-- S0-UAT-DEMO-MASTER-DATA-CLOSURE-1 · VERIFY
+--
+-- ⚠️ 执行契约(AutoVersionUpdate.RunVerifyScriptIfExists):
+--    每条语句经 db.Ado.SqlQuerySingle<object>(statement) 执行,取「首行首列」,
+--    并要求 IsTruthy(result) == true,否则抛错、整个 migration 判 Failed。
+--    因此本文件的硬性约束:
+--      1) 禁止 SET @var —— SqlQuerySingle 对 SET 返回 null → 立即判失败;
+--         租户 ID 一律内联子查询,每条语句自包含。
+--      2) 每条语句必须返回「通过时为真」的标量(用 = / <=> 比较得 1/0),
+--         不能返回「错误条数」(0 = 通过会被 IsTruthy 判成 false)。
+--      3) 一条语句一个断言,不用多行 UNION 汇总。
+--
+-- 全部为 SELECT,无任何写操作。
+-- PRE 阶段可手工执行本文件了解基线;POST / RERUN 由 AutoVersionUpdate 自动运行。
+-- ============================================================================
+
+-- ---------------------------------------------------------------------------
+-- §1 目标计数(RERUN 后必须仍然成立 —— 这就是幂等的直接证据)
+-- ---------------------------------------------------------------------------
+SELECT COUNT(*) = 26 FROM EmployeeMaster        WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 13 FROM SuppMaster            WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 15 FROM EmpWorkDutyMaster     WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 57 FROM NbrTypeMaster         WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 20 FROM LocationShelfMaster   WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  8 FROM CostCtrMaster         WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  3 FROM S0OrderReviewCycle    WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  3 FROM S0ProductDesignCycle  WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  3 FROM S0MaterialPlanCycle   WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  3 FROM S0OrderScheduleCycle  WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  4 FROM S0CategoryLeadTime    WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+
+-- 25 名员工有部门(19 复制 + 4 seed + 2 WB8 补正;WB8-UNB 仍无部门,属刻意保留)
+SELECT COUNT(*) = 25 FROM EmployeeMaster WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND Department IS NOT NULL AND Department <> '';
+-- 5 名 login-bound assignee
+SELECT COUNT(*) =  5 FROM EmployeeMaster WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND sys_user_id IS NOT NULL;
+
+-- 回归:本批不得改动的对象
+SELECT COUNT(*) =  5 FROM CustMaster       WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  7 FROM DepartmentMaster WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 18 FROM LocationMaster   WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 20 FROM LineMaster       WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 56 FROM ItemMaster       WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) =  7 FROM WorkCtrMaster    WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+SELECT COUNT(*) = 91 FROM NbrControl       WHERE tenant_id = (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1);
+
+
+-- ---------------------------------------------------------------------------
+-- §2 逻辑 FK 校验
+-- ---------------------------------------------------------------------------
+-- 2.1 Employee → DepartmentMaster:无悬空
+SELECT COUNT(*) = 0 FROM EmployeeMaster e LEFT JOIN DepartmentMaster d ON d.tenant_id=e.tenant_id AND d.Department=e.Department WHERE e.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND e.Department IS NOT NULL AND e.Department<>'' AND d.RecID IS NULL;
+
+-- 2.2 Employee → SysUser 且同租户:无悬空
+SELECT COUNT(*) = 0 FROM EmployeeMaster e LEFT JOIN SysUser u ON u.Id=e.sys_user_id AND u.TenantId=e.tenant_id WHERE e.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND e.sys_user_id IS NOT NULL AND u.Id IS NULL;
+
+-- 2.3 srm_purchase → SuppMaster:PRE=3 悬空 → POST 必须 0
+SELECT COUNT(DISTINCT p.supplier_number) = 0 FROM srm_purchase p LEFT JOIN SuppMaster s ON s.tenant_id=p.tenant_id AND s.Supp=p.supplier_number WHERE p.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND s.RecID IS NULL;
+
+-- 2.4 WorkCtrMaster → DepartmentMaster:PRE=7 悬空 → POST 必须 0
+SELECT COUNT(*) = 0 FROM WorkCtrMaster w LEFT JOIN DepartmentMaster d ON d.tenant_id=w.tenant_id AND d.Department=w.Department WHERE w.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND d.RecID IS NULL;
+
+-- 2.5 EmpWorkDuty → Employee / Location / Item:无悬空
+SELECT COUNT(*) = 0 FROM EmpWorkDutyMaster k LEFT JOIN EmployeeMaster e ON e.tenant_id=k.tenant_id AND e.Domain=k.Domain AND e.Employee=k.Employee LEFT JOIN LocationMaster m ON m.tenant_id=k.tenant_id AND m.location=k.Location LEFT JOIN ItemMaster i ON i.tenant_id=k.tenant_id AND i.ItemNum=k.ItemNum1 WHERE k.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND (e.RecID IS NULL OR m.RecID IS NULL OR i.RecID IS NULL);
+
+-- 2.6 LocationShelf → LocationMaster(shelf.domain_code ↔ LocationMaster.Domain):无悬空
+SELECT COUNT(*) = 0 FROM LocationShelfMaster s LEFT JOIN LocationMaster l ON l.tenant_id=s.tenant_id AND l.Domain=s.domain_code AND l.location=s.location WHERE s.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND l.RecID IS NULL;
+
+-- 2.7 NbrControl → NbrTypeMaster:期望剩 42 条悬空(源租户仅覆盖 49/91,其余全库无定义,本批不造数)
+SELECT COUNT(*) = 42 FROM NbrControl n LEFT JOIN NbrTypeMaster t2 ON t2.tenant_id=n.tenant_id AND t2.NbrType=n.NbrType WHERE n.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND t2.RecID IS NULL;
+
+-- 2.8 ProductDesignCycle.item_type → ItemMaster.ItemType:无悬空
+SELECT COUNT(*) = 0 FROM S0ProductDesignCycle c WHERE c.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND NOT EXISTS (SELECT 1 FROM ItemMaster i WHERE i.tenant_id=c.tenant_id AND i.ItemType=c.item_type);
+
+-- 2.9 七部门 7/7 均至少 1 名员工
+SELECT COUNT(*) = 0 FROM (SELECT d.Department FROM DepartmentMaster d LEFT JOIN EmployeeMaster e ON e.tenant_id=d.tenant_id AND e.Department=d.Department WHERE d.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY d.Department HAVING COUNT(e.RecID)=0) z;
+
+-- 2.10 五名 login-bound assignee 均有合法部门
+SELECT COUNT(*) = 5 FROM SysUser u JOIN EmployeeMaster e ON e.sys_user_id=u.Id AND e.tenant_id=u.TenantId JOIN DepartmentMaster d ON d.tenant_id=e.tenant_id AND d.Department=e.Department WHERE u.TenantId=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND u.Account IN ('UATPlanA','UATPurchaseA','UATWarehouseA','UATQualityA','UATExceptionA');
+
+-- 2.11 WB8-UNB 保持未绑定(WP-WB8「未绑定应仅告警」用例的 fixture,不得被本批污染)
+SELECT COUNT(*) = 1 FROM EmployeeMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND Employee='WB8-UNB' AND sys_user_id IS NULL;
+
+
+-- ---------------------------------------------------------------------------
+-- §3 作用域纯净(新增/受影响表不得出现 0 / 1 / 默认租户 / NULL)
+-- ---------------------------------------------------------------------------
+SELECT COUNT(*) = 0 FROM EmployeeMaster      WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+SELECT COUNT(*) = 0 FROM SuppMaster          WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+SELECT COUNT(*) = 0 FROM EmpWorkDutyMaster   WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+SELECT COUNT(*) = 0 FROM NbrTypeMaster       WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+SELECT COUNT(*) = 0 FROM LocationShelfMaster WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+SELECT COUNT(*) = 0 FROM CostCtrMaster       WHERE tenant_id IS NULL OR tenant_id IN (0,1,1300000000001);
+
+-- 本批新增行(Domain='8010')的作用域三件套必须全部落在目标口径(company/factory = 部门锚)
+SELECT COUNT(*) = 0 FROM EmployeeMaster e WHERE e.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND e.Domain='8010' AND (e.company_ref_id <> (SELECT d.company_ref_id FROM DepartmentMaster d WHERE d.tenant_id=e.tenant_id LIMIT 1) OR e.factory_ref_id <> (SELECT d.factory_ref_id FROM DepartmentMaster d WHERE d.tenant_id=e.tenant_id LIMIT 1));
+-- 供应商同理
+SELECT COUNT(*) = 0 FROM SuppMaster s WHERE s.tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND (s.company_ref_id <> (SELECT d.company_ref_id FROM DepartmentMaster d WHERE d.tenant_id=s.tenant_id LIMIT 1) OR s.factory_ref_id <> (SELECT d.factory_ref_id FROM DepartmentMaster d WHERE d.tenant_id=s.tenant_id LIMIT 1));
+
+
+-- ---------------------------------------------------------------------------
+-- §4 源租户零变更守卫(标记式不变量,非绝对总数)
+--
+--   ⚠️ 刻意不写「源租户当前总数 = 464 / 3185 / …」这类硬编码常量:
+--      源租户会被其它合法批次正常增长,把它固化成 migration 契约,会在
+--      未来的 fresh environment / 延迟执行场景下产生误报。
+--
+--   本节改用「本批次写入标记」作为稳定不变量:本批所有写入一律带
+--   CreateUser / create_user = 'UAT_DEMO_SEED',且 tenant_id 恒为 @dst。
+--   若任何一条带本批标记的行出现在源租户,即证明发生了越界写入。
+--
+--   源租户「行数完全未变」的运行时证据由独立的
+--   WIP-S0-UAT-CLOSURE1.snapshot.sql 在 PRE / POST 两次执行后比对给出,
+--   不进入本永久 verify 契约。
+-- ---------------------------------------------------------------------------
+SELECT COUNT(*) = 0 FROM EmployeeMaster       WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND CreateUser  = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM SuppMaster           WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND CreateUser  = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM NbrTypeMaster        WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND CreateUser  = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM CostCtrMaster        WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND CreateUser  = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM EmpWorkDutyMaster    WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND CreateUser  = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM LocationShelfMaster  WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM S0OrderReviewCycle   WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM S0ProductDesignCycle WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM S0MaterialPlanCycle  WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM S0OrderScheduleCycle WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM S0CategoryLeadTime   WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND create_user = 'UAT_DEMO_SEED';
+-- 本批 UPDATE 同样只允许落在目标租户
+SELECT COUNT(*) = 0 FROM EmployeeMaster WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND UpdateUser = 'UAT_DEMO_SEED';
+SELECT COUNT(*) = 0 FROM WorkCtrMaster  WHERE tenant_id <> (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) AND UpdateUser = 'UAT_DEMO_SEED';
+
+
+-- ---------------------------------------------------------------------------
+-- §5 业务键唯一性(rerun 未产生重复的直接证据)
+-- ---------------------------------------------------------------------------
+SELECT COUNT(*) = 0 FROM (SELECT Domain, Employee FROM EmployeeMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY Domain, Employee HAVING COUNT(*)>1) a;
+SELECT COUNT(*) = 0 FROM (SELECT factory_ref_id, Supp FROM SuppMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY factory_ref_id, Supp HAVING COUNT(*)>1) b;
+SELECT COUNT(*) = 0 FROM (SELECT Domain, NbrClass, NbrType FROM NbrTypeMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY Domain, NbrClass, NbrType HAVING COUNT(*)>1) c;
+SELECT COUNT(*) = 0 FROM (SELECT Domain, CostCtr FROM CostCtrMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY Domain, CostCtr HAVING COUNT(*)>1) d;
+SELECT COUNT(*) = 0 FROM (SELECT Domain, Employee, Duty, Location FROM EmpWorkDutyMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY Domain, Employee, Duty, Location HAVING COUNT(*)>1) e;
+SELECT COUNT(*) = 0 FROM (SELECT domain_code, location, inv_shelf FROM LocationShelfMaster WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY domain_code, location, inv_shelf HAVING COUNT(*)>1) f;
+SELECT COUNT(*) = 0 FROM (SELECT factory_ref_id, order_type FROM S0OrderReviewCycle WHERE tenant_id=(SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id=t.OrgId WHERE o.Code='UATTEST_CHL' AND t.Status=1 LIMIT 1) GROUP BY factory_ref_id, order_type HAVING COUNT(*)>1) g;