瀏覽代碼

fix(s8): 通知设置页不再露出接口路径,并对齐三处前端守卫

冒烟时发现「通知设置」的「发生时机」列直接显示 POST {id}/claim 这类接口路径 ——
业务用户不需要理解接口。TriggerHint 现在是给业务用户看的「什么时候会发生」,
三个事件改成业务语言,字段语义在注释里写清楚,避免以后又被当成派发点标注来填。

同时对齐三处仍锁定旧实现的前端守卫(保护意图不变,只换判据):
- Tab 4 收件人文案不再由前端维护 RECIPIENT_LABEL 映射,改断言由后端下发;
  顺带把「页面不得出现收件人技术枚举」也纳入该守卫。
- 检验人候选来源从「全租户 ∩ 审核权限」换成规则级复核池端点,
  「前端能选的 == 后端会放行的」这条要求没变,而且现在更强 ——
  候选与提交校验共用同一份 IS8VerifierEligibility。
- 四页签守卫改用新名字(责任分配 / 通知设置),仍然断言恰好四个页签、
  且弹窗不嵌全局「异常操作权限」。

这三处是前一提交就该带上的:前端重写后我没有重跑 C# 测试直接提交了。
YY968XX 3 天之前
父節點
當前提交
8d25d52b78

+ 13 - 5
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S8/S8ApprovalFlowRepairTests.cs

@@ -253,14 +253,22 @@ public class S8ApprovalFlowRepairTests
         var page = File.ReadAllText(Path.GetFullPath(Path.Combine(
         var page = File.ReadAllText(Path.GetFullPath(Path.Combine(
             PluginRoot, "../../../Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue")));
             PluginRoot, "../../../Web/src/views/aidop/s8/exceptions/S8TaskDetailPage.vue")));
 
 
-        Assert.Contains("s8ExceptionApi.users('EXCEPTION_VERIFY')", page);
+        // S8-RESPONSIBILITY-POOL-1:候选来源从「全租户 ∩ 审核权限」收窄为
+        // 「该规则的复核池 ∩ 审核权限 ∩ 可用账号」,由后端一个端点给出。
+        // 本守卫保护的一直是「前端能选的 == 后端会放行的」,来源换了但要求没变 ——
+        // 而且现在更强:候选与提交校验共用同一份 IS8VerifierEligibility 实现。
+        Assert.Contains("s8ExceptionApi.verifierCandidates(", page);
+        Assert.DoesNotContain("s8ExceptionApi.users('EXCEPTION_VERIFY')", page);
         Assert.Contains("verifierUsers", page);
         Assert.Contains("verifierUsers", page);
-        // 没有可审核账号时必须显式提示,而不是给一个空下拉
+
+        // 没有可选复核人时必须显式说明原因,而不是给一个空下拉
+        Assert.Contains("该规则没有可用的复核人员", page);
         Assert.Contains("本租户没有任何账号具备「审核」权限", page);
         Assert.Contains("本租户没有任何账号具备「审核」权限", page);
 
 
-        var adapter = CodeOnly("Service/S8/S8MasterDataAdapter.cs");
-        Assert.Contains("requiredAction", adapter);
-        Assert.Contains("_actionAuthorizer.AuthorizeAsync(scope.TenantId, u.UserId, requiredAction!)", adapter);
+        var eligibility = CodeOnly("Infrastructure/S8/S8VerifierEligibility.cs");
+        Assert.Contains("ListCandidatesAsync", eligibility);
+        Assert.Contains("CheckAsync", eligibility);
+        Assert.Contains("S8ExceptionActionCode.Verify", eligibility);
     }
     }
 
 
     /// <summary>T12 / T13:修复链路上没有 Employee、没有 Factory。</summary>
     /// <summary>T12 / T13:修复链路上没有 Employee、没有 Factory。</summary>

+ 10 - 1
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S8/S8NotificationRecipientTests.cs

@@ -336,6 +336,15 @@ public class S8NotificationRecipientTests
 
 
         foreach (var phantom in new[] { "L1_OPERATOR", "L2_MANAGER", "L3_DIRECTOR" })
         foreach (var phantom in new[] { "L1_OPERATOR", "L2_MANAGER", "L3_DIRECTOR" })
             Assert.DoesNotContain(phantom, noComments);
             Assert.DoesNotContain(phantom, noComments);
-        Assert.Contains("RECIPIENT_LABEL", page);
+
+        // S8-RESPONSIBILITY-POOL-1:收件人文案不再由前端维护一份 RECIPIENT_LABEL 映射,
+        // 改为后端按责任关系推导后下发(recipientLabel)。前端各存一份映射,
+        // 迟早与后端的推导规则不一致 —— 而不一致的那一刻页面显示的是错的对象。
+        Assert.DoesNotContain("RECIPIENT_LABEL", page);
+        Assert.Contains("recipientLabel", page);
+
+        // 页面也不得再让业务用户挑收件人技术类型
+        foreach (var techEnum in new[] { "HANDLER_POOL", "SPECIFIC_USER", "recipientTypes" })
+            Assert.DoesNotContain(techEnum, noComments);
     }
     }
 }
 }

