Explorar o código

feat(s8): support period filter for monitoring dashboards

YY968XX hai 3 meses
pai
achega
39190ebba1

+ 4 - 3
Web/src/views/aidop/s8/api/s8DashboardApi.ts

@@ -1,4 +1,5 @@
 import service from '/@/utils/request';
+import type { S8Period } from './s8MonitoringApi';
 
 function unwrap<T>(res: { data: T }): T {
 	return res.data;
@@ -91,19 +92,19 @@ export interface S8CellDataQuery {
 }
 
 export const s8DashboardApi = {
-	overview: (params?: { tenantId?: number; factoryId?: number; beginTime?: string; endTime?: string }) =>
+	overview: (params?: { tenantId?: number; factoryId?: number; beginTime?: string; endTime?: string; period?: S8Period }) =>
 		service.get<S8OverviewData>('/api/aidop/s8/dashboard/overview', { params }).then(unwrap),
 
 	trends: (params?: { tenantId?: number; factoryId?: number; days?: number }) =>
 		service.get<{ date: string; count: number }[]>('/api/aidop/s8/dashboard/trends', { params }).then(unwrap),
 
-	distributions: (params?: { tenantId?: number; factoryId?: number }) =>
+	distributions: (params?: { tenantId?: number; factoryId?: number; period?: S8Period }) =>
 		service.get<S8Distributions>('/api/aidop/s8/dashboard/distributions', { params }).then(unwrap),
 
 	quickExceptions: (params?: { tenantId?: number; factoryId?: number; mode?: string }) =>
 		service.get<S8QuickException[]>('/api/aidop/s8/dashboard/quick-exceptions', { params }).then(unwrap),
 
-	deptBacklog: (params?: { tenantId?: number; factoryId?: number }) =>
+	deptBacklog: (params?: { tenantId?: number; factoryId?: number; period?: S8Period }) =>
 		service.get<S8DeptBacklogItem[]>('/api/aidop/s8/dashboard/dept-backlog', { params }).then(unwrap),
 
 	dimTrends: (params?: { tenantId?: number; factoryId?: number; dim?: string; days?: number }) =>

+ 6 - 2
Web/src/views/aidop/s8/api/s8MonitoringApi.ts

@@ -25,6 +25,8 @@ export interface S8MonitoringSummary {
 	byModule: S8ModuleSummaryItem[];
 }
 
+export type S8Period = 'today' | 'this_week' | 'this_month';
+
 export interface S8MonitoringSummaryQuery {
 	tenantId?: number;
 	factoryId?: number;
@@ -32,6 +34,8 @@ export interface S8MonitoringSummaryQuery {
 	moduleCode?: string;
 	bizDateFrom?: string;
 	bizDateTo?: string;
+	/** 统一时间筛选;bizDateFrom/bizDateTo 已传时后端优先使用显式范围。TASK-008 顶部 UI 通过此字段驱动。 */
+	period?: S8Period;
 }
 
 export interface S8ModuleOrderSummary {
@@ -149,9 +153,9 @@ export const s8MonitoringApi = {
 			.get<S8MonitoringSummary>('/api/aidop/s8/monitoring/summary', { params })
 			.then(unwrap),
 
-	orderGrid: () =>
+	orderGrid: (params?: { period?: S8Period }) =>
 		service
-			.get<S8OrderGridData>('/api/aidop/s8/monitoring/order-grid')
+			.get<S8OrderGridData>('/api/aidop/s8/monitoring/order-grid', { params })
 			.then(unwrap),
 
 	deliveryTrend: (params: { tenantId?: number; factoryId?: number; days?: number } = {}) =>

+ 7 - 6
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8DashboardController.cs

@@ -28,8 +28,9 @@ public class AdoS8DashboardController : ControllerBase
         [FromQuery] long factoryId = 1,
         [FromQuery] DateTime? beginTime = null,
         [FromQuery] DateTime? endTime = null,
-        [FromQuery] string? severity = null) =>
-        Ok(await _svc.GetOverviewAsync(tenantId, factoryId, beginTime, endTime, severity));
+        [FromQuery] string? severity = null,
+        [FromQuery] string? period = null) =>
+        Ok(await _svc.GetOverviewAsync(tenantId, factoryId, beginTime, endTime, severity, period));
 
     [HttpGet("trends")]
     public async Task<IActionResult> TrendsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
@@ -37,8 +38,8 @@ public class AdoS8DashboardController : ControllerBase
         Ok(await _svc.GetTrendsAsync(tenantId, factoryId, days));
 
     [HttpGet("distributions")]
-    public async Task<IActionResult> DistributionsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) =>
-        Ok(await _svc.GetDistributionsAsync(tenantId, factoryId));
+    public async Task<IActionResult> DistributionsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromQuery] string? period = null) =>
+        Ok(await _svc.GetDistributionsAsync(tenantId, factoryId, period));
 
     [HttpGet("quick-exceptions")]
     public async Task<IActionResult> QuickExceptionsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
