using Admin.NET.Plugin.AiDOP.Dto.SmartOps; using Admin.NET.Plugin.AiDOP.Entity; namespace Admin.NET.Plugin.AiDOP.SmartOps; public static class KpiTargetRules { public static readonly DateTime OpenEnd = new(9999, 12, 31); public static DateTime Normalize(DateTime value) => value.Date; public static DateTime EndOrMax(DateTime? effectiveTo) => (effectiveTo ?? OpenEnd).Date; public static bool Overlaps(DateTime aFrom, DateTime? aTo, DateTime bFrom, DateTime? bTo) { var aStart = aFrom.Date; var aEnd = EndOrMax(aTo); var bStart = bFrom.Date; var bEnd = EndOrMax(bTo); return aStart <= bEnd && bStart <= aEnd; } public static bool Covers(DateTime from, DateTime? to, DateTime bizDate) { var day = bizDate.Date; return from.Date <= day && day <= EndOrMax(to); } public static void EnsureTargetPositive(decimal targetValue) { if (targetValue <= 0) throw new KpiTargetValidationException("目标值必须大于 0"); } public static void EnsureRange(DateTime from, DateTime? to) { if (to.HasValue && to.Value.Date < from.Date) throw new KpiTargetValidationException("失效日期不能早于生效日期"); } public static AdoSmartOpsKpiTargetConfig PickSingle( IReadOnlyList matches, string scopeLabel) { if (matches.Count == 0) throw new InvalidOperationException("PickSingle 需要至少一条配置"); if (matches.Count > 1) throw new KpiTargetDuplicateScopeException( $"{scopeLabel} 命中 {matches.Count} 条重叠目标,拒绝自动选择最新一条"); return matches[0]; } public static List ActiveOn( IEnumerable configs, DateTime bizDate) { return configs .Where(x => x.Status == 1 && Covers(x.EffectiveFrom, x.EffectiveTo, bizDate)) .OrderBy(x => x.Id) .ToList(); } public static KpiTargetDto ToDto(AdoSmartOpsKpiTargetConfig x) => new() { Id = x.Id, FactoryId = x.FactoryId, MetricCode = x.MetricCode, TargetValue = x.TargetValue, EffectiveFrom = x.EffectiveFrom.ToString("yyyy-MM-dd"), EffectiveTo = x.EffectiveTo?.ToString("yyyy-MM-dd"), SourceType = x.SourceType, SourceBatchId = x.SourceBatchId, Status = x.Status, Remark = x.Remark, CreateTime = x.CreateTime, UpdateTime = x.UpdateTime }; } public sealed class KpiTargetValidationException : Exception { public KpiTargetValidationException(string message) : base(message) { } } public sealed class KpiTargetConflictException : Exception { public KpiTargetConflictException(string message) : base(message) { } } public sealed class KpiTargetDuplicateScopeException : Exception { public KpiTargetDuplicateScopeException(string message) : base(message) { } } public sealed class KpiTargetNotFoundException : Exception { public KpiTargetNotFoundException(string message) : base(message) { } } public sealed class KpiDemoImportForbiddenException : Exception { public KpiDemoImportForbiddenException(string message) : base(message) { } }