namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; /// /// Which authoring surface a proposed effective name comes from. Used to word the collision /// error and to exclude the correct same-kind self-row on an edit (rename / override change). /// public enum EffectiveNameSourceKind { /// A (effective name = /// DisplayNameOverride else the backing raw tag's Name). Reference, /// An equipment-bound (effective name = Name). VirtualTag, /// An equipment-bound (effective name = Name). ScriptedAlarm, } /// /// v3 Batch 3 authoring-time guard for the UNS effective-name uniqueness rule. Within an /// equipment the EFFECTIVE leaf name must be unique across its UnsTagReferences /// (DisplayNameOverride else the backing raw tag's Name), its VirtualTags /// (Name), and its ScriptedAlarms (Name) — the three share the equipment's /// UNS NodeId space ({EquipmentId}/{EffectiveName}). This is the authoring mirror of the /// deploy-time DraftValidator UnsEffectiveNameCollision rule; comparison is /// to match NodeId semantics and the validator. /// /// /// Injectable (registered Scoped, its own pooled context per call — the same pattern /// UnsTreeService uses). Consumed by UnsTreeService's reference / virtual-tag / /// scripted-alarm mutations, which call before persisting and surface a /// non-null result as the mutation's readable failure. The deploy gate remains the backstop for /// rename-induced collisions the authoring check never saw. /// public interface IEffectiveNameGuard { /// /// Tests whether would collide with an existing /// effective name within . /// /// The owning equipment's logical id. /// The effective name to be authored (override else raw name for a reference; Name for a VT/alarm). /// Which surface the proposed name is for (words the error; identifies the self-row kind to exclude). /// /// On an edit, the logical id of the row being changed (its UnsTagReferenceId / /// VirtualTagId / ScriptedAlarmId) so it is excluded from the collision set; /// on a create. /// /// Cancellation token. /// /// when the name is free; otherwise a readable error naming the /// colliding existing source and the equipment. /// Task CheckAsync( string equipmentId, string proposedEffectiveName, EffectiveNameSourceKind kind, string? excludeSourceId, CancellationToken ct = default); /// /// Checks many proposed names against ONE load of the equipment's name space. Returns the first /// collision message (in input order), or when all are free. /// /// /// The bulk-authoring entry point (a select-all "add references" over N raw tags), where the /// per-name would cost N contexts × 4 queries. There is no /// excludeSourceId: a batch is a CREATE of N new rows, so no self-row exists to exclude. /// The default implementation simply loops — correct but not fast — so /// a stub / no-op guard needs no change; EffectiveNameGuard overrides it with the /// single-load version. /// /// The owning equipment's logical id. /// The effective names to be authored, in the order the caller wants them reported. /// Which surface the proposed names are for (words the error; identifies the self-row kind). /// Cancellation token. /// when every name is free; otherwise the first name's readable error. async Task CheckManyAsync( string equipmentId, IReadOnlyList proposedEffectiveNames, EffectiveNameSourceKind kind, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(proposedEffectiveNames); foreach (var proposed in proposedEffectiveNames) { var collision = await CheckAsync(equipmentId, proposed, kind, excludeSourceId: null, ct); if (collision is not null) return collision; } return null; } }