| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- using Admin.NET.Plugin.AiDOP.Dto.S8;
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
- namespace Admin.NET.Plugin.AiDOP.Service.S8;
- public class S8MonitoringService : ITransient
- {
- private readonly SqlSugarRepository<AdoS8Exception> _rep;
- private readonly SqlSugarRepository<AdoS8ExceptionType> _typeRep;
- private readonly SqlSugarRepository<SysOrg> _orgRep;
- public S8MonitoringService(
- SqlSugarRepository<AdoS8Exception> rep,
- SqlSugarRepository<AdoS8ExceptionType> typeRep,
- SqlSugarRepository<SysOrg> orgRep)
- {
- _rep = rep;
- _typeRep = typeRep;
- _orgRep = orgRep;
- }
- /// <summary>
- /// 9宫格数据:S1-S7 订单健康分布 + S8业务类别汇总 + S9部门汇总。
- /// 数据来源 ado_s8_exception 聚合;异常表为空时返回全 0(不再返回 Demo 数据)。
- /// </summary>
- public async Task<AdoS8OrderGridDto> GetOrderGridAsync(long tenantId = 1, long factoryId = 1)
- {
- var events = await _rep.AsQueryable()
- .Where(e => e.TenantId == tenantId && e.FactoryId == factoryId && !e.IsDeleted)
- .Select(e => new
- {
- e.ModuleCode,
- e.Severity,
- e.Status,
- e.TimeoutFlag,
- e.ExceptionTypeCode,
- e.SceneCode,
- e.ResponsibleDeptId,
- e.CreatedAt,
- e.ClosedAt
- })
- .ToListAsync();
- // ── Modules:按 module_code 聚合 ──
- var modules = S8ModuleCode.All.Select(mc =>
- {
- var rows = events.Where(e => e.ModuleCode == mc).ToList();
- var unclosed = rows.Where(e => e.Status != "CLOSED").ToList();
- var red = unclosed.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH");
- var yellow = unclosed.Count(e => e.Severity == "MEDIUM");
- var green = unclosed.Count(e => e.Severity == "LOW");
- var closed = rows.Count(e => e.Status == "CLOSED");
- var total = rows.Count;
- return new AdoS8ModuleOrderSummary
- {
- ModuleCode = mc,
- ModuleLabel = S8ModuleCode.Label(mc),
- Green = green,
- Yellow = yellow,
- Red = red,
- Total = total,
- Frequency = total,
- AvgProcessHours = AvgHours(rows.Select(r => (r.CreatedAt, r.ClosedAt))),
- CloseRate = total == 0 ? 0 : Math.Round(closed * 100.0 / total, 1),
- };
- }).ToList();
- // ── ByCategory:按异常类型的 domain_code/type_code 聚合为 5 大业务类别 ──
- var typeMap = (await _typeRep.AsQueryable()
- .Where(t => (t.TenantId == 0 && t.FactoryId == 0)
- || (t.TenantId == tenantId && t.FactoryId == factoryId))
- .ToListAsync())
- .GroupBy(t => t.TypeCode)
- .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.FactoryId).First());
- var byCategory = events
- .Where(e => !string.IsNullOrEmpty(e.ExceptionTypeCode) && typeMap.ContainsKey(e.ExceptionTypeCode!))
- .GroupBy(e => CategoryOf(typeMap[e.ExceptionTypeCode!]))
- .Where(g => !string.IsNullOrEmpty(g.Key))
- .Select(g =>
- {
- var total = g.Count();
- var closed = g.Count(e => e.Status == "CLOSED");
- return new AdoS8CategorySummary
- {
- Category = g.Key!,
- Total = total,
- AvgProcessHours = AvgHours(g.Select(e => (e.CreatedAt, e.ClosedAt))),
- CloseRate = total == 0 ? 0 : Math.Round(closed * 100.0 / total, 1),
- };
- })
- .OrderBy(c => CategoryOrder(c.Category))
- .ToList();
- // ── ByDept:按 ResponsibleDeptId 聚合,JOIN SysOrg 取部门名称 ──
- var deptIds = events.Select(e => e.ResponsibleDeptId).Where(id => id > 0).Distinct().ToList();
- var deptNameMap = deptIds.Count == 0
- ? new Dictionary<long, string>()
- : (await _orgRep.AsQueryable().Where(o => deptIds.Contains(o.Id)).Select(o => new { o.Id, o.Name }).ToListAsync())
- .ToDictionary(o => o.Id, o => o.Name);
- var byDept = events
- .Where(e => e.ResponsibleDeptId > 0)
- .GroupBy(e => e.ResponsibleDeptId)
- .Select(g =>
- {
- var total = g.Count();
- var closed = g.Count(e => e.Status == "CLOSED");
- return new AdoS8DeptSummary
- {
- DeptName = deptNameMap.TryGetValue(g.Key, out var n) ? n : $"部门{g.Key}",
- Total = total,
- AvgProcessHours = AvgHours(g.Select(e => (e.CreatedAt, e.ClosedAt))),
- CloseRate = total == 0 ? 0 : Math.Round(closed * 100.0 / total, 1),
- };
- })
- .OrderByDescending(d => d.Total)
- .ToList();
- return new AdoS8OrderGridDto
- {
- Modules = modules,
- ByCategory = byCategory,
- ByDept = byDept,
- };
- }
- private static double AvgHours(IEnumerable<(DateTime createdAt, DateTime? closedAt)> items)
- {
- var closed = items.Where(x => x.closedAt.HasValue).ToList();
- if (closed.Count == 0) return 0;
- var avg = closed.Average(x => (x.closedAt!.Value - x.createdAt).TotalHours);
- return Math.Round(avg, 1);
- }
- /// <summary>异常类型 → 业务类别(与 Overview 页 5 张类别卡对应)。</summary>
- private static string CategoryOf(AdoS8ExceptionType t) => t.TypeCode switch
- {
- "ORDER_CHANGE" => "订单评审",
- "DELIVERY_DELAY" => "总装发货",
- "PENDING_SHIPMENT" => "总装发货",
- "EQUIP_FAULT" => "本体生产",
- "MATERIAL_SHORTAGE" => "本体生产",
- "QUALITY_DEFECT" => "本体生产",
- "SUPPLIER_ETA_ISSUE" => "材料采购",
- "SUPPLIER_SHIP_ISSUE" => "材料采购",
- "WH_INBOUND_ISSUE" => "材料采购",
- "IQC_ISSUE" => "材料采购",
- "WH_PUTAWAY_ISSUE" => "材料采购",
- "WH_KIT_ISSUE" => "本体生产",
- "WH_ISSUE_OUT_ISSUE" => "本体生产",
- _ => string.Empty,
- };
- private static int CategoryOrder(string category) => category switch
- {
- "订单评审" => 1,
- "产品设计" => 2,
- "材料采购" => 3,
- "本体生产" => 4,
- "总装发货" => 5,
- _ => 99,
- };
- /// <summary>
- /// 异常监控汇总:按 module_code 分组统计红/黄/绿/超时数。
- /// 供综合全景页顶部徽标和模块汇总表使用。
- /// </summary>
- public async Task<AdoS8MonitoringSummaryDto> GetSummaryAsync(AdoS8MonitoringSummaryQueryDto q)
- {
- var query = _rep.AsQueryable()
- .Where(e => e.TenantId == q.TenantId && e.FactoryId == q.FactoryId && !e.IsDeleted)
- .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);
- // 聚合到内存(数据量在可控范围内,避免复杂 GROUP BY 兼容性问题)
- var raw = await query
- .Select(e => new
- {
- e.ModuleCode,
- e.SceneCode,
- e.Severity,
- e.TimeoutFlag,
- e.Status
- })
- .ToListAsync();
- var byModule = raw
- .GroupBy(e => new { mc = e.ModuleCode ?? string.Empty, sc = e.SceneCode ?? string.Empty })
- .Select(g => new AdoS8ModuleSummaryItem
- {
- ModuleCode = g.Key.mc,
- ModuleLabel = S8ModuleCode.Label(g.Key.mc),
- SceneCode = g.Key.sc,
- SceneLabel = S8SceneCode.Label(g.Key.sc),
- Total = g.Count(),
- Red = g.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH"),
- Yellow = g.Count(e => e.Severity == "MEDIUM"),
- Green = g.Count(e => e.Severity == "LOW"),
- Timeout = g.Count(e => e.TimeoutFlag && e.Status != "CLOSED")
- })
- // 按 S8ModuleCode.All 顺序排列
- .OrderBy(r => Array.IndexOf(S8ModuleCode.All, r.ModuleCode))
- .ToList();
- return new AdoS8MonitoringSummaryDto
- {
- Total = raw.Count,
- Red = raw.Count(e => e.Severity == "CRITICAL" || e.Severity == "HIGH"),
- Yellow = raw.Count(e => e.Severity == "MEDIUM"),
- Green = raw.Count(e => e.Severity == "LOW"),
- Timeout = raw.Count(e => e.TimeoutFlag && e.Status != "CLOSED"),
- ByModule = byModule
- };
- }
- }
|