Explorar o código

fix(s0): S0 租户隔离迁移正式化 — 12 张表补 tenant_id 并租户化唯一键

12 张 S0 列表主表原本没有 tenant_id 列,任何租户看到的都是同一批行。
本批为其补列、按数据关系回填归属、并把唯一键统一前置 tenant_id。

- Entity:12 个实体加 ITenantIdFilter + tenant_id(bigint null);
  11 个唯一索引前置 TenantId,与迁移脚本目标结构逐字对齐。
- Controller:12 个控制器去 [AllowAnonymous],改用 AdoS0TenantScope.TryResolveRequired
  + 查询显式带租户条件 + 插入显式盖章。超管绕过 AOP 过滤器的口径由显式 Where 收口。
- Migration 1.0.389.sql:幂等 ADD COLUMN + idx_tenant + 回填 + 唯一键租户化。
  回填不写死租户 ID,一律 JOIN SysOrg 按 factory 列反查 TenantId,解析不出保持 NULL。
  实测 90 行 100% 可回推至 797403760988230(AIDOP-Demo),无法回推 0 行。
  PriorityCode 已有正确 tenant_id(797403760988229),只做唯一键租户化、不回填。
- AidopS0ExcelColumnMigration:移除启动期 EnsureContractReviewCycleDemoStages
  (无用户上下文向业务表写 demo 行,会产生 tenant_id=NULL 的不可见数据);
  S0OrderReviewCycle 的启动期唯一索引同步为 (tenant_id, factory_ref_id, order_type)。
- 测试:12 个实体纳入 S0TenantEntityContractTests,46 项通过。

预期副作用:那 90 行归位 AIDOP-Demo 后不再出现在 AIDOP(797403760988229) 的对应页面。
这是正确的租户归属,不是缺陷。

S0ProcessFlowCard 的租户化随其组织解析改造单独落地,不在本批。
YY968XX hai 1 día
pai
achega
488c1f6c3f
Modificáronse 27 ficheiros con 594 adicións e 143 borrados
  1. 7 4
      server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj
  2. 345 0
      server/Admin.NET.Web.Entry/UpdateScripts/1.0.389.sql
  3. 15 0
      server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S0/Tenant/S0TenantEntityContractTests.cs
  4. 14 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgOrderScheduleCyclesController.cs
  5. 14 5
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgPersonSkillsController.cs
  6. 12 5
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgWorkOrderControlsController.cs
  7. 14 5
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCycleBreakdownsController.cs
  8. 6 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCycleSyncFlagsController.cs
  9. 18 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCyclesController.cs
  10. 12 5
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0OrderPriorityRulesController.cs
  11. 14 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0OrderReviewCyclesController.cs
  12. 17 8
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0PriorityRulesController.cs
  13. 14 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ProductDesignCyclesController.cs
  14. 17 9
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0CategoryLeadTimesController.cs
  15. 14 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0MaterialPlanCyclesController.cs
  16. 12 4
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0MfgMastersAndSop.cs
  17. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0OrderScheduleCycle.cs
  18. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycle.cs
  19. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycleBreakdown.cs
  20. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycleSyncFlag.cs
  21. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0OrderReviewCycle.cs
  22. 4 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0PriorityCode.cs
  23. 4 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0PriorityRule.cs
  24. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ProductDesignCycle.cs
  25. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0CategoryLeadTime.cs
  26. 5 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0MaterialPlanCycle.cs
  27. 1 52
      server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopS0ExcelColumnMigration.cs

+ 7 - 4
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <TargetFrameworks>net10.0</TargetFrameworks>
@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.388</AssemblyVersion>
-    <FileVersion>1.0.388</FileVersion>
-    <Version>1.0.388</Version>
+    <AssemblyVersion>1.0.389</AssemblyVersion>
+    <FileVersion>1.0.389</FileVersion>
+    <Version>1.0.389</Version>
   </PropertyGroup>
 
   <ItemGroup>
@@ -505,6 +505,9 @@
     <None Update="UpdateScripts\1.0.388.sql">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="UpdateScripts\1.0.389.sql">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
     <None Update="UpdateScripts\1.0.364.verify.sql">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>

+ 345 - 0
server/Admin.NET.Web.Entry/UpdateScripts/1.0.389.sql