@@ -46,8 +47,8 @@ public class AdoS8DashboardController : ControllerBase
         Ok(await _svc.GetQuickExceptionsAsync(tenantId, factoryId, mode));
 
     [HttpGet("dept-backlog")]
-    public async Task<IActionResult> DeptBacklogAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) =>
-        Ok(await _svc.GetDeptBacklogAsync(tenantId, factoryId));
+    public async Task<IActionResult> DeptBacklogAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromQuery] string? period = null) =>
+        Ok(await _svc.GetDeptBacklogAsync(tenantId, factoryId, period));
 
     [HttpGet("dim-trends")]
     public async Task<IActionResult> DimTrendsAsync(

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8MonitoringController.cs

@@ -27,8 +27,8 @@ public class AdoS8MonitoringController : ControllerBase
     /// 9宫格数据:S1-S7 订单健康分布 + S8业务类别汇总 + S9部门汇总。
     /// </summary>
     [HttpGet("order-grid")]
-    public async Task<IActionResult> GetOrderGridAsync()
-        => Ok(await _svc.GetOrderGridAsync());
+    public async Task<IActionResult> GetOrderGridAsync([FromQuery] string? period = null)
+        => Ok(await _svc.GetOrderGridAsync(period: period));
 
     /// <summary>
     /// S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:Delivery 页近 N 日交付异常趋势。

+ 2 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S8/AdoS8Dtos.cs

@@ -178,6 +178,8 @@ public class AdoS8MonitoringSummaryQueryDto
     public string? ModuleCode { get; set; }
     public DateTime? BizDateFrom { get; set; }
     public DateTime? BizDateTo { get; set; }
+    /// <summary>统一时间筛选:today / this_week / this_month。BizDateFrom/BizDateTo 已传时优先于 Period。</summary>
+    public string? Period { get; set; }
 }
 
 /// <summary>单个模块的异常汇总行</summary>

+ 34 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/S8/S8PeriodHelper.cs

@@ -0,0 +1,34 @@
+namespace Admin.NET.Plugin.AiDOP.Infrastructure.S8;
+
+/// <summary>
+/// 首版按服务器自然周期对齐大屏统计口径;显式日期范围优先于 period。
+/// </summary>
+public static class S8PeriodHelper
+{
+    /// <summary>
+    /// 将 period 字符串解析为半开区间 [from, to)。
+    /// 支持 today / this_week / this_month(大小写容错)。
+    /// 非法或空值返回 (null, null),调用方保持全量行为。
+    /// </summary>
+    public static (DateTime? from, DateTime? to) Resolve(string? period)
+    {
+        if (string.IsNullOrWhiteSpace(period)) return (null, null);
+
+        var today = DateTime.Today;
+        return period.Trim().ToLowerInvariant() switch
+        {
+            "today" => (today, today.AddDays(1)),
+            "this_week" => ResolveThisWeek(today),
+            "this_month" => (new DateTime(today.Year, today.Month, 1), new DateTime(today.Year, today.Month, 1).AddMonths(1)),
+            _ => (null, null),
+        };
+    }
+
+    private static (DateTime? from, DateTime? to) ResolveThisWeek(DateTime today)
+    {
+        // 周一作为周起始(DayOfWeek.Monday = 1)
+        var offset = ((int)today.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7;
+        var monday = today.AddDays(-offset);
+        return (monday, monday.AddDays(7));
+    }
+}

+ 19 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DashboardService.cs

@@ -30,17 +30,24 @@ public class S8DashboardService : ITransient
         _processNodeRep = processNodeRep;
     }
 
