| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
- using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- namespace Admin.NET.Plugin.AiDOP.Service.S8;
- public class S8DashboardService : ITransient
- {
- private readonly SqlSugarRepository<AdoS8Exception> _rep;
- private readonly SqlSugarRepository<AdoS0DepartmentMaster> _deptRep;
- private readonly SqlSugarRepository<AdoS8ProcessNode> _processNodeRep;
- public S8DashboardService(
- SqlSugarRepository<AdoS8Exception> rep,
- SqlSugarRepository<AdoS0DepartmentMaster> deptRep,
- SqlSugarRepository<AdoS8ProcessNode> processNodeRep)
- {
- _rep = rep;
- _deptRep = deptRep;
- _processNodeRep = processNodeRep;
- }
- public async Task<object> GetOverviewAsync(long tenantId, long factoryId)
- {
- var q = _rep.AsQueryable().Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted);
- var total = await q.CountAsync();
- var pending = await q.CountAsync(x => x.Status == "NEW" || x.Status == "ASSIGNED" || x.Status == "IN_PROGRESS");
- var inProgress = await q.CountAsync(x => x.Status == "IN_PROGRESS");
- var timeout = await q.CountAsync(x => x.TimeoutFlag);
- var closed = await q.CountAsync(x => x.Status == "CLOSED");
- var todayNew = await q.CountAsync(x => x.CreatedAt >= DateTime.Today);
- var critical = await q.CountAsync(x => x.Severity == "CRITICAL");
- var closureRate = total > 0 ? Math.Round(closed * 100.0 / total, 1) : 0.0;
- // 平均处理周期(小时),仅对已闭环且有关闭时间的记录计算
- var closedRows = await q
- .Where(x => x.Status == "CLOSED" && x.ClosedAt != null)
- .Select(x => new { x.CreatedAt, x.ClosedAt })
- .ToListAsync();
- var avgCycleHours = closedRows.Count > 0
- ? Math.Round(closedRows.Average(x => (x.ClosedAt!.Value - x.CreatedAt).TotalHours), 1)
- : 0.0;
- return new { total, pending, inProgress, timeout, closed, todayNew, critical, closureRate, avgCycleHours };
- }
- public async Task<object> GetTrendsAsync(long tenantId, long factoryId, int days)
- {
- var from = DateTime.Today.AddDays(-Math.Clamp(days, 1, 90));
- var rows = await _rep.AsQueryable()
- .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted && x.CreatedAt >= from)
- .Select(x => new { x.CreatedAt })
- .ToListAsync();
- return rows
- .GroupBy(x => x.CreatedAt.Date)
- .OrderBy(g => g.Key)
- .Select(g => new { date = g.Key.ToString("yyyy-MM-dd"), count = g.Count() })
- .ToList();
- }
- public async Task<object> GetDistributionsAsync(long tenantId, long factoryId)
- {
- var list = await _rep.AsQueryable()
- .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
- .Select(x => new
- {
- x.Status,
- x.SceneCode,
- x.Severity,
- x.ResponsibleDeptId,
- x.OccurrenceDeptId,
- x.ProcessNodeCode,
- x.RelatedObjectCode,
- })
- .ToListAsync();
- // 部门名称字典
- var allDeptIds = list.Select(x => x.ResponsibleDeptId)
- .Concat(list.Select(x => x.OccurrenceDeptId))
- .Distinct().ToList();
- var deptMap = await _deptRep.AsQueryable()
- .Where(d => allDeptIds.Contains(d.Id))
- .Select(d => new { d.Id, Name = d.Descr ?? d.Department })
- .ToListAsync();
- var deptDict = deptMap.ToDictionary(d => d.Id, d => d.Name ?? d.Id.ToString());
- // 流程节点名称字典
- var processNodes = await _processNodeRep.AsQueryable()
- .OrderBy(p => p.SortNo)
- .Select(p => new { p.Code, p.Name })
- .ToListAsync();
- var processDict = processNodes.ToDictionary(p => p.Code, p => p.Name);
- return new
- {
- byStatus = list
- .GroupBy(x => x.Status)
- .Select(g => new { key = g.Key, count = g.Count() }),
- byScene = list
- .GroupBy(x => x.SceneCode)
- .Select(g => new { key = g.Key, count = g.Count() }),
- bySeverity = list
- .GroupBy(x => x.Severity)
- .Select(g => new { key = g.Key, count = g.Count() }),
- byDept = list
- .GroupBy(x => x.ResponsibleDeptId)
- .Select(g => new
- {
- key = g.Key,
- deptName = deptDict.GetValueOrDefault(g.Key, g.Key.ToString()),
- count = g.Count(),
- }),
- byOccurrenceDept = list
- .GroupBy(x => x.OccurrenceDeptId)
- .Select(g => new
- {
- key = g.Key,
- deptName = deptDict.GetValueOrDefault(g.Key, g.Key.ToString()),
- count = g.Count(),
- }),
- byProcess = list
- .Where(x => x.ProcessNodeCode != null)
- .GroupBy(x => x.ProcessNodeCode!)
- .Select(g => new
- {
- key = g.Key,
- nodeName = processDict.GetValueOrDefault(g.Key, g.Key),
- count = g.Count(),
- }),
- byObject = list
- .Where(x => x.RelatedObjectCode != null)
- .GroupBy(x => x.RelatedObjectCode!)
- .OrderByDescending(g => g.Count())
- .Take(20)
- .Select(g => new { key = g.Key, count = g.Count() }),
- };
- }
- public async Task<object> GetDeptBacklogAsync(long tenantId, long factoryId)
- {
- var pendingStatuses = new[] { "NEW", "ASSIGNED", "IN_PROGRESS" };
- var list = await _rep.AsQueryable()
- .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
- .Select(x => new { x.ResponsibleDeptId, x.Status, x.TimeoutFlag })
- .ToListAsync();
- var deptIds = list.Select(x => x.ResponsibleDeptId).Distinct().ToList();
- var deptMap = await _deptRep.AsQueryable()
- .Where(d => deptIds.Contains(d.Id))
- .Select(d => new { d.Id, Name = d.Descr ?? d.Department })
- .ToListAsync();
- var deptDict = deptMap.ToDictionary(d => d.Id, d => d.Name ?? d.Id.ToString());
- return list
- .GroupBy(x => x.ResponsibleDeptId)
- .Select(g => new
- {
- deptId = g.Key,
- deptName = deptDict.GetValueOrDefault(g.Key, g.Key.ToString()),
- pending = g.Count(x => pendingStatuses.Contains(x.Status)),
- inProgress = g.Count(x => x.Status == "IN_PROGRESS"),
- timeout = g.Count(x => x.TimeoutFlag),
- total = g.Count(),
- })
- .OrderByDescending(x => x.pending)
- .ToList();
- }
- /// <summary>
- /// 按维度返回多系列日趋势数据。dim: object | process | occDept | respDept
- /// 返回: { dates, series: [{ name, data[] }] }
- /// </summary>
- public async Task<object> GetDimTrendsAsync(long tenantId, long factoryId, string dim, int days)
- {
- var from = DateTime.Today.AddDays(-Math.Clamp(days, 1, 90));
- var rows = await _rep.AsQueryable()
- .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted && x.CreatedAt >= from)
- .Select(x => new
- {
- x.CreatedAt,
- x.ProcessNodeCode,
- x.RelatedObjectCode,
- x.OccurrenceDeptId,
- x.ResponsibleDeptId,
- })
- .ToListAsync();
- // 生成连续日期序列
- var dates = Enumerable.Range(0, (DateTime.Today - from).Days + 1)
- .Select(i => from.AddDays(i).Date)
- .ToList();
- var dateLabels = dates.Select(d => d.ToString("yyyy-MM-dd")).ToList();
- // 按维度取 key 函数
- Func<dynamic, string> keySelector = dim switch
- {
- "process" => r => r.ProcessNodeCode ?? "未设置",
- "occDept" => r => r.OccurrenceDeptId.ToString(),
- "respDept" => r => r.ResponsibleDeptId.ToString(),
- _ => r => r.RelatedObjectCode ?? "未设置", // object
- };
- var grouped = rows
- .GroupBy(r => keySelector(r))
- .OrderByDescending(g => g.Count())
- .Take(8) // 最多取 8 个系列,避免图例过多
- .ToList();
- // 补全部门名 / 流程节点名
- Dictionary<string, string> nameMap = new();
- if (dim == "process")
- {
- var nodes = await _processNodeRep.AsQueryable().ToListAsync();
- nameMap = nodes.ToDictionary(n => n.Code, n => n.Name);
- }
- else if (dim is "occDept" or "respDept")
- {
- var ids = grouped.Select(g => long.TryParse(g.Key, out var id) ? id : 0).ToList();
- var depts = await _deptRep.AsQueryable()
- .Where(d => ids.Contains(d.Id))
- .Select(d => new { d.Id, Name = d.Descr ?? d.Department })
- .ToListAsync();
- nameMap = depts.ToDictionary(d => d.Id.ToString(), d => d.Name ?? d.Id.ToString());
- }
- var series = grouped.Select(g =>
- {
- var seriesName = nameMap.TryGetValue(g.Key, out var n) ? n : g.Key;
- var byDate = g.GroupBy(r => r.CreatedAt.Date).ToDictionary(x => x.Key, x => x.Count());
- var data = dates.Select(d => byDate.TryGetValue(d, out var c) ? c : 0).ToList();
- return new { name = seriesName, data };
- }).ToList();
- return new { dates = dateLabels, series };
- }
- public async Task<List<AdoS8Exception>> GetQuickExceptionsAsync(long tenantId, long factoryId, string mode)
- {
- var q = _rep.AsQueryable().Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted);
- var ordered = mode switch
- {
- "high-priority" => q.OrderBy(x => x.PriorityScore, OrderByType.Desc),
- "timeout" => q.Where(x => x.TimeoutFlag).OrderBy(x => x.SlaDeadline),
- _ => q.OrderBy(x => x.CreatedAt, OrderByType.Desc),
- };
- return await ordered.Take(20).ToListAsync();
- }
- }
|