fix(transport): export instance AreaName — Area-by-name reconciliation was half-shipped (exporter hardcoded null)

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-09 16:09:56 -04:00
parent 721a2e595f
commit f40f52950e
4 changed files with 213 additions and 4 deletions
@@ -41,6 +41,15 @@ public sealed record EntityAggregate(
// and never sees null; producers that resolve SMS config opt in via
// object-initializer.
public IReadOnlyList<SmsConfiguration> SmsConfigurations { get; init; } = Array.Empty<SmsConfiguration>();
// Area id → name lookup for the exported instances. Areas are not carried as a
// first-class bundle collection (they reconcile by NAME under the target site),
// so the aggregate only needs enough to resolve each instance's AreaId FK back to
// a portable name at serialization time. Init-only with an empty default: producers
// that export instances populate it; every other caller keeps compiling and the
// serializer emits AreaName: null when an instance's AreaId isn't in the map.
public IReadOnlyDictionary<int, string> AreaNameById { get; init; } =
new Dictionary<int, string>();
}
/// <summary>
@@ -248,10 +248,13 @@ public sealed class EntitySerializer
UniqueName: inst.UniqueName,
TemplateName: templateNameById.TryGetValue(inst.TemplateId, out var tn) ? tn : string.Empty,
SiteIdentifier: siteIdentifierBySiteId.TryGetValue(inst.SiteId, out var isid) ? isid : string.Empty,
// TODO: Areas don't travel in the bundle yet — EntityAggregate
// carries no Areas collection, so there's no id→name lookup. Emit
// null until area transport is added; the importer leaves AreaId null.
AreaName: null,
// Area travels by NAME — the importer resolves-or-creates the
// equivalent area under the target site (ResolveOrCreateAreaIdAsync).
// Resolve the instance's AreaId FK against the aggregate's id→name
// map; if the FK is null (no area) or unresolved (orphan/absent from
// the map), the name comes through as null and the importer leaves
// AreaId null on the imported instance.
AreaName: inst.AreaId is { } aid && aggregate.AreaNameById.TryGetValue(aid, out var an) ? an : null,
State: inst.State,
AttributeOverrides: inst.AttributeOverrides.Select(o => new InstanceAttributeOverrideDto(
AttributeName: o.AttributeName,