@@ -0,0 +1,345 @@
+-- S0 租户隔离补齐:为 12 张缺 tenant_id 的 S0 列表主表补列 + 索引 + 存量行归属回填 + 唯一键租户化。
+--
+-- 归属证据(2026-08-25 只读核对,全部行可 100%% 回推,无法回推 = 0):
+--   这些表的存量行引用 SysOrg 1329900200001/…002/…003,而这三个组织由 SeedData/S0OrgSeedData.cs
+--   灌到 OrderFlowConstants.DemoTenantId = 797403760988230(AIDOP-Demo),**不是** AIDOP 797403760988229。
+--   合计回填 90 行:PersonSkill 36 / S0ContractReviewCycleBreakdown 18 / S0ContractReviewCycle 10 /
+--   S0OrderScheduleCycle 5 / S0OrderReviewCycle 5 / S0ProductDesignCycle 5 / S0CategoryLeadTime 5 /
+--   S0MaterialPlanCycle 5 / S0ContractReviewCycleSyncFlag 1。
+--   预期副作用:这批行归位 AIDOP-Demo 后,将不再出现在 AIDOP(797…229) 的对应页面——这是正确的租户归属,不是缺陷。
+--
+-- 回填口径:不写死租户 ID,一律 JOIN SysOrg 按 factory 列反查 TenantId;解析不出的行保持 NULL(不猜)。
+-- PriorityCode 例外:该表本就有 tenant_id 且 4 行均为 797403760988229,无 factory 列,故只做唯一键租户化、不回填。
+-- 空表(WorkOrderControl / ado_s0_sales_order_priority_rule)只做 ADD COLUMN + 索引,不伪造回填。
+--
+-- 唯一键:原唯一键均不含租户,而应用层唯一性预检已改为租户内判重,二者口径必须一致,故统一前置 tenant_id。
+-- 索引名保持不变,使 AidopS0ExcelColumnMigration.EnsureUniqueIndex(按名判存在)不会把旧结构建回来。
+--
+-- 幂等:ADD COLUMN / ADD INDEX 均有 information_schema 存在性保护;回填带 WHERE tenant_id IS NULL;
+--       唯一键重建按实际列清单比对,已是目标结构则跳过。重复执行无副作用。
+
+-- [1/12] S0OrderScheduleCycle
+SET @t1 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderScheduleCycle');
+SET @c1 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderScheduleCycle' AND COLUMN_NAME='tenant_id');
+SET @a1 := CASE WHEN @t1=1 AND @c1=0
+  THEN 'ALTER TABLE `S0OrderScheduleCycle` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p1a FROM @a1; EXECUTE p1a; DEALLOCATE PREPARE p1a;
+
+SET @i1 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderScheduleCycle' AND INDEX_NAME='idx_tenant');
+SET @b1 := CASE WHEN @t1=1 AND @i1=0
+  THEN 'ALTER TABLE `S0OrderScheduleCycle` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p1b FROM @b1; EXECUTE p1b; DEALLOCATE PREPARE p1b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d1 := CASE WHEN @t1=1
+  THEN 'UPDATE `S0OrderScheduleCycle` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p1d FROM @d1; EXECUTE p1d; DEALLOCATE PREPARE p1d;
+
+SET @u1 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderScheduleCycle' AND INDEX_NAME='uk_S0OrderScheduleCycle_factory_ordertype');
+SET @e1 := CASE
+  WHEN @t1<>1 THEN 'SELECT 1'
+  WHEN @u1 = 'tenant_id,factory_ref_id,order_type' THEN 'SELECT 1'
+  WHEN @u1 IS NULL THEN 'ALTER TABLE `S0OrderScheduleCycle` ADD UNIQUE INDEX `uk_S0OrderScheduleCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)'
+  ELSE 'ALTER TABLE `S0OrderScheduleCycle` DROP INDEX `uk_S0OrderScheduleCycle_factory_ordertype`, ADD UNIQUE INDEX `uk_S0OrderScheduleCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)' END;
+PREPARE p1e FROM @e1; EXECUTE p1e; DEALLOCATE PREPARE p1e;
+
+-- [2/12] WorkOrderControl
+SET @t2 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='WorkOrderControl');
+SET @c2 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='WorkOrderControl' AND COLUMN_NAME='tenant_id');
+SET @a2 := CASE WHEN @t2=1 AND @c2=0
+  THEN 'ALTER TABLE `WorkOrderControl` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p2a FROM @a2; EXECUTE p2a; DEALLOCATE PREPARE p2a;
+
+SET @i2 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='WorkOrderControl' AND INDEX_NAME='idx_tenant');
+SET @b2 := CASE WHEN @t2=1 AND @i2=0
+  THEN 'ALTER TABLE `WorkOrderControl` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p2b FROM @b2; EXECUTE p2b; DEALLOCATE PREPARE p2b;
+
+-- 回填:按 FactoryRefId 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d2 := CASE WHEN @t2=1
+  THEN 'UPDATE `WorkOrderControl` t JOIN `SysOrg` o ON o.`Id` = t.`FactoryRefId` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p2d FROM @d2; EXECUTE p2d; DEALLOCATE PREPARE p2d;
+
+SET @u2 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='WorkOrderControl' AND INDEX_NAME='uk_WorkOrderControl_code');
+SET @e2 := CASE
+  WHEN @t2<>1 THEN 'SELECT 1'
+  WHEN @u2 = 'tenant_id,CompanyRefId,FactoryRefId,Code' THEN 'SELECT 1'
+  WHEN @u2 IS NULL THEN 'ALTER TABLE `WorkOrderControl` ADD UNIQUE INDEX `uk_WorkOrderControl_code` (`tenant_id`,`CompanyRefId`,`FactoryRefId`,`Code`)'
+  ELSE 'ALTER TABLE `WorkOrderControl` DROP INDEX `uk_WorkOrderControl_code`, ADD UNIQUE INDEX `uk_WorkOrderControl_code` (`tenant_id`,`CompanyRefId`,`FactoryRefId`,`Code`)' END;
+PREPARE p2e FROM @e2; EXECUTE p2e; DEALLOCATE PREPARE p2e;
+
+-- [3/12] PersonSkill
+SET @t3 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PersonSkill');
+SET @c3 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PersonSkill' AND COLUMN_NAME='tenant_id');
+SET @a3 := CASE WHEN @t3=1 AND @c3=0
+  THEN 'ALTER TABLE `PersonSkill` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p3a FROM @a3; EXECUTE p3a; DEALLOCATE PREPARE p3a;
+
+SET @i3 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PersonSkill' AND INDEX_NAME='idx_tenant');
+SET @b3 := CASE WHEN @t3=1 AND @i3=0
+  THEN 'ALTER TABLE `PersonSkill` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p3b FROM @b3; EXECUTE p3b; DEALLOCATE PREPARE p3b;
+
+-- 回填:按 FactoryRefId 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d3 := CASE WHEN @t3=1
+  THEN 'UPDATE `PersonSkill` t JOIN `SysOrg` o ON o.`Id` = t.`FactoryRefId` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p3d FROM @d3; EXECUTE p3d; DEALLOCATE PREPARE p3d;
+
+SET @u3 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PersonSkill' AND INDEX_NAME='uk_PersonSkill_code');
+SET @e3 := CASE
+  WHEN @t3<>1 THEN 'SELECT 1'
+  WHEN @u3 = 'tenant_id,CompanyRefId,FactoryRefId,Code' THEN 'SELECT 1'
+  WHEN @u3 IS NULL THEN 'ALTER TABLE `PersonSkill` ADD UNIQUE INDEX `uk_PersonSkill_code` (`tenant_id`,`CompanyRefId`,`FactoryRefId`,`Code`)'
+  ELSE 'ALTER TABLE `PersonSkill` DROP INDEX `uk_PersonSkill_code`, ADD UNIQUE INDEX `uk_PersonSkill_code` (`tenant_id`,`CompanyRefId`,`FactoryRefId`,`Code`)' END;
+PREPARE p3e FROM @e3; EXECUTE p3e; DEALLOCATE PREPARE p3e;
+
+-- [4/12] S0ContractReviewCycle
+SET @t4 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycle');
+SET @c4 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycle' AND COLUMN_NAME='tenant_id');
+SET @a4 := CASE WHEN @t4=1 AND @c4=0
+  THEN 'ALTER TABLE `S0ContractReviewCycle` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p4a FROM @a4; EXECUTE p4a; DEALLOCATE PREPARE p4a;
+
+SET @i4 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycle' AND INDEX_NAME='idx_tenant');
+SET @b4 := CASE WHEN @t4=1 AND @i4=0
+  THEN 'ALTER TABLE `S0ContractReviewCycle` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p4b FROM @b4; EXECUTE p4b; DEALLOCATE PREPARE p4b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d4 := CASE WHEN @t4=1
+  THEN 'UPDATE `S0ContractReviewCycle` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p4d FROM @d4; EXECUTE p4d; DEALLOCATE PREPARE p4d;
+
+SET @u4 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycle' AND INDEX_NAME='uk_S0ContractReviewCycle_factory_stage');
+SET @e4 := CASE
+  WHEN @t4<>1 THEN 'SELECT 1'
+  WHEN @u4 = 'tenant_id,factory_ref_id,stage_code' THEN 'SELECT 1'
+  WHEN @u4 IS NULL THEN 'ALTER TABLE `S0ContractReviewCycle` ADD UNIQUE INDEX `uk_S0ContractReviewCycle_factory_stage` (`tenant_id`,`factory_ref_id`,`stage_code`)'
+  ELSE 'ALTER TABLE `S0ContractReviewCycle` DROP INDEX `uk_S0ContractReviewCycle_factory_stage`, ADD UNIQUE INDEX `uk_S0ContractReviewCycle_factory_stage` (`tenant_id`,`factory_ref_id`,`stage_code`)' END;
+PREPARE p4e FROM @e4; EXECUTE p4e; DEALLOCATE PREPARE p4e;
+
+-- [5/12] S0ContractReviewCycleBreakdown
+SET @t5 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleBreakdown');
+SET @c5 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleBreakdown' AND COLUMN_NAME='tenant_id');
+SET @a5 := CASE WHEN @t5=1 AND @c5=0
+  THEN 'ALTER TABLE `S0ContractReviewCycleBreakdown` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p5a FROM @a5; EXECUTE p5a; DEALLOCATE PREPARE p5a;
+
+SET @i5 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleBreakdown' AND INDEX_NAME='idx_tenant');
+SET @b5 := CASE WHEN @t5=1 AND @i5=0
+  THEN 'ALTER TABLE `S0ContractReviewCycleBreakdown` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p5b FROM @b5; EXECUTE p5b; DEALLOCATE PREPARE p5b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d5 := CASE WHEN @t5=1
+  THEN 'UPDATE `S0ContractReviewCycleBreakdown` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p5d FROM @d5; EXECUTE p5d; DEALLOCATE PREPARE p5d;
+
+SET @u5 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleBreakdown' AND INDEX_NAME='uk_S0CRCBreakdown_factory_parent_group');
+SET @e5 := CASE
+  WHEN @t5<>1 THEN 'SELECT 1'
+  WHEN @u5 = 'tenant_id,factory_ref_id,parent_stage_code,group_code' THEN 'SELECT 1'
+  WHEN @u5 IS NULL THEN 'ALTER TABLE `S0ContractReviewCycleBreakdown` ADD UNIQUE INDEX `uk_S0CRCBreakdown_factory_parent_group` (`tenant_id`,`factory_ref_id`,`parent_stage_code`,`group_code`)'
+  ELSE 'ALTER TABLE `S0ContractReviewCycleBreakdown` DROP INDEX `uk_S0CRCBreakdown_factory_parent_group`, ADD UNIQUE INDEX `uk_S0CRCBreakdown_factory_parent_group` (`tenant_id`,`factory_ref_id`,`parent_stage_code`,`group_code`)' END;
+PREPARE p5e FROM @e5; EXECUTE p5e; DEALLOCATE PREPARE p5e;
+
+-- [6/12] S0ContractReviewCycleSyncFlag
+SET @t6 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleSyncFlag');
+SET @c6 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleSyncFlag' AND COLUMN_NAME='tenant_id');
+SET @a6 := CASE WHEN @t6=1 AND @c6=0
+  THEN 'ALTER TABLE `S0ContractReviewCycleSyncFlag` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p6a FROM @a6; EXECUTE p6a; DEALLOCATE PREPARE p6a;
+
+SET @i6 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleSyncFlag' AND INDEX_NAME='idx_tenant');
+SET @b6 := CASE WHEN @t6=1 AND @i6=0
+  THEN 'ALTER TABLE `S0ContractReviewCycleSyncFlag` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p6b FROM @b6; EXECUTE p6b; DEALLOCATE PREPARE p6b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d6 := CASE WHEN @t6=1
+  THEN 'UPDATE `S0ContractReviewCycleSyncFlag` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p6d FROM @d6; EXECUTE p6d; DEALLOCATE PREPARE p6d;
+
+SET @u6 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ContractReviewCycleSyncFlag' AND INDEX_NAME='uk_S0CRCSyncFlag_factory');
+SET @e6 := CASE
+  WHEN @t6<>1 THEN 'SELECT 1'
+  WHEN @u6 = 'tenant_id,factory_ref_id' THEN 'SELECT 1'
+  WHEN @u6 IS NULL THEN 'ALTER TABLE `S0ContractReviewCycleSyncFlag` ADD UNIQUE INDEX `uk_S0CRCSyncFlag_factory` (`tenant_id`,`factory_ref_id`)'
+  ELSE 'ALTER TABLE `S0ContractReviewCycleSyncFlag` DROP INDEX `uk_S0CRCSyncFlag_factory`, ADD UNIQUE INDEX `uk_S0CRCSyncFlag_factory` (`tenant_id`,`factory_ref_id`)' END;
+PREPARE p6e FROM @e6; EXECUTE p6e; DEALLOCATE PREPARE p6e;
+
+-- [7/12] S0OrderReviewCycle
+SET @t7 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderReviewCycle');
+SET @c7 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderReviewCycle' AND COLUMN_NAME='tenant_id');
+SET @a7 := CASE WHEN @t7=1 AND @c7=0
+  THEN 'ALTER TABLE `S0OrderReviewCycle` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p7a FROM @a7; EXECUTE p7a; DEALLOCATE PREPARE p7a;
+
+SET @i7 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderReviewCycle' AND INDEX_NAME='idx_tenant');
+SET @b7 := CASE WHEN @t7=1 AND @i7=0
+  THEN 'ALTER TABLE `S0OrderReviewCycle` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p7b FROM @b7; EXECUTE p7b; DEALLOCATE PREPARE p7b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d7 := CASE WHEN @t7=1
+  THEN 'UPDATE `S0OrderReviewCycle` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p7d FROM @d7; EXECUTE p7d; DEALLOCATE PREPARE p7d;
+
+SET @u7 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0OrderReviewCycle' AND INDEX_NAME='uk_S0OrderReviewCycle_factory_ordertype');
+SET @e7 := CASE
+  WHEN @t7<>1 THEN 'SELECT 1'
+  WHEN @u7 = 'tenant_id,factory_ref_id,order_type' THEN 'SELECT 1'
+  WHEN @u7 IS NULL THEN 'ALTER TABLE `S0OrderReviewCycle` ADD UNIQUE INDEX `uk_S0OrderReviewCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)'
+  ELSE 'ALTER TABLE `S0OrderReviewCycle` DROP INDEX `uk_S0OrderReviewCycle_factory_ordertype`, ADD UNIQUE INDEX `uk_S0OrderReviewCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)' END;
+PREPARE p7e FROM @e7; EXECUTE p7e; DEALLOCATE PREPARE p7e;
+
+-- [8/12] PriorityCode
+SET @t8 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PriorityCode');
+SET @c8 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PriorityCode' AND COLUMN_NAME='tenant_id');
+SET @a8 := CASE WHEN @t8=1 AND @c8=0
+  THEN 'ALTER TABLE `PriorityCode` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p8a FROM @a8; EXECUTE p8a; DEALLOCATE PREPARE p8a;
+
+SET @i8 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PriorityCode' AND INDEX_NAME='idx_tenant');
+SET @b8 := CASE WHEN @t8=1 AND @i8=0
+  THEN 'ALTER TABLE `PriorityCode` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p8b FROM @b8; EXECUTE p8b; DEALLOCATE PREPARE p8b;
+
+-- 不回填:PriorityCode 无 factory 列,存量 tenant_id 已正确(797403760988229)。
+
+SET @u8 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='PriorityCode' AND INDEX_NAME='uk_PriorityCode_scope');
+SET @e8 := CASE
+  WHEN @t8<>1 THEN 'SELECT 1'
+  WHEN @u8 = 'tenant_id,Descr' THEN 'SELECT 1'
+  WHEN @u8 IS NULL THEN 'ALTER TABLE `PriorityCode` ADD UNIQUE INDEX `uk_PriorityCode_scope` (`tenant_id`,`Descr`)'
+  ELSE 'ALTER TABLE `PriorityCode` DROP INDEX `uk_PriorityCode_scope`, ADD UNIQUE INDEX `uk_PriorityCode_scope` (`tenant_id`,`Descr`)' END;
+PREPARE p8e FROM @e8; EXECUTE p8e; DEALLOCATE PREPARE p8e;
+
+-- [9/12] ado_s0_sales_order_priority_rule
+SET @t9 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='ado_s0_sales_order_priority_rule');
+SET @c9 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='ado_s0_sales_order_priority_rule' AND COLUMN_NAME='tenant_id');
+SET @a9 := CASE WHEN @t9=1 AND @c9=0
+  THEN 'ALTER TABLE `ado_s0_sales_order_priority_rule` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p9a FROM @a9; EXECUTE p9a; DEALLOCATE PREPARE p9a;
+
+SET @i9 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='ado_s0_sales_order_priority_rule' AND INDEX_NAME='idx_tenant');
+SET @b9 := CASE WHEN @t9=1 AND @i9=0
+  THEN 'ALTER TABLE `ado_s0_sales_order_priority_rule` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p9b FROM @b9; EXECUTE p9b; DEALLOCATE PREPARE p9b;
+
+-- 回填:按 FactoryRefId 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d9 := CASE WHEN @t9=1
+  THEN 'UPDATE `ado_s0_sales_order_priority_rule` t JOIN `SysOrg` o ON o.`Id` = t.`FactoryRefId` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p9d FROM @d9; EXECUTE p9d; DEALLOCATE PREPARE p9d;
+
+-- ado_s0_sales_order_priority_rule 无业务唯一键,无需租户化。
+
+-- [10/12] S0ProductDesignCycle
+SET @t10 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ProductDesignCycle');
+SET @c10 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ProductDesignCycle' AND COLUMN_NAME='tenant_id');
+SET @a10 := CASE WHEN @t10=1 AND @c10=0
+  THEN 'ALTER TABLE `S0ProductDesignCycle` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p10a FROM @a10; EXECUTE p10a; DEALLOCATE PREPARE p10a;
+
+SET @i10 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ProductDesignCycle' AND INDEX_NAME='idx_tenant');
+SET @b10 := CASE WHEN @t10=1 AND @i10=0
+  THEN 'ALTER TABLE `S0ProductDesignCycle` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p10b FROM @b10; EXECUTE p10b; DEALLOCATE PREPARE p10b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d10 := CASE WHEN @t10=1
+  THEN 'UPDATE `S0ProductDesignCycle` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p10d FROM @d10; EXECUTE p10d; DEALLOCATE PREPARE p10d;
+
+SET @u10 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0ProductDesignCycle' AND INDEX_NAME='uk_S0ProductDesignCycle_factory_type_attr');
+SET @e10 := CASE
+  WHEN @t10<>1 THEN 'SELECT 1'
+  WHEN @u10 = 'tenant_id,factory_ref_id,item_type,owner_application' THEN 'SELECT 1'
+  WHEN @u10 IS NULL THEN 'ALTER TABLE `S0ProductDesignCycle` ADD UNIQUE INDEX `uk_S0ProductDesignCycle_factory_type_attr` (`tenant_id`,`factory_ref_id`,`item_type`,`owner_application`)'
+  ELSE 'ALTER TABLE `S0ProductDesignCycle` DROP INDEX `uk_S0ProductDesignCycle_factory_type_attr`, ADD UNIQUE INDEX `uk_S0ProductDesignCycle_factory_type_attr` (`tenant_id`,`factory_ref_id`,`item_type`,`owner_application`)' END;
+PREPARE p10e FROM @e10; EXECUTE p10e; DEALLOCATE PREPARE p10e;
+
+-- [11/12] S0CategoryLeadTime
+SET @t11 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0CategoryLeadTime');
+SET @c11 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0CategoryLeadTime' AND COLUMN_NAME='tenant_id');
+SET @a11 := CASE WHEN @t11=1 AND @c11=0
+  THEN 'ALTER TABLE `S0CategoryLeadTime` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p11a FROM @a11; EXECUTE p11a; DEALLOCATE PREPARE p11a;
+
+SET @i11 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0CategoryLeadTime' AND INDEX_NAME='idx_tenant');
+SET @b11 := CASE WHEN @t11=1 AND @i11=0
+  THEN 'ALTER TABLE `S0CategoryLeadTime` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p11b FROM @b11; EXECUTE p11b; DEALLOCATE PREPARE p11b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d11 := CASE WHEN @t11=1
+  THEN 'UPDATE `S0CategoryLeadTime` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p11d FROM @d11; EXECUTE p11d; DEALLOCATE PREPARE p11d;
+
+SET @u11 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0CategoryLeadTime' AND INDEX_NAME='uk_S0CategoryLeadTime_factory_category_owner');
+SET @e11 := CASE
+  WHEN @t11<>1 THEN 'SELECT 1'
+  WHEN @u11 = 'tenant_id,factory_ref_id,category_code,owner_application' THEN 'SELECT 1'
+  WHEN @u11 IS NULL THEN 'ALTER TABLE `S0CategoryLeadTime` ADD UNIQUE INDEX `uk_S0CategoryLeadTime_factory_category_owner` (`tenant_id`,`factory_ref_id`,`category_code`,`owner_application`)'
+  ELSE 'ALTER TABLE `S0CategoryLeadTime` DROP INDEX `uk_S0CategoryLeadTime_factory_category_owner`, ADD UNIQUE INDEX `uk_S0CategoryLeadTime_factory_category_owner` (`tenant_id`,`factory_ref_id`,`category_code`,`owner_application`)' END;
+PREPARE p11e FROM @e11; EXECUTE p11e; DEALLOCATE PREPARE p11e;
+
+-- [12/12] S0MaterialPlanCycle
+SET @t12 := (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0MaterialPlanCycle');
+SET @c12 := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0MaterialPlanCycle' AND COLUMN_NAME='tenant_id');
+SET @a12 := CASE WHEN @t12=1 AND @c12=0
+  THEN 'ALTER TABLE `S0MaterialPlanCycle` ADD COLUMN `tenant_id` BIGINT NULL COMMENT ''租户Id'''
+  ELSE 'SELECT 1' END;
+PREPARE p12a FROM @a12; EXECUTE p12a; DEALLOCATE PREPARE p12a;
+
+SET @i12 := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0MaterialPlanCycle' AND INDEX_NAME='idx_tenant');
+SET @b12 := CASE WHEN @t12=1 AND @i12=0
+  THEN 'ALTER TABLE `S0MaterialPlanCycle` ADD INDEX `idx_tenant` (`tenant_id`)'
+  ELSE 'SELECT 1' END;
+PREPARE p12b FROM @b12; EXECUTE p12b; DEALLOCATE PREPARE p12b;
+
+-- 回填:按 factory_ref_id 反查 SysOrg.TenantId(解析不出则保持 NULL)
+SET @d12 := CASE WHEN @t12=1
+  THEN 'UPDATE `S0MaterialPlanCycle` t JOIN `SysOrg` o ON o.`Id` = t.`factory_ref_id` SET t.`tenant_id` = o.`TenantId` WHERE t.`tenant_id` IS NULL AND o.`TenantId` IS NOT NULL'
+  ELSE 'SELECT 1' END;
+PREPARE p12d FROM @d12; EXECUTE p12d; DEALLOCATE PREPARE p12d;
+
+SET @u12 := (SELECT GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='S0MaterialPlanCycle' AND INDEX_NAME='uk_S0MaterialPlanCycle_factory_ordertype');
+SET @e12 := CASE
+  WHEN @t12<>1 THEN 'SELECT 1'
+  WHEN @u12 = 'tenant_id,factory_ref_id,order_type' THEN 'SELECT 1'
+  WHEN @u12 IS NULL THEN 'ALTER TABLE `S0MaterialPlanCycle` ADD UNIQUE INDEX `uk_S0MaterialPlanCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)'
+  ELSE 'ALTER TABLE `S0MaterialPlanCycle` DROP INDEX `uk_S0MaterialPlanCycle_factory_ordertype`, ADD UNIQUE INDEX `uk_S0MaterialPlanCycle_factory_ordertype` (`tenant_id`,`factory_ref_id`,`order_type`)' END;
+PREPARE p12e FROM @e12; EXECUTE p12e; DEALLOCATE PREPARE p12e;

+ 15 - 0
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S0/Tenant/S0TenantEntityContractTests.cs

@@ -49,6 +49,21 @@ public class S0TenantEntityContractTests
         yield return [typeof(AdoS0WorkOrdControl)];
         yield return [typeof(IcSubstitute)];
         yield return [typeof(AdoS0SrmPurchase)];
+
+        // 2026-08-25 批次:原本无 tenant_id 的 12 张 S0 列表主表,随 ITenantIdFilter 一并纳入契约。
+        // (AdoS0ProcessFlowCard 的租户化随其组织解析改造单独落地,届时再补入本清单。)
+        yield return [typeof(AdoS0OrderScheduleCycle)];
+        yield return [typeof(AdoS0MfgWorkOrderControl)];
+        yield return [typeof(AdoS0MfgPersonSkill)];
+        yield return [typeof(AdoS0ContractReviewCycle)];
+        yield return [typeof(AdoS0ContractReviewCycleBreakdown)];
+        yield return [typeof(AdoS0ContractReviewCycleSyncFlag)];
+        yield return [typeof(AdoS0OrderReviewCycle)];
+        yield return [typeof(AdoS0PriorityCode)];
+        yield return [typeof(AdoS0PriorityRule)];
+        yield return [typeof(AdoS0ProductDesignCycle)];
+        yield return [typeof(AdoS0CategoryLeadTime)];
+        yield return [typeof(AdoS0MaterialPlanCycle)];
     }
 
     [Theory]