+ 4 - 2
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/S8/S8ReviewSourceTests.cs

@@ -194,8 +194,10 @@ public class S8ReviewSourceTests
         Assert.DoesNotContain("actionPermissions", page);
         Assert.DoesNotContain("actionPermissions", page);
         Assert.DoesNotContain("action-permissions", page);
         Assert.DoesNotContain("action-permissions", page);
 
 
-        // 四个页签齐全
-        foreach (var tab in new[] { "规则信息", "运行设置", "处理与审核", "通知与升级" })
+        // 四个页签齐全。S8-RESPONSIBILITY-POOL-1 把后两个改名为业务语言:
+        // 「处理与审核」→「责任分配」(谁处理/谁复核/超时找谁),
+        // 「通知与升级」→「通知设置」(升级责任已在责任分配里配,此处只管提醒开关)。
+        foreach (var tab in new[] { "规则信息", "运行设置", "责任分配", "通知设置" })
             Assert.Contains($"label=\"{tab}\"", page);
             Assert.Contains($"label=\"{tab}\"", page);
         Assert.Equal(4, System.Text.RegularExpressions.Regex.Matches(page, "<el-tab-pane ").Count);
         Assert.Equal(4, System.Text.RegularExpressions.Regex.Matches(page, "<el-tab-pane ").Count);
     }
     }

+ 10 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Const/S8/S8NotificationCatalog.cs

@@ -64,7 +64,13 @@ public static class S8RecipientType
     public const string SpecificUser = "SPECIFIC_USER";
     public const string SpecificUser = "SPECIFIC_USER";
 }
 }
 
 
-/// <summary>事件元信息。</summary>
+/// <summary>
+/// 事件元信息。
+/// <para><b><paramref name="TriggerHint"/> 是给业务用户看的「什么时候会发生」</b>,
+/// 不是给开发看的派发点标注 —— 通知设置页会直接展示它。
+/// 原先几个事件写的是 <c>POST {id}/claim</c> 这样的接口路径,
+/// 与「业务用户不需要理解技术实现」相悖。</para>
+/// </summary>
 public sealed record S8NotifyEventDefinition(string Code, string DisplayName, string TriggerHint, int OrderNo);
 public sealed record S8NotifyEventDefinition(string Code, string DisplayName, string TriggerHint, int OrderNo);
 
 
 /// <summary>
 /// <summary>
@@ -76,9 +82,9 @@ public static class S8NotificationCatalog
     public static readonly IReadOnlyList<S8NotifyEventDefinition> Events = new List<S8NotifyEventDefinition>
     public static readonly IReadOnlyList<S8NotifyEventDefinition> Events = new List<S8NotifyEventDefinition>
     {
     {
         new(S8NotifyEventCode.ExceptionCreated, "异常产生", "规则命中建单 / 人工提报", 10),
         new(S8NotifyEventCode.ExceptionCreated, "异常产生", "规则命中建单 / 人工提报", 10),
-        new(S8NotifyEventCode.ExceptionClaimed, "认领成功", "POST {id}/claim", 20),
-        new(S8NotifyEventCode.ExceptionTransferred, "转派", "POST {id}/transfer", 30),
-        new(S8NotifyEventCode.VerificationSubmitted, "提交复核", "POST {id}/submit-verification", 40),
+        new(S8NotifyEventCode.ExceptionClaimed, "认领成功", "处理人认领异常时", 20),
+        new(S8NotifyEventCode.ExceptionTransferred, "转派", "异常被转派给其他处理人时", 30),
+        new(S8NotifyEventCode.VerificationSubmitted, "提交复核", "处理人提交复核时", 40),
         new(S8NotifyEventCode.EscalationTriggered, "超时升级", "超时自动升级作业", 50),
         new(S8NotifyEventCode.EscalationTriggered, "超时升级", "超时自动升级作业", 50),
         new(S8NotifyEventCode.ExceptionRecovered, "异常恢复", "调度器检测到恢复", 60),
         new(S8NotifyEventCode.ExceptionRecovered, "异常恢复", "调度器检测到恢复", 60),
         new(S8NotifyEventCode.ExceptionOverdueClosed, "超时关闭", "检验通过后闭环及时性回顾", 70),
         new(S8NotifyEventCode.ExceptionOverdueClosed, "超时关闭", "检验通过后闭环及时性回顾", 70),