S8OrderReviewKpiService.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using Admin.NET.Plugin.AiDOP.Dto.S8.OrderFlow;
  2. using Admin.NET.Plugin.AiDOP.Infrastructure;
  3. using Admin.NET.Plugin.AiDOP.Order;
  4. using SqlSugar;
  5. namespace Admin.NET.Plugin.AiDOP.Service.S8.OrderFlow;
  6. /// <summary>
  7. /// S8 Stage-1「订单评审」实际耗时只读服务。
  8. ///
  9. /// <para><b>数据来源边界(Principle 2)</b>:本服务<b>只读 <c>dwd_order_review_kpi</c></b>,
  10. /// 不触碰 <c>aidop_action_run_log</c> / <c>crm_seorder</c> / <c>crm_seorderentry</c> /
  11. /// <c>ApprovalFlow*</c>。配对与过滤已在 <see cref="ActionEventMdpSyncService"/> 的中台链路内完成。</para>
  12. ///
  13. /// <para><b>租户边界(Principle 1)</b>:租户一律由 <see cref="AidopTenantScope.ResolveOrThrow"/>
  14. /// 从认证后的 JWT 解析,<b>不接受前端传入的 tenantId</b>,无默认回退。</para>
  15. /// </summary>
  16. public class S8OrderReviewKpiService : ITransient
  17. {
  18. private readonly ISqlSugarClient _db;
  19. private readonly UserManager _userManager;
  20. private readonly ActionEventMdpSyncService _syncService;
  21. public S8OrderReviewKpiService(
  22. ISqlSugarClient db,
  23. UserManager userManager,
  24. ActionEventMdpSyncService syncService)
  25. {
  26. _db = db;
  27. _userManager = userManager;
  28. _syncService = syncService;
  29. }
  30. /// <summary>分页查询本租户的订单评审耗时。</summary>
  31. public async Task<AdoS8OrderReviewKpiListDto> GetListAsync(AdoS8OrderReviewKpiQueryDto query)
  32. {
  33. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  34. var page = query.Page <= 0 ? 1 : query.Page;
  35. var pageSize = query.PageSize is <= 0 or > 200 ? 20 : query.PageSize;
  36. var where = "`tenant_id` = @TenantId";
  37. var ps = new List<SugarParameter> { new("@TenantId", tenantId) };
  38. if (!string.IsNullOrWhiteSpace(query.OrderNo))
  39. {
  40. where += " AND `sales_order_no` LIKE @OrderNo";
  41. ps.Add(new SugarParameter("@OrderNo", $"%{query.OrderNo.Trim()}%"));
  42. }
  43. if (!string.IsNullOrWhiteSpace(query.CalculationStatus))
  44. {
  45. where += " AND `calculation_status` = @CalcStatus";
  46. ps.Add(new SugarParameter("@CalcStatus", query.CalculationStatus.Trim().ToUpperInvariant()));
  47. }
  48. var total = await _db.Ado.GetIntAsync(
  49. $"SELECT COUNT(*) FROM `dwd_order_review_kpi` WHERE {where}", ps);
  50. ps.Add(new SugarParameter("@Skip", (page - 1) * pageSize));
  51. ps.Add(new SugarParameter("@Take", pageSize));
  52. var rows = await _db.Ado.SqlQueryAsync<AdoS8OrderReviewKpiRowDto>($"""
  53. SELECT `sales_order_id` AS SalesOrderId,
  54. `sales_order_no` AS SalesOrderNo,
  55. `review_start_time` AS ReviewStartTime,
  56. `review_end_time` AS ReviewEndTime,
  57. `actual_hours` AS ActualHours,
  58. `calculation_status` AS CalculationStatus,
  59. `review_success_count` AS ReviewSuccessCount,
  60. `confirm_success_count` AS ConfirmSuccessCount,
  61. `review_failed_count` AS ReviewFailedCount,
  62. `confirm_failed_count` AS ConfirmFailedCount,
  63. `review_action_run_id` AS ReviewActionRunId,
  64. `confirm_action_run_id` AS ConfirmActionRunId,
  65. `calc_time` AS CalcTime
  66. FROM `dwd_order_review_kpi`
  67. WHERE {where}
  68. ORDER BY `review_start_time` DESC, `sales_order_id` DESC
  69. LIMIT @Skip, @Take
  70. """, ps);
  71. return new AdoS8OrderReviewKpiListDto
  72. {
  73. List = rows ?? [],
  74. Total = total,
  75. Page = page,
  76. PageSize = pageSize
  77. };
  78. }
  79. /// <summary>
  80. /// 本租户 Stage-1 评审耗时汇总。
  81. /// <para>均值只对 <c>calculation_status='OK'</c> 求 —— 未闭环订单不按 0 参与,避免把 KPI 拉低成假象。</para>
  82. /// </summary>
  83. public async Task<AdoS8OrderReviewKpiSummaryDto> GetSummaryAsync()
  84. {
  85. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  86. var list = await _db.Ado.SqlQueryAsync<AdoS8OrderReviewKpiSummaryDto>("""
  87. SELECT COUNT(*) AS TotalOrders,
  88. SUM(`calculation_status` = 'OK') AS OkCount,
  89. SUM(`calculation_status` = 'IN_PROGRESS') AS InProgressCount,
  90. SUM(`calculation_status` = 'NO_START') AS NoStartCount,
  91. SUM(`calculation_status` = 'INVALID') AS InvalidCount,
  92. SUM(`calculation_status` = 'INVALID_TIME_SEQUENCE') AS InvalidTimeSequenceCount,
  93. ROUND(AVG(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END), 2) AS AvgActualHours,
  94. MIN(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END) AS MinActualHours,
  95. MAX(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END) AS MaxActualHours,
  96. MAX(`calc_time`) AS LastCalcTime
  97. FROM `dwd_order_review_kpi`
  98. WHERE `tenant_id` = @TenantId
  99. """, new List<SugarParameter> { new("@TenantId", tenantId) });
  100. return list?.FirstOrDefault() ?? new AdoS8OrderReviewKpiSummaryDto();
  101. }
  102. /// <summary>
  103. /// 手动触发链路重算:源 → STG(增量 + RUNNING 回捞)→ STD → DWD KPI。
  104. /// 只对当前登录租户生效;幂等,可重复调用。
  105. /// </summary>
  106. public async Task<AdoS8OrderReviewKpiRefreshResultDto> RefreshAsync(CancellationToken cancellationToken = default)
  107. {
  108. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  109. var r = await _syncService.RunAsync(tenantId, cancellationToken);
  110. return new AdoS8OrderReviewKpiRefreshResultDto
  111. {
  112. BatchId = r.BatchId,
  113. StageRows = r.StageRows,
  114. StageRepulledRunning = r.StageRepulledRunning,
  115. StandardRows = r.StandardRows,
  116. KpiRows = r.KpiRows
  117. };
  118. }
  119. }