using Admin.NET.Plugin.AiDOP.Supply; using Furion.Schedule; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Admin.NET.Plugin.AiDOP.Job; /// /// S8 Stage-3 采购完成态周期性全量对账。 /// /// 这是正确性主路径,不是优化项。 HotWatch 只覆盖活动中的采购单:实测 5 个 /// PUR_ORDER 关注中 3 个已提前终止(且无一是因采购完成而终止,全部走 IQC 路径终止), /// 终止后 Status=1 的关注永不再轮询;另有 88/98 条源行根本没有对应关注。 /// 因此仅靠增量必然漏判完成与回退,全量对账不可省。 /// /// 周期取 30 分钟:与 S5InventoryInboundJob 的既有半小时级节奏一致。 /// 源侧 Status 是收货过程产物,30 分钟的对账延迟对 Stage-3 口径可接受; /// 若日后接入 HotWatch 增量钩子,延迟会降到关注轮询级,但本 Job 仍必须保留。 /// [JobDetail("job_s3_po_completion_mdp_sync", Description = "S3 采购完成态(Source B)全量对账", GroupName = "default", Concurrent = false)] [Period(1800000, TriggerId = "trigger_s3_po_completion_mdp_sync", Description = "每30分钟执行")] public class S3PoCompletionMdpSyncJob : IJob { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; public S3PoCompletionMdpSyncJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory) { _scopeFactory = scopeFactory; _logger = loggerFactory.CreateLogger(nameof(S3PoCompletionMdpSyncJob)); } public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) { using var scope = _scopeFactory.CreateScope(); try { var svc = scope.ServiceProvider.GetRequiredService(); var r = await svc.RunFullAsync(0, "AUTO", stoppingToken); _logger.LogInformation( "S3PoCompletionMdpSyncJob 完成 batch={Batch} source={Src} rows={Rows} written={Written} " + "completed={Completed} notCompleted={NotCompleted} unknown={Unknown} skipped={Skipped} stale={Stale}", r.BatchId, r.SourceCode, r.SourceRows, r.WrittenRows, r.CompletedRows, r.NotCompletedRows, r.UnknownStatusRows, r.SkippedRows, r.StaleRemoved); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { _logger.LogInformation("S3PoCompletionMdpSyncJob 收到停止信号,结束本轮对账"); } catch (Exception ex) { // 源绑定不合法(未配置 / 指向 DEV 源 / 源被禁用)会走到这里: // 运行日志已在 Service 内标 FAILED,这里只保证调度线程不被打断、也不吞掉异常信息。 _logger.LogError(ex, "S3PoCompletionMdpSyncJob 执行失败"); } } }