S1KpiTargetSnapshotRefreshService.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. namespace Admin.NET.Plugin.AiDOP.SmartOps;
  2. public sealed class S1KpiDailySnapshotRow
  3. {
  4. public string MetricCode { get; set; } = string.Empty;
  5. public DateTime BizDate { get; set; }
  6. public decimal? MetricValue { get; set; }
  7. }
  8. public interface IS1KpiDailySnapshotStore
  9. {
  10. Task ExecuteInTransactionAsync(Func<Task> action, CancellationToken ct = default);
  11. Task<DateTime?> GetMaxBizDateAsync(int level, long tenantId, long factoryId, string moduleCode, CancellationToken ct = default);
  12. Task<List<S1KpiDailySnapshotRow>> ListLatestAsync(int level, long tenantId, long factoryId, DateTime bizDate, string moduleCode, CancellationToken ct = default);
  13. Task<int> UpdateTargetsAsync(
  14. int level,
  15. long tenantId,
  16. long factoryId,
  17. DateTime bizDate,
  18. string metricCode,
  19. KpiTargetSnapshot snap,
  20. string moduleCode,
  21. CancellationToken ct = default);
  22. }
  23. public sealed class S1KpiTargetSnapshotLevelResult
  24. {
  25. public int Level { get; set; }
  26. public string? BizDate { get; set; }
  27. public int UpdatedRows { get; set; }
  28. public int MissingRows { get; set; }
  29. }
  30. public sealed class S1KpiTargetSnapshotRefreshResult
  31. {
  32. public bool Ok { get; set; } = true;
  33. public string ModuleCode { get; set; } = "S1";
  34. public long FactoryId { get; set; }
  35. public int UpdatedRows { get; set; }
  36. public int MissingRows { get; set; }
  37. public List<S1KpiTargetSnapshotLevelResult> Levels { get; set; } = new();
  38. public int ElapsedMs { get; set; }
  39. public string Message { get; set; } = string.Empty;
  40. }
  41. public sealed class S1KpiTargetSnapshotRefreshService : ITransient
  42. {
  43. public const string ModuleCode = "S1";
  44. private readonly IS1KpiDailySnapshotStore _store;
  45. private readonly IKpiTargetResolver _resolver;
  46. public S1KpiTargetSnapshotRefreshService(IS1KpiDailySnapshotStore store, IKpiTargetResolver resolver)
  47. {
  48. _store = store;
  49. _resolver = resolver;
  50. }
  51. public async Task<S1KpiTargetSnapshotRefreshResult> RefreshAsync(long tenantId, long factoryId, CancellationToken ct = default) =>
  52. await RefreshAsync(ModuleCode, tenantId, factoryId, ct);
  53. public async Task<S1KpiTargetSnapshotRefreshResult> RefreshAsync(string moduleCode, long tenantId, long factoryId, CancellationToken ct = default)
  54. {
  55. var mc = string.IsNullOrWhiteSpace(moduleCode) ? ModuleCode : moduleCode.Trim().ToUpperInvariant();
  56. if (factoryId <= 0)
  57. throw new ArgumentOutOfRangeException(nameof(factoryId), "工厂必须大于 0");
  58. if (tenantId <= 0)
  59. throw new ArgumentOutOfRangeException(nameof(tenantId), "租户无效");
  60. var started = DateTime.Now;
  61. var result = new S1KpiTargetSnapshotRefreshResult { FactoryId = factoryId, ModuleCode = mc };
  62. await _store.ExecuteInTransactionAsync(async () =>
  63. {
  64. for (var level = 1; level <= 4; level++)
  65. {
  66. ct.ThrowIfCancellationRequested();
  67. var levelResult = await RefreshLevelAsync(mc, level, tenantId, factoryId, ct);
  68. result.Levels.Add(levelResult);
  69. result.UpdatedRows += levelResult.UpdatedRows;
  70. result.MissingRows += levelResult.MissingRows;
  71. }
  72. }, ct);
  73. result.ElapsedMs = (int)(DateTime.Now - started).TotalMilliseconds;
  74. result.Message = result.UpdatedRows == 0 && result.Levels.All(x => x.BizDate == null)
  75. ? $"{mc} 暂无可刷新的日值快照"
  76. : $"{mc} 看板目标快照已刷新";
  77. return result;
  78. }
  79. private async Task<S1KpiTargetSnapshotLevelResult> RefreshLevelAsync(string moduleCode, int level, long tenantId, long factoryId, CancellationToken ct)
  80. {
  81. var maxDate = await _store.GetMaxBizDateAsync(level, tenantId, factoryId, moduleCode, ct);
  82. var levelResult = new S1KpiTargetSnapshotLevelResult { Level = level };
  83. if (maxDate == null)
  84. return levelResult;
  85. var bizDate = maxDate.Value.Date;
  86. levelResult.BizDate = bizDate.ToString("yyyy-MM-dd");
  87. var rows = await _store.ListLatestAsync(level, tenantId, factoryId, bizDate, moduleCode, ct);
  88. foreach (var group in rows.GroupBy(x => x.MetricCode, StringComparer.OrdinalIgnoreCase))
  89. {
  90. ct.ThrowIfCancellationRequested();
  91. var snap = await _resolver.ResolveAsync(tenantId, factoryId, group.Key, moduleCode, bizDate, ct);
  92. if (snap.TargetSource == KpiTargetSnapshotSource.Missing || snap.TargetValue is null or <= 0)
  93. {
  94. levelResult.MissingRows += group.Count();
  95. continue;
  96. }
  97. var updated = await _store.UpdateTargetsAsync(level, tenantId, factoryId, bizDate, group.Key, snap, moduleCode, ct);
  98. levelResult.UpdatedRows += updated;
  99. }
  100. return levelResult;
  101. }
  102. }