using Admin.NET.Plugin.AiDOP.Entity.S8; namespace Admin.NET.Plugin.AiDOP.Service.S8; /// /// S8 大屏卡片配置(ado_s8_dashboard_cell_config)CRUD。 /// 列表合并全局基线(0/0)与工厂覆盖,同 (pageCode, cellCode) 以工厂记录优先。 /// public class S8DashboardCellConfigService : ITransient { private readonly SqlSugarRepository _rep; public S8DashboardCellConfigService(SqlSugarRepository rep) => _rep = rep; public async Task> ListAsync(long tenantId, long factoryId) { var all = await _rep.AsQueryable() .Where(x => (x.TenantId == 0 && x.FactoryId == 0) || (x.TenantId == tenantId && x.FactoryId == factoryId)) .ToListAsync(); return all .GroupBy(x => (x.PageCode, x.CellCode)) .Select(g => g.OrderByDescending(x => x.FactoryId).First()) .OrderBy(x => x.PageCode) .ThenBy(x => x.SortNo) .ThenBy(x => x.CellCode) .ToList(); } public async Task CreateAsync(AdoS8DashboardCellConfig body) { ValidateAndNormalize(body); var exists = await _rep.AsQueryable() .AnyAsync(x => x.TenantId == body.TenantId && x.FactoryId == body.FactoryId && x.PageCode == body.PageCode && x.CellCode == body.CellCode); if (exists) throw new S8BizException("同一工厂下页面与卡片编码组合已存在"); body.Id = 0; body.CreatedAt = DateTime.Now; body.UpdatedAt = null; await _rep.InsertAsync(body); return body; } public async Task UpdateAsync(long id, AdoS8DashboardCellConfig body) { var e = await _rep.GetByIdAsync(id) ?? throw new S8BizException("记录不存在"); ValidateAndNormalize(body); var dup = await _rep.AsQueryable() .AnyAsync(x => x.Id != id && x.TenantId == body.TenantId && x.FactoryId == body.FactoryId && x.PageCode == body.PageCode && x.CellCode == body.CellCode); if (dup) throw new S8BizException("同一工厂下页面与卡片编码组合已存在"); body.Id = id; body.CreatedAt = e.CreatedAt; body.UpdatedAt = DateTime.Now; await _rep.UpdateAsync(body); return body; } public async Task DeleteAsync(long id) => await _rep.DeleteByIdAsync(id); private static void ValidateAndNormalize(AdoS8DashboardCellConfig body) { if (string.IsNullOrWhiteSpace(body.PageCode) || string.IsNullOrWhiteSpace(body.CellCode)) throw new S8BizException("页面编码与卡片编码必填"); body.PageCode = body.PageCode.Trim(); body.CellCode = body.CellCode.Trim(); if (body.CellTitle != null) body.CellTitle = body.CellTitle.Trim(); if (string.IsNullOrWhiteSpace(body.BindingType)) body.BindingType = "CUSTOM"; body.BindingType = body.BindingType.Trim().ToUpperInvariant(); if (body.BindingType is not ("EXCEPTION_TYPE" or "AGGREGATE" or "CUSTOM")) throw new S8BizException("绑定类型须为 EXCEPTION_TYPE / AGGREGATE / CUSTOM"); switch (body.BindingType) { case "EXCEPTION_TYPE": if (string.IsNullOrWhiteSpace(body.ExceptionTypeCode)) throw new S8BizException("绑定类型为异常类型时须填写异常类型编码"); body.ExceptionTypeCode = body.ExceptionTypeCode.Trim(); body.AggregateScope = null; break; case "AGGREGATE": if (string.IsNullOrWhiteSpace(body.AggregateScope)) throw new S8BizException("绑定类型为域聚合时须选择聚合范围"); body.AggregateScope = body.AggregateScope!.Trim(); body.ExceptionTypeCode = null; break; default: body.ExceptionTypeCode = null; body.AggregateScope = null; break; } if (string.IsNullOrWhiteSpace(body.StatMetric)) body.StatMetric = "OPEN_COUNT"; body.StatMetric = body.StatMetric.Trim().ToUpperInvariant(); if (body.StatMetric is not ("OPEN_COUNT" or "FREQUENCY" or "AVG_DURATION" or "CLOSE_RATE")) throw new S8BizException("统计指标须为 OPEN_COUNT / FREQUENCY / AVG_DURATION / CLOSE_RATE"); if (string.IsNullOrWhiteSpace(body.TimeWindow)) body.TimeWindow = "LAST_24H"; body.TimeWindow = body.TimeWindow.Trim().ToUpperInvariant(); if (body.TimeWindow is not ("TODAY" or "LAST_24H" or "LAST_7D" or "LAST_30D")) throw new S8BizException("时间窗须为 TODAY / LAST_24H / LAST_7D / LAST_30D"); if (string.IsNullOrWhiteSpace(body.DeptGroupBy)) body.DeptGroupBy = "OWNER"; body.DeptGroupBy = body.DeptGroupBy.Trim().ToUpperInvariant(); if (body.DeptGroupBy is not ("OWNER" or "OCCUR")) throw new S8BizException("部门聚合维度须为 OWNER(责任部门)或 OCCUR(发生部门)"); if (body.FilterExpression != null && body.FilterExpression.Length > 1000) throw new S8BizException("筛选表达式过长"); } }