using Admin.NET.Plugin.AiDOP.Dto.S8.OrderFlow; using Admin.NET.Plugin.AiDOP.Infrastructure; using Admin.NET.Plugin.AiDOP.Order; using SqlSugar; namespace Admin.NET.Plugin.AiDOP.Service.S8.OrderFlow; /// /// S8 Stage-1「订单评审」实际耗时只读服务。 /// /// 数据来源边界(Principle 2):本服务只读 dwd_order_review_kpi, /// 不触碰 aidop_action_run_log / crm_seorder / crm_seorderentry / /// ApprovalFlow*。配对与过滤已在 的中台链路内完成。 /// /// 租户边界(Principle 1):租户一律由 /// 从认证后的 JWT 解析,不接受前端传入的 tenantId,无默认回退。 /// public class S8OrderReviewKpiService : ITransient { private readonly ISqlSugarClient _db; private readonly UserManager _userManager; private readonly ActionEventMdpSyncService _syncService; public S8OrderReviewKpiService( ISqlSugarClient db, UserManager userManager, ActionEventMdpSyncService syncService) { _db = db; _userManager = userManager; _syncService = syncService; } /// 分页查询本租户的订单评审耗时。 public async Task GetListAsync(AdoS8OrderReviewKpiQueryDto query) { var tenantId = AidopTenantScope.ResolveOrThrow(_userManager); var page = query.Page <= 0 ? 1 : query.Page; var pageSize = query.PageSize is <= 0 or > 200 ? 20 : query.PageSize; var where = "`tenant_id` = @TenantId"; var ps = new List { new("@TenantId", tenantId) }; if (!string.IsNullOrWhiteSpace(query.OrderNo)) { where += " AND `sales_order_no` LIKE @OrderNo"; ps.Add(new SugarParameter("@OrderNo", $"%{query.OrderNo.Trim()}%")); } if (!string.IsNullOrWhiteSpace(query.CalculationStatus)) { where += " AND `calculation_status` = @CalcStatus"; ps.Add(new SugarParameter("@CalcStatus", query.CalculationStatus.Trim().ToUpperInvariant())); } var total = await _db.Ado.GetIntAsync( $"SELECT COUNT(*) FROM `dwd_order_review_kpi` WHERE {where}", ps); ps.Add(new SugarParameter("@Skip", (page - 1) * pageSize)); ps.Add(new SugarParameter("@Take", pageSize)); var rows = await _db.Ado.SqlQueryAsync($""" SELECT `sales_order_id` AS SalesOrderId, `sales_order_no` AS SalesOrderNo, `review_start_time` AS ReviewStartTime, `review_end_time` AS ReviewEndTime, `actual_hours` AS ActualHours, `calculation_status` AS CalculationStatus, `review_success_count` AS ReviewSuccessCount, `confirm_success_count` AS ConfirmSuccessCount, `review_failed_count` AS ReviewFailedCount, `confirm_failed_count` AS ConfirmFailedCount, `review_action_run_id` AS ReviewActionRunId, `confirm_action_run_id` AS ConfirmActionRunId, `calc_time` AS CalcTime FROM `dwd_order_review_kpi` WHERE {where} ORDER BY `review_start_time` DESC, `sales_order_id` DESC LIMIT @Skip, @Take """, ps); return new AdoS8OrderReviewKpiListDto { List = rows ?? [], Total = total, Page = page, PageSize = pageSize }; } /// /// 本租户 Stage-1 评审耗时汇总。 /// 均值只对 calculation_status='OK' 求 —— 未闭环订单不按 0 参与,避免把 KPI 拉低成假象。 /// public async Task GetSummaryAsync() { var tenantId = AidopTenantScope.ResolveOrThrow(_userManager); var list = await _db.Ado.SqlQueryAsync(""" SELECT COUNT(*) AS TotalOrders, SUM(`calculation_status` = 'OK') AS OkCount, SUM(`calculation_status` = 'IN_PROGRESS') AS InProgressCount, SUM(`calculation_status` = 'NO_START') AS NoStartCount, SUM(`calculation_status` = 'INVALID') AS InvalidCount, SUM(`calculation_status` = 'INVALID_TIME_SEQUENCE') AS InvalidTimeSequenceCount, ROUND(AVG(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END), 2) AS AvgActualHours, MIN(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END) AS MinActualHours, MAX(CASE WHEN `calculation_status` = 'OK' THEN `actual_hours` END) AS MaxActualHours, MAX(`calc_time`) AS LastCalcTime FROM `dwd_order_review_kpi` WHERE `tenant_id` = @TenantId """, new List { new("@TenantId", tenantId) }); return list?.FirstOrDefault() ?? new AdoS8OrderReviewKpiSummaryDto(); } /// /// 手动触发链路重算:源 → STG(增量 + RUNNING 回捞)→ STD → DWD KPI。 /// 只对当前登录租户生效;幂等,可重复调用。 /// public async Task RefreshAsync(CancellationToken cancellationToken = default) { var tenantId = AidopTenantScope.ResolveOrThrow(_userManager); var r = await _syncService.RunAsync(tenantId, cancellationToken); return new AdoS8OrderReviewKpiRefreshResultDto { BatchId = r.BatchId, StageRows = r.StageRows, StageRepulledRunning = r.StageRepulledRunning, StandardRows = r.StandardRows, KpiRows = r.KpiRows }; } }