-    public async Task<object> GetOverviewAsync(long tenantId, long factoryId, DateTime? beginTime = null, DateTime? endTime = null, string? severity = null)
+    public async Task<object> GetOverviewAsync(long tenantId, long factoryId, DateTime? beginTime = null, DateTime? endTime = null, string? severity = null, string? period = null)
     {
         // 看板顶部的"开始/结束日期"通过 beginTime/endTime 透传,与列表的 beginTime/endTime 同语义。
         // BUG-12:补 severity 过滤,与异常列表 API 的 severity 口径一致(精确匹配 LOW/MEDIUM/HIGH/CRITICAL)。
         // BUG-22:REJECTED 状态归入 pending 桶,避免从总数视角"消失";与列表 statusBucket="pending" 同步对齐。
         // S8-DASHBOARD-DATA-ALIGN-S1S7-1:统一只统计 module_code IN S1-S7;NULL module 的 legacy 行不参与看板 KPI。
+        // 显式日期范围优先;未传时从 period 解析。
+        var (periodFrom, periodTo) = (!beginTime.HasValue && !endTime.HasValue)
+            ? S8PeriodHelper.Resolve(period)
+            : ((DateTime?)null, (DateTime?)null);
+        var effectiveFrom = beginTime ?? periodFrom;
+        var effectiveTo   = endTime   ?? periodTo;
+
         var q = _rep.AsQueryable()
             .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
             .Where(x => S8ModuleCode.All.Contains(x.ModuleCode))
-            .WhereIF(beginTime.HasValue, x => x.CreatedAt >= beginTime!.Value)
-            .WhereIF(endTime.HasValue, x => x.CreatedAt <= endTime!.Value)
+            .WhereIF(effectiveFrom.HasValue, x => x.CreatedAt >= effectiveFrom!.Value)
+            .WhereIF(effectiveTo.HasValue,   x => x.CreatedAt < effectiveTo!.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(severity), x => x.Severity == severity);
         var total      = await q.CountAsync();
         var pending    = await q.CountAsync(x => x.Status == "NEW" || x.Status == "ASSIGNED" || x.Status == "IN_PROGRESS" || x.Status == "PENDING_VERIFICATION" || x.Status == "REJECTED");
@@ -85,11 +92,15 @@ public class S8DashboardService : ITransient
             .ToList();
     }
 
