| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- using Admin.NET.Plugin.AiDOP.Dto.S8;
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- namespace Admin.NET.Plugin.AiDOP.Service.S8;
- /// <summary>
- /// 大屏卡片数据查询服务:按 (pageCode, cellCode) 读取配置,按 binding_type 分发组装结果。
- /// 配置解析:工厂覆盖优先(tenant+factory 精确匹配)→ 全局基线(tenant=0/factory=0)兜底。
- /// </summary>
- public class S8DashboardCellDataService : ITransient
- {
- private readonly SqlSugarRepository<AdoS8DashboardCellConfig> _cfgRep;
- private readonly SqlSugarRepository<AdoS8Exception> _exRep;
- private readonly SqlSugarRepository<SysOrg> _orgRep;
- public S8DashboardCellDataService(
- SqlSugarRepository<AdoS8DashboardCellConfig> cfgRep,
- SqlSugarRepository<AdoS8Exception> exRep,
- SqlSugarRepository<SysOrg> orgRep)
- {
- _cfgRep = cfgRep;
- _exRep = exRep;
- _orgRep = orgRep;
- }
- public async Task<AdoS8CellDataDto> GetAsync(AdoS8CellDataQueryDto q)
- {
- if (string.IsNullOrWhiteSpace(q.PageCode) || string.IsNullOrWhiteSpace(q.CellCode))
- throw new S8BizException("pageCode 和 cellCode 必填");
- var cfg = await ResolveConfigAsync(q.TenantId, q.FactoryId, q.PageCode, q.CellCode);
- if (cfg is null)
- return new AdoS8CellDataDto
- {
- PageCode = q.PageCode,
- CellCode = q.CellCode,
- Message = "未配置",
- };
- if (!cfg.Enabled)
- return new AdoS8CellDataDto
- {
- PageCode = q.PageCode,
- CellCode = q.CellCode,
- BindingType = cfg.BindingType,
- Title = cfg.CellTitle,
- StatMetric = cfg.StatMetric,
- TimeWindow = cfg.TimeWindow,
- Message = "已禁用",
- };
- var (from, to) = TimeRange(cfg.TimeWindow);
- var effectiveDeptGroupBy = !string.IsNullOrWhiteSpace(q.DeptGroupBy) ? q.DeptGroupBy! : cfg.DeptGroupBy;
- // 取事实数据
- var events = await _exRep.AsQueryable()
- .Where(e => e.TenantId == q.TenantId && e.FactoryId == q.FactoryId && !e.IsDeleted)
- .Where(e => e.CreatedAt >= from && e.CreatedAt < to)
- .Select(e => new EvtRow
- {
- Status = e.Status,
- ExceptionTypeCode = e.ExceptionTypeCode,
- ModuleCode = e.ModuleCode,
- SceneCode = e.SceneCode,
- ResponsibleDeptId = e.ResponsibleDeptId,
- OccurrenceDeptId = e.OccurrenceDeptId,
- CreatedAt = e.CreatedAt,
- ClosedAt = e.ClosedAt,
- })
- .ToListAsync();
- // 按 binding_type 过滤
- IEnumerable<EvtRow> scoped = cfg.BindingType switch
- {
- "EXCEPTION_TYPE" => events.Where(e => !string.IsNullOrEmpty(cfg.ExceptionTypeCode) && e.ExceptionTypeCode == cfg.ExceptionTypeCode),
- "AGGREGATE" => ApplyAggregateScope(events, cfg.AggregateScope),
- _ => events, // CUSTOM:不按类型过滤,返回同页范围聚合值供前端参考
- };
- var list = scoped.ToList();
- var dto = new AdoS8CellDataDto
- {
- CellCode = cfg.CellCode,
- PageCode = cfg.PageCode,
- Title = cfg.CellTitle,
- BindingType = cfg.BindingType,
- StatMetric = cfg.StatMetric,
- TimeWindow = cfg.TimeWindow,
- Value = ComputeValue(list, cfg.StatMetric),
- };
- // 明细分组:AGGREGATE 按部门;EXCEPTION_TYPE 可选按部门;CUSTOM 跳过
- if (cfg.BindingType == "AGGREGATE" || cfg.BindingType == "EXCEPTION_TYPE")
- {
- dto.Breakdown = await BuildDeptBreakdownAsync(list, effectiveDeptGroupBy, cfg.StatMetric);
- }
- return dto;
- }
- private async Task<AdoS8DashboardCellConfig?> ResolveConfigAsync(long tenantId, long factoryId, string pageCode, string cellCode)
- {
- var rows = await _cfgRep.AsQueryable()
- .Where(x => x.PageCode == pageCode && x.CellCode == cellCode
- && ((x.TenantId == 0 && x.FactoryId == 0)
- || (x.TenantId == tenantId && x.FactoryId == factoryId)))
- .ToListAsync();
- return rows.OrderByDescending(x => x.FactoryId).FirstOrDefault();
- }
- private static (DateTime from, DateTime to) TimeRange(string window)
- {
- var now = DateTime.Now;
- return window switch
- {
- "TODAY" => (now.Date, now.Date.AddDays(1)),
- "LAST_24H" => (now.AddHours(-24), now.AddMinutes(1)),
- "LAST_7D" => (now.AddDays(-7), now.AddMinutes(1)),
- "LAST_30D" => (now.AddDays(-30), now.AddMinutes(1)),
- _ => (now.AddHours(-24), now.AddMinutes(1)),
- };
- }
- private static IEnumerable<EvtRow> ApplyAggregateScope(List<EvtRow> events, string? scope) => scope switch
- {
- "DOMAIN_DELIVERY" => events.Where(e => e.SceneCode == "S1S7_DELIVERY"),
- "DOMAIN_PRODUCTION" => events.Where(e => e.SceneCode == "S2S6_PRODUCTION"),
- "DOMAIN_SUPPLY" => events.Where(e => e.SceneCode == "S3S5_SUPPLY"),
- "ALL" => events,
- _ => events,
- };
- private static double ComputeValue(List<EvtRow> rows, string metric)
- {
- if (rows.Count == 0) return 0;
- return metric switch
- {
- "OPEN_COUNT" => rows.Count(e => e.Status != "CLOSED"),
- "FREQUENCY" => rows.Count,
- "AVG_DURATION" => AvgHours(rows),
- "CLOSE_RATE" => Math.Round(rows.Count(e => e.Status == "CLOSED") * 100.0 / rows.Count, 1),
- _ => rows.Count,
- };
- }
- private static double AvgHours(List<EvtRow> rows)
- {
- var closed = rows.Where(r => r.ClosedAt.HasValue).ToList();
- if (closed.Count == 0) return 0;
- return Math.Round(closed.Average(r => (r.ClosedAt!.Value - r.CreatedAt).TotalHours), 1);
- }
- private async Task<List<AdoS8CellBreakdownItem>> BuildDeptBreakdownAsync(List<EvtRow> rows, string groupBy, string metric)
- {
- Func<EvtRow, long> keySel = groupBy == "OCCUR"
- ? (EvtRow e) => e.OccurrenceDeptId
- : (EvtRow e) => e.ResponsibleDeptId;
- var groups = rows.Where(e => keySel(e) > 0).GroupBy(keySel).ToList();
- if (groups.Count == 0) return new();
- var deptIds = groups.Select(g => g.Key).Distinct().ToList();
- var names = (await _orgRep.AsQueryable().Where(o => deptIds.Contains(o.Id)).Select(o => new { o.Id, o.Name }).ToListAsync())
- .ToDictionary(o => o.Id, o => o.Name);
- return groups.Select(g =>
- {
- var list = g.ToList();
- return new AdoS8CellBreakdownItem
- {
- Code = g.Key.ToString(),
- Label = names.TryGetValue(g.Key, out var n) ? n : $"部门{g.Key}",
- Value = ComputeValue(list, metric),
- };
- })
- .OrderByDescending(i => i.Value)
- .ToList();
- }
- private class EvtRow
- {
- public string Status { get; set; } = string.Empty;
- public string? ExceptionTypeCode { get; set; }
- public string? ModuleCode { get; set; }
- public string? SceneCode { get; set; }
- public long ResponsibleDeptId { get; set; }
- public long OccurrenceDeptId { get; set; }
- public DateTime CreatedAt { get; set; }
- public DateTime? ClosedAt { get; set; }
- }
- }
|