fix(transport): transport template NativeAlarmSources — imports no longer amputate native alarm mirrors nor dangle instance overrides
New TemplateNativeAlarmSourceDto + init-only TemplateDto.NativeAlarmSources (empty default, additive — IsInherited placeholders carried for the collision detector). Exported in ToBundleContent, consumed in FromBundleContent + BuildTemplate, and a new SyncTemplateNativeAlarmSourcesAsync runs the Overwrite add/update/delete child sync with per-change audit rows. ArtifactDiff.CompareTemplate now DiffChildren over native sources so Preview reports a NativeAlarmSources field change. Proven by an end-to-end flatten test: an instance native-alarm-source override that dangled pre-fix now resolves against the re-imported template source. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -126,7 +126,39 @@ public sealed record TemplateDto(
|
||||
IReadOnlyList<TemplateAttributeDto> Attributes,
|
||||
IReadOnlyList<TemplateAlarmDto> Alarms,
|
||||
IReadOnlyList<TemplateScriptDto> Scripts,
|
||||
IReadOnlyList<TemplateCompositionDto> Compositions);
|
||||
IReadOnlyList<TemplateCompositionDto> Compositions)
|
||||
{
|
||||
// Template-defined native alarm source bindings. Modeled as an init-only
|
||||
// property with an empty default (NOT a positional ctor param) because an
|
||||
// IReadOnlyList has no valid compile-time default for a positional
|
||||
// parameter, and for the same forward/source-compat reasons as the
|
||||
// site/instance arrays on BundleContentDto:
|
||||
// 1. Forward-compat: a bundle written before native alarm sources were
|
||||
// transported has no NativeAlarmSources field and deserializes this to
|
||||
// Array.Empty<> — consumers always see an empty list, never null.
|
||||
// 2. Source-compat: every existing positional `new TemplateDto(...)` caller
|
||||
// keeps compiling; producers opt in via the object-initializer.
|
||||
// IsInherited placeholder rows ARE carried (the collision detector depends
|
||||
// on them), mirroring how the flattener treats inherited native sources.
|
||||
public IReadOnlyList<TemplateNativeAlarmSourceDto> NativeAlarmSources { get; init; } =
|
||||
Array.Empty<TemplateNativeAlarmSourceDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A template-defined native alarm source binding. Carries every writable field
|
||||
/// of the <c>TemplateNativeAlarmSource</c> entity, including the inheritance/lock
|
||||
/// flags, so an export→import round-trip is field-faithful and instance-level
|
||||
/// overrides of the source no longer dangle in the target environment.
|
||||
/// </summary>
|
||||
public sealed record TemplateNativeAlarmSourceDto(
|
||||
string Name,
|
||||
string? Description,
|
||||
string ConnectionName,
|
||||
string SourceReference,
|
||||
string? ConditionFilter,
|
||||
bool IsLocked,
|
||||
bool IsInherited,
|
||||
bool LockedInDerived);
|
||||
|
||||
public sealed record TemplateAttributeDto(
|
||||
string Name,
|
||||
|
||||
@@ -91,7 +91,20 @@ public sealed class EntitySerializer
|
||||
LockedInDerived: s.LockedInDerived)).ToList(),
|
||||
Compositions: t.Compositions.Select(c => new TemplateCompositionDto(
|
||||
InstanceName: c.InstanceName,
|
||||
ComposedTemplateName: templateNameById.TryGetValue(c.ComposedTemplateId, out var cn) ? cn : string.Empty)).ToList());
|
||||
ComposedTemplateName: templateNameById.TryGetValue(c.ComposedTemplateId, out var cn) ? cn : string.Empty)).ToList())
|
||||
{
|
||||
// Native alarm sources carry ALL rows including IsInherited
|
||||
// placeholders — the collision detector depends on them.
|
||||
NativeAlarmSources = t.NativeAlarmSources.Select(n => new TemplateNativeAlarmSourceDto(
|
||||
Name: n.Name,
|
||||
Description: n.Description,
|
||||
ConnectionName: n.ConnectionName,
|
||||
SourceReference: n.SourceReference,
|
||||
ConditionFilter: n.ConditionFilter,
|
||||
IsLocked: n.IsLocked,
|
||||
IsInherited: n.IsInherited,
|
||||
LockedInDerived: n.LockedInDerived)).ToList(),
|
||||
};
|
||||
}).ToList(),
|
||||
SharedScripts: aggregate.SharedScripts.Select(s => new SharedScriptDto(
|
||||
Name: s.Name,
|
||||
@@ -352,6 +365,20 @@ public sealed class EntitySerializer
|
||||
LockedInDerived = s.LockedInDerived,
|
||||
});
|
||||
}
|
||||
foreach (var n in dto.NativeAlarmSources)
|
||||
{
|
||||
t.NativeAlarmSources.Add(new TemplateNativeAlarmSource(n.Name)
|
||||
{
|
||||
TemplateId = t.Id,
|
||||
Description = n.Description,
|
||||
ConnectionName = n.ConnectionName,
|
||||
SourceReference = n.SourceReference,
|
||||
ConditionFilter = n.ConditionFilter,
|
||||
IsLocked = n.IsLocked,
|
||||
IsInherited = n.IsInherited,
|
||||
LockedInDerived = n.LockedInDerived,
|
||||
});
|
||||
}
|
||||
return t;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
Reference in New Issue
Block a user