feat(native-alarms): per-binding IsMesReportable on native alarm source bindings (ScadaBridge#59)

TemplateNativeAlarmSource.IsMesReportable + tri-state InstanceNativeAlarmSourceOverride
.IsMesReportableOverride flatten onto ResolvedNativeAlarmSource; NativeAlarmActor stamps every
mirrored condition and InstanceActor the quiet-binding placeholder, so ScriptAlarm,
Alarms.MesReportableAsync() and the #60 wire fields carry it. Additive to the 900-999 severity band.
Migration AddNativeAlarmSourceMesReportable. Management commands, CLI --mes-reportable, Central UI
(template dialog checkbox, instance override select), Transport DTOs/importer/diff and the revision
hash (true-only, so existing hashes are unchanged) carry it.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 15:09:54 -05:00
parent 69a2017000
commit f98369a786
38 changed files with 2579 additions and 43 deletions
@@ -151,6 +151,7 @@ public class DiffService
a.ConnectionName == b.ConnectionName &&
a.SourceReference == b.SourceReference &&
a.ConditionFilter == b.ConditionFilter &&
a.IsMesReportable == b.IsMesReportable &&
a.IsLocked == b.IsLocked;
private static bool ScriptsEqual(ResolvedScript a, ResolvedScript b) =>
@@ -730,6 +730,7 @@ public class FlatteningService
ConnectionName = binding.ConnectionName,
SourceReference = binding.SourceReference,
ConditionFilter = binding.ConditionFilter,
IsMesReportable = binding.IsMesReportable,
Source = source,
IsLocked = binding.IsLocked || lockedNames.Contains(binding.Name)
};
@@ -832,6 +833,7 @@ public class FlatteningService
ConnectionName = ovr.ConnectionNameOverride ?? existing.ConnectionName,
SourceReference = ovr.SourceReferenceOverride ?? existing.SourceReference,
ConditionFilter = ovr.ConditionFilterOverride ?? existing.ConditionFilter,
IsMesReportable = ovr.IsMesReportableOverride ?? existing.IsMesReportable,
Source = "Override"
};
}
@@ -109,6 +109,8 @@ public class RevisionHashService
ConditionFilter = n.ConditionFilter,
ConnectionName = n.ConnectionName,
IsLocked = n.IsLocked,
// true-only (null when false) so existing revision hashes are unchanged.
IsMesReportable = n.IsMesReportable ? true : null,
SourceReference = n.SourceReference
})
.ToList()
@@ -296,6 +298,11 @@ public class RevisionHashService
/// </summary>
public bool IsLocked { get; init; }
/// <summary>
/// MES-reportable binding flag; null when false (the serializer drops nulls), so
/// pre-existing configurations hash identically.
/// </summary>
public bool? IsMesReportable { get; init; }
/// <summary>
/// The source reference (OPC UA SourceNode/notifier nodeId, or MxAccess object/area).
/// </summary>
public string SourceReference { get; init; } = string.Empty;
@@ -1176,6 +1176,7 @@ public class TemplateService
ConnectionName = src.ConnectionName,
SourceReference = src.SourceReference,
ConditionFilter = src.ConditionFilter,
IsMesReportable = src.IsMesReportable,
IsLocked = src.IsLocked,
IsInherited = true,
LockedInDerived = false,
@@ -1546,6 +1547,7 @@ public class TemplateService
ConnectionName = win.ConnectionName,
SourceReference = win.SourceReference,
ConditionFilter = win.ConditionFilter,
IsMesReportable = win.IsMesReportable,
IsLocked = win.IsLocked,
IsInherited = true,
LockedInDerived = false,
@@ -1556,12 +1558,14 @@ public class TemplateService
existing.ConnectionName != win.ConnectionName ||
existing.SourceReference != win.SourceReference ||
existing.ConditionFilter != win.ConditionFilter ||
existing.IsMesReportable != win.IsMesReportable ||
existing.Description != win.Description ||
existing.IsLocked != win.IsLocked))
{
existing.ConnectionName = win.ConnectionName;
existing.SourceReference = win.SourceReference;
existing.ConditionFilter = win.ConditionFilter;
existing.IsMesReportable = win.IsMesReportable;
existing.Description = win.Description;
existing.IsLocked = win.IsLocked;
await _repository.UpdateTemplateNativeAlarmSourceAsync(existing, ct);