-    public async Task<object> GetDistributionsAsync(long tenantId, long factoryId)
+    public async Task<object> GetDistributionsAsync(long tenantId, long factoryId, string? period = null)
     {
+        var (periodFrom, periodTo) = S8PeriodHelper.Resolve(period);
+
         var list = await _rep.AsQueryable()
             .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
             .Where(x => S8ModuleCode.All.Contains(x.ModuleCode))
+            .WhereIF(periodFrom.HasValue, x => x.CreatedAt >= periodFrom!.Value)
+            .WhereIF(periodTo.HasValue,   x => x.CreatedAt < periodTo!.Value)
             .Select(x => new
             {
                 x.Status,
@@ -176,14 +187,17 @@ public class S8DashboardService : ITransient
         };
     }
 
-    public async Task<object> GetDeptBacklogAsync(long tenantId, long factoryId)
+    public async Task<object> GetDeptBacklogAsync(long tenantId, long factoryId, string? period = null)
     {
         var pendingStatuses = new[] { "NEW", "ASSIGNED", "IN_PROGRESS" };
+        var (periodFrom, periodTo) = S8PeriodHelper.Resolve(period);
 
         // S8-SLA-TIMEOUT-RUNTIME-1(P3):投影 SlaDeadline 替代 TimeoutFlag;timeout 由内存计算(与 GetSummaryAsync 一致 now)。
         var list = await _rep.AsQueryable()
             .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
             .Where(x => S8ModuleCode.All.Contains(x.ModuleCode))
+            .WhereIF(periodFrom.HasValue, x => x.CreatedAt >= periodFrom!.Value)
+            .WhereIF(periodTo.HasValue,   x => x.CreatedAt < periodTo!.Value)
             .Select(x => new { x.ResponsibleDeptId, x.Status, x.SlaDeadline })
             .ToListAsync();
         var nowForTimeout = DateTime.Now;

+ 14 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8MonitoringService.cs

@@ -28,13 +28,17 @@ public class S8MonitoringService : ITransient
     /// 9宫格数据:S1-S7 订单健康分布 + S8业务类别汇总 + S9部门汇总。
     /// 数据来源 ado_s8_exception 聚合;异常表为空时返回全 0(不再返回 Demo 数据)。
     /// </summary>
-    public async Task<AdoS8OrderGridDto> GetOrderGridAsync(long tenantId = 1, long factoryId = 1)
+    public async Task<AdoS8OrderGridDto> GetOrderGridAsync(long tenantId = 1, long factoryId = 1, string? period = null)
     {
+        var (periodFrom, periodTo) = S8PeriodHelper.Resolve(period);
+
         // S8-DASHBOARD-DATA-ALIGN-S1S7-1:统一统计基准 module_code IN S1-S7;
         // module_code IS NULL 的 legacy/demo 行不参与大屏聚合(不依赖 scene_code、不映射 legacy scene)。
         var events = await _rep.AsQueryable()
             .Where(e => e.TenantId == tenantId && e.FactoryId == factoryId && !e.IsDeleted)
             .Where(e => S8ModuleCode.All.Contains(e.ModuleCode))
+            .WhereIF(periodFrom.HasValue, e => e.CreatedAt >= periodFrom!.Value)
+            .WhereIF(periodTo.HasValue,   e => e.CreatedAt < periodTo!.Value)
             .Select(e => new
             {
                 e.ModuleCode,
@@ -209,14 +213,21 @@ public class S8MonitoringService : ITransient
     /// </summary>
     public async Task<AdoS8MonitoringSummaryDto> GetSummaryAsync(AdoS8MonitoringSummaryQueryDto q)
     {
+        // 显式日期范围优先;未传显式范围时尝试从 period 解析。
+        var (periodFrom, periodTo) = (!q.BizDateFrom.HasValue && !q.BizDateTo.HasValue)
+            ? S8PeriodHelper.Resolve(q.Period)
+            : (q.BizDateFrom, q.BizDateTo);
+        var effectiveFrom = q.BizDateFrom ?? periodFrom;
+        var effectiveTo   = q.BizDateTo   ?? periodTo;
+
         var query = _rep.AsQueryable()
             .Where(e => e.TenantId == q.TenantId && e.FactoryId == q.FactoryId && !e.IsDeleted)
             // S8-DASHBOARD-DATA-ALIGN-S1S7-1:统一统计基准 module_code IN S1-S7。
             .Where(e => S8ModuleCode.All.Contains(e.ModuleCode))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SceneCode), e => e.SceneCode == q.SceneCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.ModuleCode), e => e.ModuleCode == q.ModuleCode)
-            .WhereIF(q.BizDateFrom.HasValue, e => e.CreatedAt >= q.BizDateFrom!.Value)
-            .WhereIF(q.BizDateTo.HasValue, e => e.CreatedAt <= q.BizDateTo!.Value);
+            .WhereIF(effectiveFrom.HasValue, e => e.CreatedAt >= effectiveFrom!.Value)
+            .WhereIF(effectiveTo.HasValue,   e => e.CreatedAt < effectiveTo!.Value);
 
         // 聚合到内存(数据量在可控范围内,避免复杂 GROUP BY 兼容性问题)
         // S8-SLA-TIMEOUT-RUNTIME-1(P3):投影 SlaDeadline 替代 TimeoutFlag,timeout 改为运行时计算。