using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Service.S8.Rules; namespace Admin.NET.Plugin.AiDOP.Service.S8; /// /// S8-STEP6E-CFG-WATCH-ORCHESTRATION-DEDUPE-UI-CERT-1:Watch 主链的**业务建单边界**。 /// /// 只抽象一件事——「就一次命中提交一次异常报告」。这是 /// S8WatchSchedulerService 调用 S8ManualReportService 的**唯一最窄位置** /// (抗抖门通过之后、计数回写/检测日志/通知派发之前)。 /// /// 为什么需要它:Watch 主链的编排(数据源加载 → evaluator → 检测状态 → 抗抖 → /// dedupe)必须能在认证中真实跑通,但真实建单会连带写 ado_s8_exception、 /// ado_s8_exception_timeline,并触发 ApprovalFlow 建实例/任务/日志——这些既不可回滚 /// (只有 exception + timeline 在同一事务内,其余全是自动提交),也依赖部门主数据。 /// 把边界抽出来后,认证 Host 可以只替换这一个接口,从而做到: /// 上游全真实、建单意图可核对、业务副作用为零。 /// /// ⚠️ 生产语义不得改变:正式实现只做一次纯委派,不改参数、不改事务、 /// 不改异常处理、不改 dedupe、不改通知、不改返回值语义。 /// public interface IS8ExceptionReportDispatcher { /// 把一次规则命中提交为标准异常单;参数与返回值同 Task CreateFromHitAsync(long tenantId, long factoryId, S8RuleHit hit); } /// /// 正式实现:**纯委派**,零业务逻辑。Furion 按 ITransient 自动注册到 /// ,无需改动 Startup。 /// public sealed class S8ExceptionReportDispatcher : IS8ExceptionReportDispatcher, ITransient { private readonly S8ManualReportService _manualReportService; public S8ExceptionReportDispatcher(S8ManualReportService manualReportService) => _manualReportService = manualReportService; public Task CreateFromHitAsync(long tenantId, long factoryId, S8RuleHit hit) => _manualReportService.CreateFromHitAsync(tenantId, factoryId, hit); }