diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/AliasTagEditDto.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/AliasTagEditDto.cs new file mode 100644 index 00000000..3115a57e --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/AliasTagEditDto.cs @@ -0,0 +1,15 @@ +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns; +using ZB.MOM.WW.OtOpcUa.Configuration.Enums; + +/// The editable state of a Galaxy alias tag, loaded for the alias edit modal. +/// is parsed out of the tag's TagConfig {"FullName":…}. +/// The tag's stable id (read-only on edit). +/// The tag name. +/// The bound Galaxy gateway driver id. +/// The OPC UA built-in type name. +/// The tag-level access baseline. +/// The Galaxy object reference parsed from TagConfig; empty string when absent. +/// The optimistic-concurrency token last read. +public sealed record AliasTagEditDto( + string TagId, string Name, string DriverInstanceId, string DataType, + TagAccessLevel AccessLevel, string FullName, byte[] RowVersion); diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs index f9934fa5..c47d84e6 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs @@ -175,6 +175,12 @@ public interface IUnsTreeService /// The tag's edit projection, or null when missing. Task LoadTagAsync(string tagId, CancellationToken ct = default); + /// Load a Galaxy alias tag for editing (FullName parsed from TagConfig). Null if not found. + /// The alias tag to load. + /// A token to cancel the load. + /// The alias tag's edit projection, or null when missing. + Task LoadAliasTagAsync(string tagId, CancellationToken ct = default); + /// /// Loads a single equipment-bound virtual tag projected for editing, or null if it no longer /// exists. Reads untracked and captures the current concurrency token for last-write-wins saves. diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs index 55d9a146..25512183 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs @@ -204,6 +204,16 @@ public sealed class UnsTreeService(IDbContextFactory dbF .FirstOrDefaultAsync(ct); } + /// + public async Task LoadAliasTagAsync(string tagId, CancellationToken ct = default) + { + await using var db = await dbFactory.CreateDbContextAsync(ct); + var t = await db.Tags.AsNoTracking().FirstOrDefaultAsync(x => x.TagId == tagId, ct); + if (t is null) return null; + return new AliasTagEditDto(t.TagId, t.Name, t.DriverInstanceId, t.DataType, t.AccessLevel, + ExtractTagConfigFullName(t.TagConfig) ?? string.Empty, t.RowVersion); + } + /// public async Task LoadVirtualTagAsync(string virtualTagId, CancellationToken ct = default) { @@ -975,6 +985,8 @@ public sealed class UnsTreeService(IDbContextFactory dbF entity.DataType = input.DataType; entity.AccessLevel = input.AccessLevel; entity.FolderPath = null; + entity.WriteIdempotent = false; + entity.PollGroupId = null; entity.TagConfig = BuildAliasTagConfig(input.FullName); try @@ -1397,6 +1409,9 @@ public sealed class UnsTreeService(IDbContextFactory dbF string? equipmentCluster, CancellationToken ct) { + if (string.IsNullOrWhiteSpace(driverInstanceId)) + return new UnsMutationResult(false, "An alias must be bound to a Galaxy gateway."); + var driver = await db.DriverInstances.FirstOrDefaultAsync(d => d.DriverInstanceId == driverInstanceId, ct); if (driver is null) { diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceAliasTagTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceAliasTagTests.cs index 37e88769..77924e98 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceAliasTagTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceAliasTagTests.cs @@ -288,6 +288,8 @@ public sealed class UnsTreeServiceAliasTagTests doc.RootElement.GetProperty("FullName").GetString().ShouldBe("TestMachine_020.Speed"); tag.TagConfig.ShouldContain("FullName"); tag.TagConfig.ShouldContain("TestMachine_020.Speed"); + tag.WriteIdempotent.ShouldBeFalse(); + tag.PollGroupId.ShouldBeNull(); } /// An empty/whitespace Galaxy reference is rejected before anything is written. @@ -364,6 +366,44 @@ public sealed class UnsTreeServiceAliasTagTests result.Error!.ShouldContain("speed-alias"); } + /// + /// returns a DTO whose FullName matches the + /// TagConfig and whose RowVersion is non-empty. + /// + [Fact] + public async Task LoadAliasTag_returns_dto_with_FullName() + { + var dbName = SeedCluster(); + + using (var db = UnsTreeTestDb.CreateNamed(dbName)) + { + db.Tags.Add(new Tag + { + TagId = "TAG-LOAD-ALIAS", + DriverInstanceId = GatewayDriverId, + EquipmentId = EquipmentId, + Name = "loaded-speed", + DataType = "Float", + AccessLevel = TagAccessLevel.ReadWrite, + TagConfig = "{\"FullName\":\"TestMachine_020.LoadedSpeed\"}", + }); + db.SaveChanges(); + } + + var service = new UnsTreeService(UnsTreeTestDb.Factory(dbName)); + + var dto = await service.LoadAliasTagAsync("TAG-LOAD-ALIAS"); + + dto.ShouldNotBeNull(); + dto!.TagId.ShouldBe("TAG-LOAD-ALIAS"); + dto.Name.ShouldBe("loaded-speed"); + dto.DriverInstanceId.ShouldBe(GatewayDriverId); + dto.DataType.ShouldBe("Float"); + dto.AccessLevel.ShouldBe(TagAccessLevel.ReadWrite); + dto.FullName.ShouldBe("TestMachine_020.LoadedSpeed"); + dto.RowVersion.ShouldNotBeNull(); + } + /// /// Update changes the alias's name, data type, access level, and Galaxy reference, returning Ok and /// persisting the new values and the refreshed {"FullName":…} TagConfig.