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 0fd500e2..056d722d 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs @@ -1316,6 +1316,9 @@ public sealed class UnsTreeService(IDbContextFactory dbF if (await db.ScriptedAlarms.AnyAsync(a => a.ScriptedAlarmId == input.ScriptedAlarmId, ct)) return new UnsMutationResult(false, $"ScriptedAlarm '{input.ScriptedAlarmId}' already exists."); + if (await db.ScriptedAlarms.AnyAsync(a => a.EquipmentId == equipmentId && a.Name == input.Name, ct)) + return new UnsMutationResult(false, $"A scripted alarm named '{input.Name}' already exists on this equipment."); + db.ScriptedAlarms.Add(new ScriptedAlarm { ScriptedAlarmId = input.ScriptedAlarmId, @@ -1338,7 +1341,14 @@ public sealed class UnsTreeService(IDbContextFactory dbF { await using var db = await dbFactory.CreateDbContextAsync(ct); var entity = await db.ScriptedAlarms.FirstOrDefaultAsync(a => a.ScriptedAlarmId == scriptedAlarmId, ct); - if (entity is null) return new UnsMutationResult(false, "Row no longer exists."); + if (entity is null) + { + return new UnsMutationResult(false, "Row no longer exists."); + } + + if (await db.ScriptedAlarms.AnyAsync(a => a.EquipmentId == entity.EquipmentId && a.Name == input.Name && a.ScriptedAlarmId != scriptedAlarmId, ct)) + return new UnsMutationResult(false, $"A scripted alarm named '{input.Name}' already exists on this equipment."); + db.Entry(entity).Property(e => e.RowVersion).OriginalValue = rowVersion; entity.Name = input.Name; entity.AlarmType = input.AlarmType; @@ -1348,8 +1358,16 @@ public sealed class UnsTreeService(IDbContextFactory dbF entity.HistorizeToAveva = input.HistorizeToAveva; entity.Retain = input.Retain; entity.Enabled = input.Enabled; - try { await db.SaveChangesAsync(ct); return new UnsMutationResult(true, null); } - catch (DbUpdateConcurrencyException) { return new UnsMutationResult(false, "Another user changed this scripted alarm while you were editing."); } + + try + { + await db.SaveChangesAsync(ct); + return new UnsMutationResult(true, null); + } + catch (DbUpdateConcurrencyException) + { + return new UnsMutationResult(false, "Another user changed this scripted alarm while you were editing."); + } } /// @@ -1357,11 +1375,26 @@ public sealed class UnsTreeService(IDbContextFactory dbF { await using var db = await dbFactory.CreateDbContextAsync(ct); var entity = await db.ScriptedAlarms.FirstOrDefaultAsync(a => a.ScriptedAlarmId == scriptedAlarmId, ct); - if (entity is null) return new UnsMutationResult(true, null); + if (entity is null) + { + return new UnsMutationResult(true, null); + } + db.Entry(entity).Property(e => e.RowVersion).OriginalValue = rowVersion; db.ScriptedAlarms.Remove(entity); - try { await db.SaveChangesAsync(ct); return new UnsMutationResult(true, null); } - catch (DbUpdateConcurrencyException) { return new UnsMutationResult(false, "Another user changed this alarm while you were viewing it."); } - catch (Exception ex) { return new UnsMutationResult(false, $"Delete failed: {ex.Message}."); } + + try + { + await db.SaveChangesAsync(ct); + return new UnsMutationResult(true, null); + } + catch (DbUpdateConcurrencyException) + { + return new UnsMutationResult(false, "Another user changed this alarm while you were viewing it."); + } + catch (Exception ex) + { + return new UnsMutationResult(false, $"Delete failed: {ex.Message}."); + } } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceScriptedAlarmTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceScriptedAlarmTests.cs index 5547626d..e3c497ab 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceScriptedAlarmTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceScriptedAlarmTests.cs @@ -74,4 +74,16 @@ public sealed class UnsTreeServiceScriptedAlarmTests (await svc.DeleteScriptedAlarmAsync("SA-1", dto!.RowVersion)).Ok.ShouldBeTrue(); (await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId)).ShouldBeEmpty(); } + + [Fact] + public async Task Create_rejects_duplicate_name_on_same_equipment() + { + var svc = SeededService(); + (await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-1"))).Ok.ShouldBeTrue(); + // Same Name ("Over-temp" from Sample), different id, same equipment → rejected. + var dup = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-2")); + dup.Ok.ShouldBeFalse(); + dup.Error.ShouldNotBeNull(); + dup.Error!.ShouldContain("already exists"); + } }