|
|
@@ -29,6 +29,40 @@ public class S8ManualReportService : ITransient
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// S8-EXCEPTION-CREATION-MODULE-CODE-FIX-1:建单 module_code 派生统一入口。
|
|
|
+ /// 优先级:显式 module → hit module → 单模块 sceneCode(S1-S7)→ exception_type.scene_code。
|
|
|
+ /// 严格 S1-S7 校验,不接受 legacy 复合 scene;最终无法确定时返回 null(caller 决定记日志或拒绝)。
|
|
|
+ /// </summary>
|
|
|
+ private async Task<string?> ResolveModuleCodeAsync(
|
|
|
+ string? explicitModuleCode,
|
|
|
+ string? hitModuleCode,
|
|
|
+ string? sceneCode,
|
|
|
+ string? exceptionTypeCode)
|
|
|
+ {
|
|
|
+ var byExplicit = S8ModuleCode.Normalize(explicitModuleCode);
|
|
|
+ if (byExplicit != null) return byExplicit;
|
|
|
+
|
|
|
+ var byHit = S8ModuleCode.Normalize(hitModuleCode);
|
|
|
+ if (byHit != null) return byHit;
|
|
|
+
|
|
|
+ var byScene = S8ModuleCode.FromCanonicalScene(sceneCode);
|
|
|
+ if (byScene != null) return byScene;
|
|
|
+
|
|
|
+ if (!string.IsNullOrWhiteSpace(exceptionTypeCode))
|
|
|
+ {
|
|
|
+ var typeScene = await _typeRep.AsQueryable().ClearFilter()
|
|
|
+ .Where(t => t.TypeCode == exceptionTypeCode && t.Enabled)
|
|
|
+ .OrderByDescending(t => t.FactoryId)
|
|
|
+ .Select(t => t.SceneCode)
|
|
|
+ .FirstAsync();
|
|
|
+ var byType = S8ModuleCode.FromCanonicalScene(typeScene);
|
|
|
+ if (byType != null) return byType;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
private readonly SqlSugarRepository<AdoS8Exception> _rep;
|
|
|
private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
|
|
|
private readonly SqlSugarRepository<AdoS8Evidence> _evidenceRep;
|
|
|
@@ -164,6 +198,20 @@ public class S8ManualReportService : ITransient
|
|
|
// 主动提报无前端 type 字段,按场景兜底推断;保证不进"未分类"桶。
|
|
|
var inferredType = await InferExceptionTypeCodeAsync(dto.TenantId, dto.FactoryId, dto.SceneCode.Trim());
|
|
|
|
|
|
+ // S8-EXCEPTION-CREATION-MODULE-CODE-FIX-1:手工提报 module_code 严格按 S1-S7 派生;
|
|
|
+ // dto 暂不携带显式 module_code,按 scene → exception_type.scene 链路降级。
|
|
|
+ var resolvedModule = await ResolveModuleCodeAsync(
|
|
|
+ explicitModuleCode: null,
|
|
|
+ hitModuleCode: null,
|
|
|
+ sceneCode: dto.SceneCode.Trim(),
|
|
|
+ exceptionTypeCode: inferredType);
|
|
|
+ if (resolvedModule == null)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "manual_report_module_code_unresolved sceneCode={SceneCode} exceptionTypeCode={TypeCode} title={Title}",
|
|
|
+ dto.SceneCode, inferredType, dto.Title);
|
|
|
+ }
|
|
|
+
|
|
|
var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
|
|
|
var entity = new AdoS8Exception
|
|
|
{
|
|
|
@@ -182,8 +230,8 @@ public class S8ManualReportService : ITransient
|
|
|
ResponsibleDeptId = dto.ResponsibleDeptId,
|
|
|
ReporterId = currentUserId,
|
|
|
ExceptionTypeCode = inferredType,
|
|
|
- ModuleCode = S8ModuleCode.FromScene(dto.SceneCode.Trim()),
|
|
|
- ProcessNodeCode = ResolveProcessNodeCode(dto.SceneCode.Trim(), S8ModuleCode.FromScene(dto.SceneCode.Trim())),
|
|
|
+ ModuleCode = resolvedModule,
|
|
|
+ ProcessNodeCode = ResolveProcessNodeCode(dto.SceneCode.Trim(), resolvedModule),
|
|
|
CreatedAt = DateTime.Now,
|
|
|
IsDeleted = false
|
|
|
};
|
|
|
@@ -232,6 +280,19 @@ public class S8ManualReportService : ITransient
|
|
|
|
|
|
var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
|
|
|
var title = $"[自动] 设备 {hit.RelatedObjectCode} {hit.TriggerCondition} {hit.ThresholdValue}(当前 {hit.CurrentValue})";
|
|
|
+ // S8-EXCEPTION-CREATION-MODULE-CODE-FIX-1:固定 SceneCode=S2 + ExceptionTypeCode=EQUIP_FAULT,
|
|
|
+ // 派生链路通过 ResolveModuleCodeAsync 严格按 S1-S7 走(结果稳定为 S2,但消除 FromScene 的 legacy 兼容路径)。
|
|
|
+ var resolvedModule = await ResolveModuleCodeAsync(
|
|
|
+ explicitModuleCode: null,
|
|
|
+ hitModuleCode: null,
|
|
|
+ sceneCode: S8SceneCode.S2,
|
|
|
+ exceptionTypeCode: "EQUIP_FAULT");
|
|
|
+ if (resolvedModule == null)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "auto_watch_module_code_unresolved sceneCode={SceneCode} exceptionTypeCode={TypeCode} ruleId={RuleId}",
|
|
|
+ S8SceneCode.S2, "EQUIP_FAULT", hit.SourceRuleId);
|
|
|
+ }
|
|
|
var entity = new AdoS8Exception
|
|
|
{
|
|
|
// 租户/工厂:与 S8WatchSchedulerService.RunOnceAsync 当前固定上下文一致。
|
|
|
@@ -256,10 +317,9 @@ public class S8ManualReportService : ITransient
|
|
|
IsDeleted = false,
|
|
|
// G-01 首版唯一异常类型映射(baseline 已迁后 EQUIP_FAULT 属 S2 制造协同场景)。
|
|
|
ExceptionTypeCode = "EQUIP_FAULT",
|
|
|
- // S2 自映射 → S2。看板按 module_code 聚合,留空会导致模块卡空白。
|
|
|
- ModuleCode = S8ModuleCode.FromScene(S8SceneCode.S2),
|
|
|
+ ModuleCode = resolvedModule,
|
|
|
// S8-PROCESS-NODE-S1S7-ALIGN-1:process_node_code 与 module 对齐 S1-S7。
|
|
|
- ProcessNodeCode = ResolveProcessNodeCode(S8SceneCode.S2, S8ModuleCode.FromScene(S8SceneCode.S2)),
|
|
|
+ ProcessNodeCode = ResolveProcessNodeCode(S8SceneCode.S2, resolvedModule),
|
|
|
// 追溯三件套(自动建单必填口径)。
|
|
|
SourceRuleId = hit.SourceRuleId,
|
|
|
SourceDataSourceId = hit.DataSourceId,
|
|
|
@@ -301,6 +361,21 @@ public class S8ManualReportService : ITransient
|
|
|
if (hit.SourceRuleId <= 0 || string.IsNullOrWhiteSpace(hit.RelatedObjectCode))
|
|
|
throw new S8BizException("自动建单缺失追溯键");
|
|
|
|
|
|
+ var effectiveScene = string.IsNullOrWhiteSpace(hit.SceneCode) ? S8SceneCode.S2 : hit.SceneCode;
|
|
|
+ // S8-EXCEPTION-CREATION-MODULE-CODE-FIX-1:R2 自动建单 module_code 派生统一走严格 S1-S7 链路;
|
|
|
+ // hit.ModuleCode(evaluator 显式)→ rule/hit.SceneCode(当前 DB 100% S1-S7)→ exception_type.scene_code。
|
|
|
+ var resolvedModule = await ResolveModuleCodeAsync(
|
|
|
+ explicitModuleCode: null,
|
|
|
+ hitModuleCode: hit.ModuleCode,
|
|
|
+ sceneCode: effectiveScene,
|
|
|
+ exceptionTypeCode: hit.ExceptionTypeCode);
|
|
|
+ if (resolvedModule == null)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "auto_watch_hit_module_code_unresolved sceneCode={SceneCode} hitModule={HitModule} typeCode={TypeCode} ruleCode={RuleCode}",
|
|
|
+ hit.SceneCode, hit.ModuleCode, hit.ExceptionTypeCode, hit.SourceRuleCode);
|
|
|
+ }
|
|
|
+
|
|
|
var code = $"EX-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
|
|
|
var entity = new AdoS8Exception
|
|
|
{
|
|
|
@@ -312,7 +387,7 @@ public class S8ManualReportService : ITransient
|
|
|
? $"[自动] {hit.SourceObjectType} {hit.SourceObjectId}"
|
|
|
: hit.Title,
|
|
|
Description = null,
|
|
|
- SceneCode = string.IsNullOrWhiteSpace(hit.SceneCode) ? S8SceneCode.S2 : hit.SceneCode,
|
|
|
+ SceneCode = effectiveScene,
|
|
|
SourceType = "AUTO_WATCH",
|
|
|
Status = "NEW",
|
|
|
Severity = string.IsNullOrWhiteSpace(hit.Severity) ? "MEDIUM" : hit.Severity,
|
|
|
@@ -324,16 +399,9 @@ public class S8ManualReportService : ITransient
|
|
|
CreatedAt = DateTime.Now,
|
|
|
IsDeleted = false,
|
|
|
ExceptionTypeCode = hit.ExceptionTypeCode,
|
|
|
- // 优先使用 evaluator 显式提供的 module_code,否则按 hit.SceneCode 取代表模块。
|
|
|
- ModuleCode = !string.IsNullOrWhiteSpace(hit.ModuleCode)
|
|
|
- ? hit.ModuleCode
|
|
|
- : S8ModuleCode.FromScene(string.IsNullOrWhiteSpace(hit.SceneCode) ? S8SceneCode.S2 : hit.SceneCode),
|
|
|
+ ModuleCode = resolvedModule,
|
|
|
// S8-PROCESS-NODE-S1S7-ALIGN-1:process_node_code 与 module 对齐 S1-S7。
|
|
|
- ProcessNodeCode = ResolveProcessNodeCode(
|
|
|
- string.IsNullOrWhiteSpace(hit.SceneCode) ? S8SceneCode.S2 : hit.SceneCode,
|
|
|
- !string.IsNullOrWhiteSpace(hit.ModuleCode)
|
|
|
- ? hit.ModuleCode
|
|
|
- : S8ModuleCode.FromScene(string.IsNullOrWhiteSpace(hit.SceneCode) ? S8SceneCode.S2 : hit.SceneCode)),
|
|
|
+ ProcessNodeCode = ResolveProcessNodeCode(effectiveScene, resolvedModule),
|
|
|
SourceRuleId = hit.SourceRuleId,
|
|
|
SourceDataSourceId = hit.DataSourceId == 0 ? null : hit.DataSourceId,
|
|
|
SourcePayload = hit.SourcePayload,
|