using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; using ZB.MOM.WW.ScadaBridge.Transport.Serialization; namespace ZB.MOM.WW.ScadaBridge.Transport.Import; /// /// The single source of truth for "has this template child changed?": the /// field-for-field equality between a persisted template-child entity and its bundle /// DTO. Every writable field is compared. /// /// BOTH the Preview diff () and the /// Overwrite child-sync (BundleImporter.SyncTemplate*Async) MUST route their /// change detection through these methods so a Preview can never disagree with what /// an Overwrite actually writes. Historically the two drifted — the diff omitted /// ElementDataType, ExecutionTimeoutSeconds, the alarm on-trigger /// script, and LockedInDerived — reporting Identical for artifacts an /// Overwrite would in fact mutate. Adding a writable field to any template-child /// entity means adding it here, once. /// /// internal static class TemplateChildEquality { /// /// True when the persisted attribute equals its bundle DTO across every writable /// field. The single source of truth for "attribute changed"; both ArtifactDiff /// and SyncTemplateAttributesAsync call it. /// /// Callers that normalise the DTO Value before comparing (the importer /// normalises List values) pass a DTO whose Value is already normalised. /// /// /// The persisted template attribute. /// The bundle DTO to compare against. /// true when every writable field matches. public static bool AttributesEqual(TemplateAttribute e, TemplateAttributeDto i) => e.Value == i.Value && e.DataType == i.DataType && e.ElementDataType == i.ElementDataType && e.IsLocked == i.IsLocked && e.LockedInDerived == i.LockedInDerived && e.Description == i.Description && e.DataSourceReference == i.DataSourceReference; /// /// True when the persisted alarm equals its bundle DTO across every writable /// field, including the on-trigger script binding. Bundles reference the /// on-trigger script by NAME (ids are environment-specific), so the caller /// supplies — a resolver over the alarm's own /// template scripts — to map the persisted OnTriggerScriptId FK back to a /// name for comparison. The single source of truth for "alarm changed"; both /// ArtifactDiff and SyncTemplateAlarmsAsync call it. /// /// The persisted template alarm. /// The bundle DTO to compare against. /// Resolver mapping an on-trigger script id to its name. /// true when every writable field matches. public static bool AlarmsEqual(TemplateAlarm e, TemplateAlarmDto i, Func scriptNameById) => e.Description == i.Description && e.PriorityLevel == i.PriorityLevel && e.TriggerType == i.TriggerType && e.TriggerConfiguration == i.TriggerConfiguration && e.IsLocked == i.IsLocked && e.LockedInDerived == i.LockedInDerived && scriptNameById(e.OnTriggerScriptId) == i.OnTriggerScriptName; /// /// True when the persisted script equals its bundle DTO across every writable /// field. The single source of truth for "script changed"; both ArtifactDiff /// (via DiffScriptChildren) and SyncTemplateScriptsAsync call it. /// /// The persisted template script. /// The bundle DTO to compare against. /// true when every writable field matches. public static bool ScriptsEqual(TemplateScript e, TemplateScriptDto i) => string.Equals(e.Code, i.Code, StringComparison.Ordinal) && e.TriggerType == i.TriggerType && e.TriggerConfiguration == i.TriggerConfiguration && e.ParameterDefinitions == i.ParameterDefinitions && e.ReturnDefinition == i.ReturnDefinition && e.IsLocked == i.IsLocked && e.LockedInDerived == i.LockedInDerived && e.MinTimeBetweenRuns == i.MinTimeBetweenRuns && e.ExecutionTimeoutSeconds == i.ExecutionTimeoutSeconds; /// /// True when the persisted native alarm source equals its bundle DTO across every /// writable field. The single source of truth for "native alarm source changed"; /// both ArtifactDiff and SyncTemplateNativeAlarmSourcesAsync call it. /// /// The persisted native alarm source. /// The bundle DTO to compare against. /// true when every writable field matches. public static bool NativeAlarmSourcesEqual(TemplateNativeAlarmSource e, TemplateNativeAlarmSourceDto i) => e.Description == i.Description && e.ConnectionName == i.ConnectionName && e.SourceReference == i.SourceReference && e.ConditionFilter == i.ConditionFilter && e.IsLocked == i.IsLocked && e.IsInherited == i.IsInherited && e.LockedInDerived == i.LockedInDerived; /// /// Builds a resolver mapping a template script id → its name over the given /// scripts, for the alarm on-trigger comparison. Returns null for a null id or an /// id absent from the set. /// /// The template scripts to index by id. /// A resolver mapping a script id to its name, or null when unresolved. public static Func ScriptNameResolver(IEnumerable scripts) { var byId = new Dictionary(); foreach (var s in scripts) { byId[s.Id] = s.Name; } return id => id is { } sid && byId.TryGetValue(sid, out var name) ? name : null; } }