+ 14 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgOrderScheduleCyclesController.cs

@@ -6,7 +6,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
 
 [ApiController]
 [Route("api/s0/manufacturing/order-schedule-cycles")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
 {
@@ -19,11 +18,13 @@ public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0OrderScheduleCycleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var page = q.EffectivePage;
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -42,20 +43,23 @@ public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0OrderScheduleCycleUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在订单排程周期标准");
 
         var now = DateTime.Now;
         var entity = new AdoS0OrderScheduleCycle
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -75,11 +79,12 @@ public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0OrderScheduleCycleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在其他记录");
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -99,7 +104,8 @@ public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0OrderScheduleCycleToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsEnabled;
         entity.UpdateTime = DateTime.Now;
@@ -110,7 +116,8 @@ public class AdoS0MfgOrderScheduleCyclesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 14 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgPersonSkillsController.cs

@@ -7,7 +7,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
 
 [ApiController]
 [Route("api/s0/manufacturing/person-skills")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0MfgPersonSkillsController : ControllerBase
 {
@@ -23,11 +22,13 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0MfgPersonSkillQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var page = q.EffectivePage;
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
@@ -42,7 +43,8 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
@@ -54,10 +56,12 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
         [FromQuery] bool? isEnabled,
         [FromQuery] int? limit)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var enabledFilter = isEnabled ?? true;
         var take = Math.Clamp(limit ?? 100, 1, 200);
 
         var list = await _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(companyRefId.HasValue, x => x.CompanyRefId == companyRefId!.Value)
             .WhereIF(factoryRefId.HasValue, x => x.FactoryRefId == factoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(keyword),
@@ -84,8 +88,10 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0MfgPersonSkillUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var entity = new AdoS0MfgPersonSkill
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             Code = dto.Code,
@@ -102,7 +108,8 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0MfgPersonSkillUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.CompanyRefId = dto.CompanyRefId;
         entity.FactoryRefId = dto.FactoryRefId;
@@ -119,7 +126,8 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleEnabledAsync(long id, [FromBody] AdoS0ToggleEnabledDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsEnabled = dto.IsEnabled;
         entity.UpdatedAt = DateTime.Now;
@@ -130,7 +138,8 @@ public class AdoS0MfgPersonSkillsController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
 
         var refInfo = await _refChecker.PersonSkillReferencesAsync(item.Id, item.Code);

+ 12 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Manufacturing/AdoS0MfgWorkOrderControlsController.cs

@@ -7,7 +7,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
 
 [ApiController]
 [Route("api/s0/manufacturing/work-order-controls")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0MfgWorkOrderControlsController : ControllerBase
 {
@@ -21,11 +20,13 @@ public class AdoS0MfgWorkOrderControlsController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0MfgWorkOrderControlQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var page = q.EffectivePage;
         var pageSize = q.PageSize;
         (page, pageSize) = PagingGuard.Normalize(page, pageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
@@ -40,15 +41,18 @@ public class AdoS0MfgWorkOrderControlsController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0MfgWorkOrderControlUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var entity = new AdoS0MfgWorkOrderControl
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             Code = dto.Code,
@@ -66,7 +70,8 @@ public class AdoS0MfgWorkOrderControlsController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0MfgWorkOrderControlUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.CompanyRefId = dto.CompanyRefId;
         entity.FactoryRefId = dto.FactoryRefId;
@@ -84,7 +89,8 @@ public class AdoS0MfgWorkOrderControlsController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleEnabledAsync(long id, [FromBody] AdoS0ToggleEnabledDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsEnabled = dto.IsEnabled;
         entity.UpdatedAt = DateTime.Now;
@@ -95,7 +101,8 @@ public class AdoS0MfgWorkOrderControlsController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 14 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCycleBreakdownsController.cs

@@ -10,7 +10,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 /// </summary>
 [ApiController]
 [Route("api/s0/sales/contract-review-cycle-breakdowns")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
 {
@@ -22,9 +21,11 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ContractReviewCycleBreakdownQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -39,14 +40,17 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0ContractReviewCycleBreakdownUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         if (await _rep.IsAnyAsync(x =>
+                x.TenantId == tenantId &&
                 x.FactoryRefId == dto.FactoryRefId &&
                 x.ParentStageCode == dto.ParentStageCode &&
                 x.GroupCode == dto.GroupCode))
@@ -54,6 +58,7 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
 
         var entity = new AdoS0ContractReviewCycleBreakdown
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -73,10 +78,12 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ContractReviewCycleBreakdownUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         if (await _rep.IsAnyAsync(x =>
+                x.TenantId == tenantId &&
                 x.Id != id &&
                 x.FactoryRefId == dto.FactoryRefId &&
                 x.ParentStageCode == dto.ParentStageCode &&
@@ -102,7 +109,8 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ContractReviewCycleBreakdownToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsActive;
         entity.UpdateTime = DateTime.Now;
@@ -113,7 +121,8 @@ public class AdoS0ContractReviewCycleBreakdownsController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 6 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCycleSyncFlagsController.cs

@@ -1,5 +1,6 @@
 using Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
 using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
+using Admin.NET.Plugin.AiDOP.Infrastructure;
 
 namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 
@@ -9,7 +10,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 /// </summary>
 [ApiController]
 [Route("api/s0/sales/contract-review-cycle-sync-flag")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0ContractReviewCycleSyncFlagsController : ControllerBase
 {
@@ -21,9 +21,11 @@ public class AdoS0ContractReviewCycleSyncFlagsController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetByFactoryAsync([FromQuery] long factoryRefId)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         if (factoryRefId <= 0) return BadRequest(new { message = "factoryRefId 不能为空" });
 
         var entity = await _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .Where(x => x.FactoryRefId == factoryRefId)
             .FirstAsync();
 
@@ -49,8 +51,10 @@ public class AdoS0ContractReviewCycleSyncFlagsController : ControllerBase
     [HttpPut]
     public async Task<IActionResult> UpsertAsync([FromBody] AdoS0ContractReviewCycleSyncFlagUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var now = DateTime.Now;
         var entity = await _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .Where(x => x.FactoryRefId == dto.FactoryRefId)
             .FirstAsync();
 
@@ -58,6 +62,7 @@ public class AdoS0ContractReviewCycleSyncFlagsController : ControllerBase
         {
             entity = new AdoS0ContractReviewCycleSyncFlag
             {
+                TenantId = tenantId,
                 CompanyRefId = dto.CompanyRefId,
                 FactoryRefId = dto.FactoryRefId,
                 DomainCode = dto.DomainCode,

+ 18 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ContractReviewCyclesController.cs

@@ -6,7 +6,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 
 [ApiController]
 [Route("api/s0/sales/contract-review-cycles")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0ContractReviewCyclesController : ControllerBase
 {
@@ -27,9 +26,11 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ContractReviewCycleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -43,18 +44,21 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0ContractReviewCycleUpsertDto dto)
     {
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.StageCode == dto.StageCode))
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.StageCode == dto.StageCode))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂下此阶段已存在");
 
         var entity = new AdoS0ContractReviewCycle
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -73,10 +77,11 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ContractReviewCycleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.StageCode == dto.StageCode))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.StageCode == dto.StageCode))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂下此阶段已存在");
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -97,7 +102,8 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ContractReviewCycleToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsActive;
         entity.UpdateTime = DateTime.Now;
@@ -108,7 +114,8 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });
@@ -121,9 +128,11 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
     [HttpGet("pi-config")]
     public async Task<IActionResult> GetPiConfigAsync([FromQuery] long factoryRefId)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         if (factoryRefId <= 0) return BadRequest(new { message = "factoryRefId 不能为空" });
 
         var syncFlag = await _syncRep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .Where(x => x.FactoryRefId == factoryRefId)
             .FirstAsync();
         var syncEnabled = syncFlag?.IsSyncEnabled ?? false;
@@ -137,6 +146,7 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
         if (!syncEnabled) return Ok(result);
 
         var stages = await _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .Where(x => x.FactoryRefId == factoryRefId && x.IsActive)
             .OrderBy(x => x.OrderNo)
             .ToListAsync();
@@ -149,6 +159,7 @@ public class AdoS0ContractReviewCyclesController : ControllerBase
         }).ToList();
 
         var breakdowns = await _breakdownRep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .Where(x => x.FactoryRefId == factoryRefId && x.ParentStageCode == "feedback" && x.IsActive)
             .OrderBy(x => x.OrderNo)
             .ToListAsync();

+ 12 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0OrderPriorityRulesController.cs

@@ -9,7 +9,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 /// </summary>
 [ApiController]
 [Route("api/s0/sales/order-priority-rules")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0OrderPriorityRulesController : ControllerBase
 {
@@ -23,9 +22,11 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0PriorityCodeQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.Descr), x => x.Descr.Contains(q.Descr!))
             .WhereIF(q.Priority.HasValue, x => x.Priority == q.Priority.Value)
@@ -56,7 +57,8 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpGet("{id}")]
     public async Task<IActionResult> GetAsync(string id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         item.OrderByText = AdoS0SalesRules.PriorityCodeOrderByText(item.OrderBy);
         return Ok(item);
@@ -65,9 +67,11 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0PriorityCodeUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var now = DateTime.Now;
         var entity = new AdoS0PriorityCode
         {
+            TenantId = tenantId,
             Id = Guid.NewGuid().ToString("D").ToUpperInvariant(),
             DomainCode = dto.DomainCode,
             Descr = dto.Descr ?? string.Empty,
@@ -97,7 +101,8 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpPut("{id}")]
     public async Task<IActionResult> UpdateAsync(string id, [FromBody] AdoS0PriorityCodeUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         entity.DomainCode = dto.DomainCode;
@@ -125,7 +130,8 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpPatch("{id}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(string id, [FromBody] AdoS0PriorityCodeToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         entity.IsActive = dto.IsActive;
@@ -138,7 +144,8 @@ public class AdoS0OrderPriorityRulesController : ControllerBase
     [HttpDelete("{id}")]
     public async Task<IActionResult> DeleteAsync(string id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
 
         await _rep.DeleteAsync(item);

+ 14 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0OrderReviewCyclesController.cs

@@ -6,7 +6,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 
 [ApiController]
 [Route("api/s0/sales/order-review-cycles")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0OrderReviewCyclesController : ControllerBase
 {
@@ -20,9 +19,11 @@ public class AdoS0OrderReviewCyclesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0OrderReviewCycleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -37,19 +38,22 @@ public class AdoS0OrderReviewCyclesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0OrderReviewCycleUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在订单评审周期配置,请使用编辑功能");
 
         var entity = new AdoS0OrderReviewCycle
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -67,11 +71,12 @@ public class AdoS0OrderReviewCyclesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0OrderReviewCycleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在其他记录");
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -91,7 +96,8 @@ public class AdoS0OrderReviewCyclesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0OrderReviewCycleToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsActive;
         entity.UpdateTime = DateTime.Now;
@@ -102,7 +108,8 @@ public class AdoS0OrderReviewCyclesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 17 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0PriorityRulesController.cs

@@ -10,7 +10,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 /// </summary>
 [ApiController]
 [Route("api/s0/sales/priority-rules")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0PriorityRulesController : ControllerBase
 {
@@ -24,9 +23,11 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0PriorityRuleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(q.IsEnabled.HasValue, x => x.IsEnabled == q.IsEnabled!.Value)
@@ -50,7 +51,8 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         FillRuleExprLabels(item);
         return Ok(item);
@@ -59,8 +61,9 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0PriorityRuleUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var now = DateTime.Now;
-        var entity = MapToEntity(dto);
+        var entity = MapToEntity(dto, tenantId);
         entity.CreatedAt = now;
 
         await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
@@ -71,7 +74,8 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0PriorityRuleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         ApplyUpsert(entity, dto);
@@ -85,7 +89,8 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleEnabledAsync(long id, [FromBody] AdoS0PriorityRuleToggleDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         entity.IsEnabled = dto.IsEnabled;
@@ -98,7 +103,8 @@ public class AdoS0PriorityRulesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
 
         await _rep.DeleteAsync(item);
@@ -107,9 +113,12 @@ public class AdoS0PriorityRulesController : ControllerBase
 
     // ── helpers ──────────────────────────────────────────────────────────────
 
-    private static AdoS0PriorityRule MapToEntity(AdoS0PriorityRuleUpsertDto dto)
+    private static AdoS0PriorityRule MapToEntity(AdoS0PriorityRuleUpsertDto dto, long tenantId)
     {
-        var entity = new AdoS0PriorityRule();
+        var entity = new AdoS0PriorityRule
+        {
+            TenantId = tenantId
+        };
         ApplyUpsert(entity, dto);
         return entity;
     }

+ 14 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Sales/AdoS0ProductDesignCyclesController.cs

@@ -6,7 +6,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
 
 [ApiController]
 [Route("api/s0/sales/product-design-cycles")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0ProductDesignCyclesController : ControllerBase
 {
@@ -24,9 +23,11 @@ public class AdoS0ProductDesignCyclesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ProductDesignCycleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -116,18 +117,21 @@ public class AdoS0ProductDesignCyclesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0ProductDesignCycleUpsertDto dto)
     {
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.ItemType == dto.ItemType && x.OwnerApplication == dto.OwnerApplication))
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.ItemType == dto.ItemType && x.OwnerApplication == dto.OwnerApplication))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂下此物料类型+属性组合已存在");
 
         var entity = new AdoS0ProductDesignCycle
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -145,10 +149,11 @@ public class AdoS0ProductDesignCyclesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ProductDesignCycleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.ItemType == dto.ItemType && x.OwnerApplication == dto.OwnerApplication))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.ItemType == dto.ItemType && x.OwnerApplication == dto.OwnerApplication))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂下此物料类型+属性组合已存在");
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -168,7 +173,8 @@ public class AdoS0ProductDesignCyclesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ProductDesignCycleToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsActive;
         entity.UpdateTime = DateTime.Now;
@@ -179,7 +185,8 @@ public class AdoS0ProductDesignCyclesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 17 - 9
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0CategoryLeadTimesController.cs

@@ -9,7 +9,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Supply;
 /// </summary>
 [ApiController]
 [Route("api/s0/supply/category-lead-times")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0CategoryLeadTimesController : ControllerBase
 {
@@ -23,9 +22,11 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0CategoryLeadTimeQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var baseQuery = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -50,7 +51,8 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         return Ok(item);
     }
@@ -58,12 +60,14 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0CategoryLeadTimeUpsertDto dto)
     {
-        var uniqueErr = await EnsureUniqueAsync(dto, null);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var uniqueErr = await EnsureUniqueAsync(dto, null, tenantId);
         if (uniqueErr != null) return uniqueErr;
 
         var now = DateTime.Now;
         var entity = new AdoS0CategoryLeadTime
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -86,10 +90,11 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0CategoryLeadTimeUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
-        var uniqueErr = await EnsureUniqueAsync(dto, id);
+        var uniqueErr = await EnsureUniqueAsync(dto, id, tenantId);
         if (uniqueErr != null) return uniqueErr;
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -111,7 +116,8 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0CategoryLeadTimeToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         entity.IsActive = dto.IsActive;
@@ -124,19 +130,21 @@ public class AdoS0CategoryLeadTimesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
 
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });
     }
 
-    private async Task<IActionResult?> EnsureUniqueAsync(AdoS0CategoryLeadTimeUpsertDto dto, long? id)
+    private async Task<IActionResult?> EnsureUniqueAsync(AdoS0CategoryLeadTimeUpsertDto dto, long? id, long tenantId)
     {
         var categoryCode = dto.CategoryCode.Trim();
         var ownerApplication = dto.OwnerApplication.Trim();
         if (await _rep.IsAnyAsync(x =>
-                x.Id != (id ?? 0)
+                x.TenantId == tenantId
+                && x.Id != (id ?? 0)
                 && x.FactoryRefId == dto.FactoryRefId
                 && x.CategoryCode == categoryCode
                 && x.OwnerApplication == ownerApplication))

+ 14 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0MaterialPlanCyclesController.cs

@@ -7,7 +7,6 @@ namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Supply;
 /// <summary>S0 物料计划周期标准(与订单评审/排程周期维度一致:工厂 + 订单类型 + std_hours)。</summary>
 [ApiController]
 [Route("api/s0/supply/material-plan-cycles")]
-[AllowAnonymous]
 [NonUnify]
 public class AdoS0MaterialPlanCyclesController : ControllerBase
 {
@@ -20,9 +19,11 @@ public class AdoS0MaterialPlanCyclesController : ControllerBase
     [HttpGet]
     public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0MaterialPlanCycleQueryDto q)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
 
         var query = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId)
             .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId!.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
@@ -41,20 +42,23 @@ public class AdoS0MaterialPlanCyclesController : ControllerBase
     [HttpGet("{id:long}")]
     public async Task<IActionResult> GetAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         return item == null ? NotFound() : Ok(item);
     }
 
     [HttpPost]
     public async Task<IActionResult> CreateAsync([FromBody] AdoS0MaterialPlanCycleUpsertDto dto)
     {
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在物料计划周期标准");
 
         var now = DateTime.Now;
         var entity = new AdoS0MaterialPlanCycle
         {
+            TenantId = tenantId,
             CompanyRefId = dto.CompanyRefId,
             FactoryRefId = dto.FactoryRefId,
             DomainCode = dto.DomainCode,
@@ -74,11 +78,12 @@ public class AdoS0MaterialPlanCyclesController : ControllerBase
     [HttpPut("{id:long}")]
     public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0MaterialPlanCycleUpsertDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
 
         var orderType = NormalizeOrderType(dto.OrderType);
-        if (await _rep.IsAnyAsync(x => x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
+        if (await _rep.IsAnyAsync(x => x.TenantId == tenantId && x.Id != id && x.FactoryRefId == dto.FactoryRefId && x.OrderType == orderType))
             return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DuplicateCode, "该工厂与订单类型组合已存在其他记录");
 
         entity.CompanyRefId = dto.CompanyRefId;
@@ -98,7 +103,8 @@ public class AdoS0MaterialPlanCyclesController : ControllerBase
     [HttpPatch("{id:long}/toggle-enabled")]
     public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0MaterialPlanCycleToggleActiveDto dto)
     {
-        var entity = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (entity == null) return NotFound();
         entity.IsActive = dto.IsActive;
         entity.UpdateTime = DateTime.Now;
@@ -109,7 +115,8 @@ public class AdoS0MaterialPlanCyclesController : ControllerBase
     [HttpDelete("{id:long}")]
     public async Task<IActionResult> DeleteAsync(long id)
     {
-        var item = await _rep.GetByIdAsync(id);
+        if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
+        var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
         if (item == null) return NotFound();
         await _rep.DeleteAsync(item);
         return Ok(new { message = "删除成功" });

+ 12 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0MfgMastersAndSop.cs

@@ -44,8 +44,9 @@ public class AdoS0MfgPersonSkillAssignment : ITenantIdFilter
 }
 
 [SugarTable("WorkOrderControl", "S0 工单控制参数")]
-[SugarIndex("uk_WorkOrderControl_code", nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(Code), OrderByType.Asc, IsUnique = true)]
-public class AdoS0MfgWorkOrderControl
+[SugarIndex("uk_WorkOrderControl_code",
+    nameof(TenantId), OrderByType.Asc, nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(Code), OrderByType.Asc, IsUnique = true)]
+public class AdoS0MfgWorkOrderControl : ITenantIdFilter
 {
     [SugarColumn(ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -79,11 +80,15 @@ public class AdoS0MfgWorkOrderControl
 
     [SugarColumn(ColumnDescription = "更新时间", IsNullable = true)]
     public DateTime? UpdatedAt { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }
 
 [SugarTable("PersonSkill", "S0 人员技能主数据")]
-[SugarIndex("uk_PersonSkill_code", nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(Code), OrderByType.Asc, IsUnique = true)]
-public class AdoS0MfgPersonSkill
+[SugarIndex("uk_PersonSkill_code",
+    nameof(TenantId), OrderByType.Asc, nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(Code), OrderByType.Asc, IsUnique = true)]
+public class AdoS0MfgPersonSkill : ITenantIdFilter
 {
     [SugarColumn(ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -114,6 +119,9 @@ public class AdoS0MfgPersonSkill
 
     [SugarColumn(ColumnDescription = "更新时间", IsNullable = true)]
     public DateTime? UpdatedAt { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }
 
 [SugarTable("LinePost", "S0 线体岗位")]

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0OrderScheduleCycle.cs

@@ -5,10 +5,11 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
 /// </summary>
 [SugarTable("S0OrderScheduleCycle", "订单排程周期标准")]
 [SugarIndex("uk_S0OrderScheduleCycle_factory_ordertype",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(OrderType), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0OrderScheduleCycle
+public class AdoS0OrderScheduleCycle : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -46,4 +47,7 @@ public class AdoS0OrderScheduleCycle
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycle.cs

@@ -2,10 +2,11 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 
 [SugarTable("S0ContractReviewCycle", "合同评审周期标准")]
 [SugarIndex("uk_S0ContractReviewCycle_factory_stage",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(StageCode), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0ContractReviewCycle
+public class AdoS0ContractReviewCycle : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -45,4 +46,7 @@ public class AdoS0ContractReviewCycle
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycleBreakdown.cs

@@ -6,6 +6,7 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 /// </summary>
 [SugarTable("S0ContractReviewCycleBreakdown", "合同评审周期下钻配置")]
 [SugarIndex("uk_S0CRCBreakdown_factory_parent_group",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(ParentStageCode), OrderByType.Asc,
     nameof(GroupCode), OrderByType.Asc,
@@ -14,7 +15,7 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(ParentStageCode), OrderByType.Asc,
     IsUnique = false)]
-public class AdoS0ContractReviewCycleBreakdown
+public class AdoS0ContractReviewCycleBreakdown : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -57,4 +58,7 @@ public class AdoS0ContractReviewCycleBreakdown
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ContractReviewCycleSyncFlag.cs

@@ -7,9 +7,10 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 /// </summary>
 [SugarTable("S0ContractReviewCycleSyncFlag", "合同评审周期同步开关")]
 [SugarIndex("uk_S0CRCSyncFlag_factory",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0ContractReviewCycleSyncFlag
+public class AdoS0ContractReviewCycleSyncFlag : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -37,4 +38,7 @@ public class AdoS0ContractReviewCycleSyncFlag
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0OrderReviewCycle.cs

@@ -2,10 +2,11 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 
 [SugarTable("S0OrderReviewCycle", "订单评审周期标准")]
 [SugarIndex("uk_S0OrderReviewCycle_factory_ordertype",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(OrderType), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0OrderReviewCycle
+public class AdoS0OrderReviewCycle : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -43,4 +44,7 @@ public class AdoS0OrderReviewCycle
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 4 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0PriorityCode.cs

@@ -4,7 +4,7 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 /// 订单优先级配置(复刻 PriorityCode)
 /// </summary>
 [SugarTable("PriorityCode", "订单优先级配置(复刻 PriorityCode)")]
-public class AdoS0PriorityCode
+public class AdoS0PriorityCode : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "RecID", ColumnDescription = "主键", IsPrimaryKey = true, Length = 36)]
     public string Id { get; set; } = string.Empty;
@@ -70,4 +70,7 @@ public class AdoS0PriorityCode
     [SugarColumn(IsIgnore = true)]
     public string? OrderByText { get; set; }
 
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 4 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0PriorityRule.cs

@@ -4,7 +4,7 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 /// S0 订单优先级业务规则(ado_s0_sales_order_priority_rule)
 /// </summary>
 [SugarTable("ado_s0_sales_order_priority_rule", "S0 订单优先级业务规则")]
-public class AdoS0PriorityRule
+public class AdoS0PriorityRule : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "Id", ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true)]
     public long Id { get; set; }
@@ -74,4 +74,7 @@ public class AdoS0PriorityRule
     /// <summary>到期状态显示标签(不落库,由业务层填充)</summary>
     [SugarColumn(IsIgnore = true)]
     public string? DueStatusLabel { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/AdoS0ProductDesignCycle.cs

@@ -2,11 +2,12 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 
 [SugarTable("S0ProductDesignCycle", "产品设计周期标准")]
 [SugarIndex("uk_S0ProductDesignCycle_factory_type_attr",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(ItemType), OrderByType.Asc,
     nameof(OwnerApplication), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0ProductDesignCycle
+public class AdoS0ProductDesignCycle : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -43,4 +44,7 @@ public class AdoS0ProductDesignCycle
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0CategoryLeadTime.cs

@@ -5,11 +5,12 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
 /// </summary>
 [SugarTable("S0CategoryLeadTime", "品类采购前置期主数据")]
 [SugarIndex("uk_S0CategoryLeadTime_factory_category_owner",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(CategoryCode), OrderByType.Asc,
     nameof(OwnerApplication), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0CategoryLeadTime
+public class AdoS0CategoryLeadTime : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -52,4 +53,7 @@ public class AdoS0CategoryLeadTime
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 5 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0MaterialPlanCycle.cs

@@ -5,10 +5,11 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
 /// </summary>
 [SugarTable("S0MaterialPlanCycle", "物料计划周期标准")]
 [SugarIndex("uk_S0MaterialPlanCycle_factory_ordertype",
+    nameof(TenantId), OrderByType.Asc,
     nameof(FactoryRefId), OrderByType.Asc,
     nameof(OrderType), OrderByType.Asc,
     IsUnique = true)]
-public class AdoS0MaterialPlanCycle
+public class AdoS0MaterialPlanCycle : ITenantIdFilter
 {
     [SugarColumn(ColumnName = "rec_id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
@@ -46,4 +47,7 @@ public class AdoS0MaterialPlanCycle
 
     [SugarColumn(ColumnName = "update_time", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+    public long? TenantId { get; set; }
 }

+ 1 - 52
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopS0ExcelColumnMigration.cs

@@ -1,7 +1,6 @@
 using System.Data;
 using System.Diagnostics;
 using System.Text;
-using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 using SqlSugar;
 
 namespace Admin.NET.Plugin.AiDOP.Infrastructure;
@@ -484,10 +483,8 @@ public static class AidopS0ExcelColumnMigration
 
             TryDropIndex(db, databaseName, "S0OrderReviewCycle", "uk_S0OrderReviewCycle_factory");
             EnsureUniqueIndex(db, databaseName, "S0OrderReviewCycle", "uk_S0OrderReviewCycle_factory_ordertype",
-                "`factory_ref_id`, `order_type`");
+                "`tenant_id`, `factory_ref_id`, `order_type`");
         }
-
-        EnsureContractReviewCycleDemoStages(db);
     }
 
     private static void EnsureColumn(ISqlSugarClient db, string databaseName, string tableName, string columnName, string columnDefinitionTail)
@@ -556,54 +553,6 @@ public static class AidopS0ExcelColumnMigration
         Trace.TraceInformation($"Ai-DOP S0 add unique index: {tableName}.{indexName}");
     }
 
-    private static void EnsureContractReviewCycleDemoStages(ISqlSugarClient db)
-    {
-        const long s0CompanyId = 1329900200001L;
-        const long s0Factory1Id = 1329900200002L;
-        const long s0Factory2Id = 1329900200003L;
-
-        var tables = db.DbMaintenance.GetTableInfoList(false);
-        if (!tables.Any(t => t.Name.Equals("S0ContractReviewCycle", StringComparison.OrdinalIgnoreCase)))
-            return;
-
-        var stages = new (string Code, string Name, int Order)[]
-        {
-            ("CR1", "意见评审", 1),
-            ("CR2", "意见反馈", 2),
-            ("CR3", "二次评审", 3),
-            ("CR4", "领导意见", 4),
-            ("CR5", "合同盖章", 5),
-        };
-
-        foreach (var factoryId in new[] { s0Factory1Id, s0Factory2Id })
-        {
-            var hasAny = db.Queryable<AdoS0ContractReviewCycle>()
-                .Where(x => x.FactoryRefId == factoryId)
-                .Any();
-            if (hasAny)
-                continue;
-
-            var now = DateTime.Now;
-            foreach (var s in stages)
-            {
-                db.Insertable(new AdoS0ContractReviewCycle
-                {
-                    CompanyRefId = s0CompanyId,
-                    FactoryRefId = factoryId,
-                    DomainCode = null,
-                    StageCode = s.Code,
-                    StageName = s.Name,
-                    StdHours = 0,
-                    OrderNo = s.Order,
-                    IsActive = true,
-                    CreateTime = now,
-                }).ExecuteCommand();
-            }
-
-            Trace.TraceInformation($"Ai-DOP S0 seeded contract review stages for factory {factoryId}");
-        }
-    }
-
     private static void RenameColumnMySql(ISqlSugarClient db, string databaseName, string tableName, string oldColumnName, string newColumnName)
     {
         var meta = GetMySqlColumnMeta(db, databaseName, tableName, oldColumnName);