feat(template-engine): persist IsMesReportable on alarm update; IsMesReportableOverride on SetAlarmOverrideAsync
Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
This commit is contained in:
@@ -213,6 +213,7 @@ public class InstanceService
|
|||||||
/// <param name="triggerConfigurationOverride">Override JSON for the trigger configuration.</param>
|
/// <param name="triggerConfigurationOverride">Override JSON for the trigger configuration.</param>
|
||||||
/// <param name="priorityLevelOverride">Override priority level, or null to use template value.</param>
|
/// <param name="priorityLevelOverride">Override priority level, or null to use template value.</param>
|
||||||
/// <param name="user">Username of the user setting the override.</param>
|
/// <param name="user">Username of the user setting the override.</param>
|
||||||
|
/// <param name="isMesReportableOverride">Override for the alarm's MES-reportable flag, or null to use the template value.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>Result containing the alarm override or failure message.</returns>
|
/// <returns>Result containing the alarm override or failure message.</returns>
|
||||||
public async Task<Result<InstanceAlarmOverride>> SetAlarmOverrideAsync(
|
public async Task<Result<InstanceAlarmOverride>> SetAlarmOverrideAsync(
|
||||||
@@ -221,6 +222,7 @@ public class InstanceService
|
|||||||
string? triggerConfigurationOverride,
|
string? triggerConfigurationOverride,
|
||||||
int? priorityLevelOverride,
|
int? priorityLevelOverride,
|
||||||
string user,
|
string user,
|
||||||
|
bool? isMesReportableOverride = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var instance = await _repository.GetInstanceByIdAsync(instanceId, cancellationToken);
|
var instance = await _repository.GetInstanceByIdAsync(instanceId, cancellationToken);
|
||||||
@@ -254,6 +256,7 @@ public class InstanceService
|
|||||||
{
|
{
|
||||||
existingOverride.TriggerConfigurationOverride = triggerConfigurationOverride;
|
existingOverride.TriggerConfigurationOverride = triggerConfigurationOverride;
|
||||||
existingOverride.PriorityLevelOverride = priorityLevelOverride;
|
existingOverride.PriorityLevelOverride = priorityLevelOverride;
|
||||||
|
existingOverride.IsMesReportableOverride = isMesReportableOverride;
|
||||||
await _repository.UpdateInstanceAlarmOverrideAsync(existingOverride, cancellationToken);
|
await _repository.UpdateInstanceAlarmOverrideAsync(existingOverride, cancellationToken);
|
||||||
await _repository.SaveChangesAsync(cancellationToken);
|
await _repository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -268,7 +271,8 @@ public class InstanceService
|
|||||||
{
|
{
|
||||||
InstanceId = instanceId,
|
InstanceId = instanceId,
|
||||||
TriggerConfigurationOverride = triggerConfigurationOverride,
|
TriggerConfigurationOverride = triggerConfigurationOverride,
|
||||||
PriorityLevelOverride = priorityLevelOverride
|
PriorityLevelOverride = priorityLevelOverride,
|
||||||
|
IsMesReportableOverride = isMesReportableOverride
|
||||||
};
|
};
|
||||||
|
|
||||||
await _repository.AddInstanceAlarmOverrideAsync(newOverride, cancellationToken);
|
await _repository.AddInstanceAlarmOverrideAsync(newOverride, cancellationToken);
|
||||||
|
|||||||
@@ -621,6 +621,7 @@ public class TemplateService
|
|||||||
existing.Description = proposed.Description;
|
existing.Description = proposed.Description;
|
||||||
existing.OnTriggerScriptId = proposed.OnTriggerScriptId;
|
existing.OnTriggerScriptId = proposed.OnTriggerScriptId;
|
||||||
existing.IsLocked = proposed.IsLocked;
|
existing.IsLocked = proposed.IsLocked;
|
||||||
|
existing.IsMesReportable = proposed.IsMesReportable;
|
||||||
if (template?.IsDerived == true)
|
if (template?.IsDerived == true)
|
||||||
existing.IsInherited = proposed.IsInherited;
|
existing.IsInherited = proposed.IsInherited;
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -220,6 +220,101 @@ public class InstanceServiceTests
|
|||||||
Assert.Contains("locked", result.Error, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("locked", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetAlarmOverrideAsync_StoresIsMesReportableOverride()
|
||||||
|
{
|
||||||
|
var instance = new Instance("Inst1") { Id = 1, TemplateId = 1 };
|
||||||
|
_repoMock.Setup(r => r.GetInstanceByIdAsync(1, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(instance);
|
||||||
|
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<Template>
|
||||||
|
{
|
||||||
|
TemplateWithAlarms(1, new TemplateAlarm("HighTemp") { Id = 10, IsLocked = false })
|
||||||
|
});
|
||||||
|
_repoMock.Setup(r => r.GetAlarmOverrideAsync(1, "HighTemp", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((InstanceAlarmOverride?)null);
|
||||||
|
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(1);
|
||||||
|
|
||||||
|
InstanceAlarmOverride? added = null;
|
||||||
|
_repoMock.Setup(r => r.AddInstanceAlarmOverrideAsync(
|
||||||
|
It.IsAny<InstanceAlarmOverride>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Callback<InstanceAlarmOverride, CancellationToken>((o, _) => added = o);
|
||||||
|
|
||||||
|
var result = await _sut.SetAlarmOverrideAsync(
|
||||||
|
1, "HighTemp", "{}", null, "admin", isMesReportableOverride: true);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(added);
|
||||||
|
Assert.True(added!.IsMesReportableOverride);
|
||||||
|
Assert.True(result.Value.IsMesReportableOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetAlarmOverrideAsync_NullIsMesReportableOverride_ClearsExistingOverride()
|
||||||
|
{
|
||||||
|
var instance = new Instance("Inst1") { Id = 1, TemplateId = 1 };
|
||||||
|
_repoMock.Setup(r => r.GetInstanceByIdAsync(1, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(instance);
|
||||||
|
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<Template>
|
||||||
|
{
|
||||||
|
TemplateWithAlarms(1, new TemplateAlarm("HighTemp") { Id = 10, IsLocked = false })
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingOverride = new InstanceAlarmOverride("HighTemp")
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
InstanceId = 1,
|
||||||
|
IsMesReportableOverride = true
|
||||||
|
};
|
||||||
|
_repoMock.Setup(r => r.GetAlarmOverrideAsync(1, "HighTemp", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(existingOverride);
|
||||||
|
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(1);
|
||||||
|
|
||||||
|
var result = await _sut.SetAlarmOverrideAsync(
|
||||||
|
1, "HighTemp", "{}", null, "admin", isMesReportableOverride: null);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Null(existingOverride.IsMesReportableOverride);
|
||||||
|
Assert.Null(result.Value.IsMesReportableOverride);
|
||||||
|
_repoMock.Verify(r => r.UpdateInstanceAlarmOverrideAsync(
|
||||||
|
It.Is<InstanceAlarmOverride>(o => o.IsMesReportableOverride == null),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetAlarmOverrideAsync_UpdatesIsMesReportableOverrideOnExisting()
|
||||||
|
{
|
||||||
|
var instance = new Instance("Inst1") { Id = 1, TemplateId = 1 };
|
||||||
|
_repoMock.Setup(r => r.GetInstanceByIdAsync(1, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(instance);
|
||||||
|
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<Template>
|
||||||
|
{
|
||||||
|
TemplateWithAlarms(1, new TemplateAlarm("HighTemp") { Id = 10, IsLocked = false })
|
||||||
|
});
|
||||||
|
|
||||||
|
var existingOverride = new InstanceAlarmOverride("HighTemp")
|
||||||
|
{
|
||||||
|
Id = 5,
|
||||||
|
InstanceId = 1,
|
||||||
|
IsMesReportableOverride = null
|
||||||
|
};
|
||||||
|
_repoMock.Setup(r => r.GetAlarmOverrideAsync(1, "HighTemp", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(existingOverride);
|
||||||
|
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(1);
|
||||||
|
|
||||||
|
var result = await _sut.SetAlarmOverrideAsync(
|
||||||
|
1, "HighTemp", "{}", 3, "admin", isMesReportableOverride: false);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.False(existingOverride.IsMesReportableOverride);
|
||||||
|
Assert.Equal(3, existingOverride.PriorityLevelOverride);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Enable_ExistingInstance_SetsEnabled()
|
public async Task Enable_ExistingInstance_SetsEnabled()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -530,6 +530,65 @@ public class TemplateServiceTests
|
|||||||
Assert.Contains("TriggerType", result.Error);
|
Assert.Contains("TriggerType", result.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAlarmAsync_CopiesIsMesReportable()
|
||||||
|
{
|
||||||
|
var existing = new TemplateAlarm("HighTemp")
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
TemplateId = 1,
|
||||||
|
TriggerType = AlarmTriggerType.ValueMatch,
|
||||||
|
PriorityLevel = 500,
|
||||||
|
IsMesReportable = false
|
||||||
|
};
|
||||||
|
_repoMock.Setup(r => r.GetTemplateAlarmByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(existing);
|
||||||
|
|
||||||
|
var template = new Template("Pump") { Id = 1 };
|
||||||
|
_repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(template);
|
||||||
|
|
||||||
|
var proposed = new TemplateAlarm("HighTemp")
|
||||||
|
{
|
||||||
|
TriggerType = AlarmTriggerType.ValueMatch,
|
||||||
|
PriorityLevel = 500,
|
||||||
|
IsMesReportable = true
|
||||||
|
};
|
||||||
|
var result = await _service.UpdateAlarmAsync(1, proposed, "admin");
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value.IsMesReportable);
|
||||||
|
Assert.True(existing.IsMesReportable);
|
||||||
|
_repoMock.Verify(r => r.UpdateTemplateAlarmAsync(
|
||||||
|
It.Is<TemplateAlarm>(a => a.IsMesReportable), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAlarmAsync_ClearsIsMesReportable()
|
||||||
|
{
|
||||||
|
var existing = new TemplateAlarm("HighTemp")
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
TemplateId = 1,
|
||||||
|
TriggerType = AlarmTriggerType.ValueMatch,
|
||||||
|
PriorityLevel = 500,
|
||||||
|
IsMesReportable = true
|
||||||
|
};
|
||||||
|
_repoMock.Setup(r => r.GetTemplateAlarmByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(existing);
|
||||||
|
|
||||||
|
var template = new Template("Pump") { Id = 1 };
|
||||||
|
_repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(template);
|
||||||
|
|
||||||
|
var proposed = new TemplateAlarm("HighTemp")
|
||||||
|
{
|
||||||
|
TriggerType = AlarmTriggerType.ValueMatch,
|
||||||
|
PriorityLevel = 500,
|
||||||
|
IsMesReportable = false
|
||||||
|
};
|
||||||
|
var result = await _service.UpdateAlarmAsync(1, proposed, "admin");
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.False(result.Value.IsMesReportable);
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// WP-4: Script Definitions
|
// WP-4: Script Definitions
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user