Эх сурвалжийг харах

fix(s8): align timeout rule result columns

- Prefer canonical SQL aliases due_at/status/source_object_id/related_object_code when evaluating timeout hits.
- Fall back only to params_json configured fields when canonical aliases are absent.
- Preserve existing generated SQL and dictionary metadata; no SQL/params change.
- Do not create or enable watch rules.
- Live smoke confirmed no exception was created (ado_s8_exception 41 -> 41).
YY968XX 1 өдөр өмнө
parent
commit
9a1d086039

+ 29 - 9
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8TimeoutRuleEvaluator.cs

@@ -22,6 +22,14 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
 
 
     private const string SqlDataSourceType = "SQL";
     private const string SqlDataSourceType = "SQL";
 
 
+    // S8-WATCH-EXPRESSION-COLUMN-CONTRACT-FIX-1:S8ConfigDraftService.BuildExpression 统一把结果列
+    // 别名为以下 canonical 名(无论源表真实列名为何)。evaluator 优先按 canonical 读取,仅当结果集
+    // 不含 canonical 列时才回退到 params_json 指定的真实列名(兼容历史未别名规则)。
+    private const string CanonicalDueAtColumn = "due_at";
+    private const string CanonicalStatusColumn = "status";
+    private const string CanonicalSourceObjectIdColumn = "source_object_id";
+    private const string CanonicalRelatedObjectCodeColumn = "related_object_code";
+
     private readonly SqlSugarRepository<AdoS8DataSource> _dataSourceRep;
     private readonly SqlSugarRepository<AdoS8DataSource> _dataSourceRep;
     private readonly S8SqlSugarScopeFactory _scopeFactory;
     private readonly S8SqlSugarScopeFactory _scopeFactory;
     private readonly ILogger<S8TimeoutRuleEvaluator> _logger;
     private readonly ILogger<S8TimeoutRuleEvaluator> _logger;
@@ -95,24 +103,25 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
             ? rule.WatchObjectType
             ? rule.WatchObjectType
             : rule.SourceObjectType!;
             : rule.SourceObjectType!;
 
 
+        // 结果列名一次性解析(结果集列在整张 DataTable 内稳定):canonical 优先,缺失回退 params 真实列名。
+        var statusColumn = ResolveResultColumn(table, CanonicalStatusColumn, parameters.StatusField);
+        var dueAtColumn = ResolveResultColumn(table, CanonicalDueAtColumn, parameters.DueAtField);
+        var objectCodeColumn = ResolveResultColumn(table, CanonicalRelatedObjectCodeColumn, parameters.ObjectCodeField);
+        var objectIdColumn = ResolveResultColumn(table, CanonicalSourceObjectIdColumn, parameters.ObjectIdField);
+
         foreach (DataRow row in table.Rows)
         foreach (DataRow row in table.Rows)
         {
         {
-            var status = ReadString(row, parameters.StatusField!) ?? string.Empty;
+            var status = ReadString(row, statusColumn) ?? string.Empty;
             if (parameters.CompletedStates.Contains(status, StringComparer.OrdinalIgnoreCase))
             if (parameters.CompletedStates.Contains(status, StringComparer.OrdinalIgnoreCase))
                 continue;
                 continue;
 
 
-            var due = ReadDateTime(row, parameters.DueAtField!);
+            var due = ReadDateTime(row, dueAtColumn);
             if (due == null || due > threshold) continue;
             if (due == null || due > threshold) continue;
 
 
-            var objectCodeField = string.IsNullOrWhiteSpace(parameters.ObjectCodeField)
-                ? "related_object_code"
-                : parameters.ObjectCodeField!;
-            var relatedObjectCode = ReadString(row, objectCodeField) ?? string.Empty;
+            var relatedObjectCode = ReadString(row, objectCodeColumn) ?? string.Empty;
             if (string.IsNullOrWhiteSpace(relatedObjectCode)) continue;
             if (string.IsNullOrWhiteSpace(relatedObjectCode)) continue;
 
 
-            var sourceObjectId = string.IsNullOrWhiteSpace(parameters.ObjectIdField)
-                ? relatedObjectCode
-                : ReadString(row, parameters.ObjectIdField!) ?? relatedObjectCode;
+            var sourceObjectId = ReadString(row, objectIdColumn) ?? relatedObjectCode;
 
 
             var dedupKey = BuildDedupKey(tenantId, factoryId, rule.RuleCode, sourceObjectType, sourceObjectId);
             var dedupKey = BuildDedupKey(tenantId, factoryId, rule.RuleCode, sourceObjectType, sourceObjectId);
 
 
@@ -161,6 +170,17 @@ public class S8TimeoutRuleEvaluator : IS8RuleEvaluator, ITransient
         return JsonSerializer.Serialize(payload);
         return JsonSerializer.Serialize(payload);
     }
     }
 
 
+    /// <summary>
+    /// 结果列名解析:BuildExpression 已把结果列统一别名为 canonical(due_at/status/source_object_id/
+    /// related_object_code)。优先返回 canonical 列名;仅当结果集不含 canonical 列时,回退到 params_json
+    /// 指定的真实列名(兼容历史未别名规则)。仅在 canonical 与 params 字段之间二选一,不新增无依据兜底字段。
+    /// </summary>
+    private static string ResolveResultColumn(DataTable table, string canonicalColumn, string? paramsColumn)
+    {
+        if (table.Columns.Contains(canonicalColumn)) return canonicalColumn;
+        return string.IsNullOrWhiteSpace(paramsColumn) ? canonicalColumn : paramsColumn!;
+    }
+
     private static string? ReadString(DataRow row, string columnName)
     private static string? ReadString(DataRow row, string columnName)
     {
     {
         if (string.IsNullOrWhiteSpace(columnName)) return null;
         if (string.IsNullOrWhiteSpace(columnName)) return null;