|
@@ -1,7 +1,9 @@
|
|
|
|
|
+using System.Text.Json;
|
|
|
using Admin.NET.Core;
|
|
using Admin.NET.Core;
|
|
|
using Admin.NET.Plugin.AiDOP.Dto.SmartOps;
|
|
using Admin.NET.Plugin.AiDOP.Dto.SmartOps;
|
|
|
using Admin.NET.Plugin.AiDOP.Entity;
|
|
using Admin.NET.Plugin.AiDOP.Entity;
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure;
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure;
|
|
|
|
|
+using Admin.NET.Plugin.AiDOP.Infrastructure.FormulaExpr;
|
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http;
|
|
|
using MiniExcelLibs;
|
|
using MiniExcelLibs;
|
|
|
using SqlSugar;
|
|
using SqlSugar;
|
|
@@ -93,7 +95,9 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
Id = e.Id, MetricCode = e.MetricCode, ModuleCode = e.ModuleCode,
|
|
Id = e.Id, MetricCode = e.MetricCode, ModuleCode = e.ModuleCode,
|
|
|
MetricLevel = e.MetricLevel, ParentId = e.ParentId, ParentName = parentName,
|
|
MetricLevel = e.MetricLevel, ParentId = e.ParentId, ParentName = parentName,
|
|
|
MetricName = e.MetricName, Description = e.Description, Formula = e.Formula,
|
|
MetricName = e.MetricName, Description = e.Description, Formula = e.Formula,
|
|
|
- CalcRule = e.CalcRule, DataSource = e.DataSource, StatFrequency = e.StatFrequency,
|
|
|
|
|
|
|
+ CalcRule = e.CalcRule,
|
|
|
|
|
+ FormulaExpr = e.FormulaExpr, FormulaPreview = e.FormulaPreview, FormulaRefs = e.FormulaRefs,
|
|
|
|
|
+ DataSource = e.DataSource, StatFrequency = e.StatFrequency,
|
|
|
Department = e.Department, DopFields = e.DopFields, Unit = e.Unit,
|
|
Department = e.Department, DopFields = e.DopFields, Unit = e.Unit,
|
|
|
Direction = e.Direction,
|
|
Direction = e.Direction,
|
|
|
YellowThreshold = e.YellowThreshold, RedThreshold = e.RedThreshold,
|
|
YellowThreshold = e.YellowThreshold, RedThreshold = e.RedThreshold,
|
|
@@ -115,6 +119,12 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
return BadRequest(new { message = "L2/L3 必须指定父指标" });
|
|
return BadRequest(new { message = "L2/L3 必须指定父指标" });
|
|
|
|
|
|
|
|
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
|
|
var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
|
|
|
|
|
+
|
|
|
|
|
+ // Q4-C:对 FormulaExpr 做解析 + 校验;启用指标硬校验,否则仅警告
|
|
|
|
|
+ var fxResult = await ProcessFormulaAsync(tenantId, dto.FormulaExpr, dto.IsEnabled);
|
|
|
|
|
+ if (fxResult.BlockSave)
|
|
|
|
|
+ return BadRequest(new { message = "公式校验失败", errors = fxResult.Errors });
|
|
|
|
|
+
|
|
|
var code = await GenerateMetricCodeAsync(dto.ModuleCode, dto.MetricLevel, tenantId);
|
|
var code = await GenerateMetricCodeAsync(dto.ModuleCode, dto.MetricLevel, tenantId);
|
|
|
|
|
|
|
|
var entity = new AdoSmartOpsKpiMaster
|
|
var entity = new AdoSmartOpsKpiMaster
|
|
@@ -127,6 +137,9 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
Description = dto.Description,
|
|
Description = dto.Description,
|
|
|
Formula = dto.Formula,
|
|
Formula = dto.Formula,
|
|
|
CalcRule = dto.CalcRule,
|
|
CalcRule = dto.CalcRule,
|
|
|
|
|
+ FormulaExpr = dto.FormulaExpr,
|
|
|
|
|
+ FormulaPreview = fxResult.Preview,
|
|
|
|
|
+ FormulaRefs = fxResult.RefsJson,
|
|
|
DataSource = dto.DataSource,
|
|
DataSource = dto.DataSource,
|
|
|
StatFrequency = dto.StatFrequency,
|
|
StatFrequency = dto.StatFrequency,
|
|
|
Department = dto.Department,
|
|
Department = dto.Department,
|
|
@@ -145,7 +158,13 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
|
|
|
|
|
var newId = await _db.Insertable(entity).ExecuteReturnBigIdentityAsync();
|
|
var newId = await _db.Insertable(entity).ExecuteReturnBigIdentityAsync();
|
|
|
entity.Id = newId;
|
|
entity.Id = newId;
|
|
|
- return Ok(new { id = newId, metricCode = code });
|
|
|
|
|
|
|
+ return Ok(new
|
|
|
|
|
+ {
|
|
|
|
|
+ id = newId,
|
|
|
|
|
+ metricCode = code,
|
|
|
|
|
+ formulaPreview = fxResult.Preview,
|
|
|
|
|
+ formulaWarnings = fxResult.Warnings
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>编辑指标</summary>
|
|
/// <summary>编辑指标</summary>
|
|
@@ -155,6 +174,13 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
var entity = await _db.Queryable<AdoSmartOpsKpiMaster>().FirstAsync(x => x.Id == id);
|
|
var entity = await _db.Queryable<AdoSmartOpsKpiMaster>().FirstAsync(x => x.Id == id);
|
|
|
if (entity == null) return NotFound();
|
|
if (entity == null) return NotFound();
|
|
|
|
|
|
|
|
|
|
+ var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
|
|
|
|
|
+
|
|
|
|
|
+ // Q4-C:公式解析 + 校验
|
|
|
|
|
+ var fxResult = await ProcessFormulaAsync(tenantId, dto.FormulaExpr, dto.IsEnabled, excludeKpiId: id);
|
|
|
|
|
+ if (fxResult.BlockSave)
|
|
|
|
|
+ return BadRequest(new { message = "公式校验失败", errors = fxResult.Errors });
|
|
|
|
|
+
|
|
|
entity.ModuleCode = dto.ModuleCode;
|
|
entity.ModuleCode = dto.ModuleCode;
|
|
|
entity.MetricLevel = dto.MetricLevel;
|
|
entity.MetricLevel = dto.MetricLevel;
|
|
|
entity.ParentId = dto.ParentId;
|
|
entity.ParentId = dto.ParentId;
|
|
@@ -162,6 +188,9 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
entity.Description = dto.Description;
|
|
entity.Description = dto.Description;
|
|
|
entity.Formula = dto.Formula;
|
|
entity.Formula = dto.Formula;
|
|
|
entity.CalcRule = dto.CalcRule;
|
|
entity.CalcRule = dto.CalcRule;
|
|
|
|
|
+ entity.FormulaExpr = dto.FormulaExpr;
|
|
|
|
|
+ entity.FormulaPreview = fxResult.Preview;
|
|
|
|
|
+ entity.FormulaRefs = fxResult.RefsJson;
|
|
|
entity.DataSource = dto.DataSource;
|
|
entity.DataSource = dto.DataSource;
|
|
|
entity.StatFrequency = dto.StatFrequency;
|
|
entity.StatFrequency = dto.StatFrequency;
|
|
|
entity.Department = dto.Department;
|
|
entity.Department = dto.Department;
|
|
@@ -177,7 +206,12 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
entity.UpdatedAt = DateTime.Now;
|
|
entity.UpdatedAt = DateTime.Now;
|
|
|
|
|
|
|
|
await _db.Updateable(entity).ExecuteCommandAsync();
|
|
await _db.Updateable(entity).ExecuteCommandAsync();
|
|
|
- return Ok(new { ok = true });
|
|
|
|
|
|
|
+ return Ok(new
|
|
|
|
|
+ {
|
|
|
|
|
+ ok = true,
|
|
|
|
|
+ formulaPreview = fxResult.Preview,
|
|
|
|
|
+ formulaWarnings = fxResult.Warnings
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>删除指标(含子节点级联删除)</summary>
|
|
/// <summary>删除指标(含子节点级联删除)</summary>
|
|
@@ -559,4 +593,82 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
|
|
|
SortChildren(n.Children);
|
|
SortChildren(n.Children);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ── 公式解析 / 校验 / 预览:第三批新增 ──
|
|
|
|
|
+
|
|
|
|
|
+ private sealed class FormulaProcessResult
|
|
|
|
|
+ {
|
|
|
|
|
+ public string? Preview { get; init; }
|
|
|
|
|
+ public string? RefsJson { get; init; }
|
|
|
|
|
+ public List<string> Errors { get; init; } = new();
|
|
|
|
|
+ public List<string> Warnings { get; init; } = new();
|
|
|
|
|
+ public bool BlockSave { get; init; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task<FormulaProcessResult> ProcessFormulaAsync(
|
|
|
|
|
+ long tenantId, string? expr, bool isEnabled, long? excludeKpiId = null)
|
|
|
|
|
+ {
|
|
|
|
|
+ var parsed = FormulaParser.Parse(expr);
|
|
|
|
|
+ if (parsed.IsEmpty)
|
|
|
|
|
+ return new FormulaProcessResult();
|
|
|
|
|
+
|
|
|
|
|
+ var validation = await FormulaValidator.ValidateAsync(_db, tenantId, parsed, isEnabled, excludeKpiId);
|
|
|
|
|
+
|
|
|
|
|
+ // 启用指标 → 硬校验;否则仅警告
|
|
|
|
|
+ if (isEnabled && validation.HasErrors)
|
|
|
|
|
+ {
|
|
|
|
|
+ return new FormulaProcessResult
|
|
|
|
|
+ {
|
|
|
|
|
+ Errors = validation.Errors,
|
|
|
|
|
+ Warnings = validation.Warnings,
|
|
|
|
|
+ BlockSave = true,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var preview = await FormulaPreviewBuilder.BuildAsync(_db, tenantId, expr, parsed);
|
|
|
|
|
+ var refsJson = JsonSerializer.Serialize(new
|
|
|
|
|
+ {
|
|
|
|
|
+ metrics = parsed.MetricRefs,
|
|
|
|
|
+ facts = parsed.FactRefs,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return new FormulaProcessResult
|
|
|
|
|
+ {
|
|
|
|
|
+ Preview = preview,
|
|
|
|
|
+ RefsJson = refsJson,
|
|
|
|
|
+ Errors = validation.Errors,
|
|
|
|
|
+ Warnings = validation.Warnings,
|
|
|
|
|
+ BlockSave = false,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>实时预览公式(不落库)。前端编辑器失焦/定时调用。</summary>
|
|
|
|
|
+ [HttpPost("preview-formula")]
|
|
|
|
|
+ public async Task<IActionResult> PreviewFormula([FromBody] PreviewFormulaIn input)
|
|
|
|
|
+ {
|
|
|
|
|
+ var tenantId = AidopTenantHelper.GetTenantId(HttpContext);
|
|
|
|
|
+ var parsed = FormulaParser.Parse(input?.FormulaExpr);
|
|
|
|
|
+ if (parsed.IsEmpty)
|
|
|
|
|
+ return Ok(new { preview = "", metrics = new List<string>(), facts = new List<string>(), errors = Array.Empty<string>(), warnings = Array.Empty<string>() });
|
|
|
|
|
+
|
|
|
|
|
+ var validation = await FormulaValidator.ValidateAsync(
|
|
|
|
|
+ _db, tenantId, parsed, isEnabled: input?.IsEnabled ?? true, excludeKpiId: input?.ExcludeKpiId);
|
|
|
|
|
+ var preview = await FormulaPreviewBuilder.BuildAsync(_db, tenantId, input!.FormulaExpr, parsed);
|
|
|
|
|
+
|
|
|
|
|
+ return Ok(new
|
|
|
|
|
+ {
|
|
|
|
|
+ preview,
|
|
|
|
|
+ metrics = parsed.MetricRefs,
|
|
|
|
|
+ facts = parsed.FactRefs,
|
|
|
|
|
+ errors = validation.Errors,
|
|
|
|
|
+ warnings = validation.Warnings,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public sealed class PreviewFormulaIn
|
|
|
|
|
+ {
|
|
|
|
|
+ public string? FormulaExpr { get; set; }
|
|
|
|
|
+ public bool IsEnabled { get; set; } = true;
|
|
|
|
|
+ public long? ExcludeKpiId { get; set; }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|