diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs
index 36e50faa..92eb4370 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs
@@ -34,13 +34,23 @@ public record SetInstanceAreaCommand(int InstanceId, int? AreaId);
/// Sets (or upserts) a per-instance alarm override. For HiLo trigger types the
/// TriggerConfigurationOverride JSON is merged setpoint-by-setpoint with
/// the inherited config; for binary trigger types it replaces the whole config.
-/// Either field is optional — pass null to leave it unchanged.
+/// Each override field is optional — null means the instance inherits the template value
+/// for that field (the row is replaced whole on every call, not merged).
///
+/// ID of the instance the override applies to.
+/// Canonical (path-qualified for composed members) alarm name.
+/// Trigger configuration JSON override, or null.
+/// Priority level override, or null.
+///
+/// Per-instance override of the template alarm's MES-reportable flag. Additive trailing
+/// optional; null (the default) inherits the template value, true/false forces it.
+///
public record SetInstanceAlarmOverrideCommand(
int InstanceId,
string AlarmCanonicalName,
string? TriggerConfigurationOverride,
- int? PriorityLevelOverride);
+ int? PriorityLevelOverride,
+ bool? IsMesReportableOverride = null);
public record DeleteInstanceAlarmOverrideCommand(
int InstanceId,
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/TemplateCommands.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/TemplateCommands.cs
index 35178d9b..ba6f1774 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/TemplateCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/TemplateCommands.cs
@@ -42,8 +42,11 @@ public record ResyncInheritedMembersCommand(int TemplateId);
public record AddTemplateAttributeCommand(int TemplateId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked, string? ElementDataType = null);
public record UpdateTemplateAttributeCommand(int AttributeId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked, string? ElementDataType = null);
public record DeleteTemplateAttributeCommand(int AttributeId);
-public record AddTemplateAlarmCommand(int TemplateId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
-public record UpdateTemplateAlarmCommand(int AlarmId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
+// IsMesReportable is an additive trailing optional (default false = "not reported to MES"):
+// it mirrors TemplateAlarm.IsMesReportable and marks the alarm for inclusion in the MES
+// alarm-status feed. Additive-only: never reorder the existing parameters.
+public record AddTemplateAlarmCommand(int TemplateId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked, bool IsMesReportable = false);
+public record UpdateTemplateAlarmCommand(int AlarmId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked, bool IsMesReportable = false);
public record DeleteTemplateAlarmCommand(int AlarmId);
// Native alarm source bindings (read-only mirror of an OPC UA A&C / MxGateway source's alarms)
public record AddTemplateNativeAlarmSourceCommand(int TemplateId, string Name, string ConnectionName, string SourceReference, string? ConditionFilter, string? Description, bool IsLocked);
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
index 0752be61..cbb28e01 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
@@ -1175,7 +1175,7 @@ public class ManagementActor : ReceiveActor
var result = await svc.SetAlarmOverrideAsync(
cmd.InstanceId, cmd.AlarmCanonicalName,
cmd.TriggerConfigurationOverride, cmd.PriorityLevelOverride,
- user.Username);
+ user.Username, cmd.IsMesReportableOverride);
return result.IsSuccess
? result.Value
: throw new ManagementCommandException(result.Error);
@@ -3179,7 +3179,8 @@ public class ManagementActor : ReceiveActor
PriorityLevel = cmd.PriorityLevel,
Description = cmd.Description,
TriggerConfiguration = cmd.TriggerConfiguration,
- IsLocked = cmd.IsLocked
+ IsLocked = cmd.IsLocked,
+ IsMesReportable = cmd.IsMesReportable
};
var result = await svc.AddAlarmAsync(cmd.TemplateId, alarm, user);
return result.IsSuccess ? result.Value : throw new ManagementCommandException(result.Error);
@@ -3194,7 +3195,8 @@ public class ManagementActor : ReceiveActor
PriorityLevel = cmd.PriorityLevel,
Description = cmd.Description,
TriggerConfiguration = cmd.TriggerConfiguration,
- IsLocked = cmd.IsLocked
+ IsLocked = cmd.IsLocked,
+ IsMesReportable = cmd.IsMesReportable
};
var result = await svc.UpdateAlarmAsync(cmd.AlarmId, alarm, user);
return result.IsSuccess ? result.Value : throw new ManagementCommandException(result.Error);
diff --git a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs
index 232132b9..779a5cce 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs
@@ -1014,6 +1014,112 @@ public class ManagementActorTests : TestKit, IDisposable
Assert.Contains("Valid values:", response.Error);
}
+ [Fact]
+ public void AddTemplateAlarm_CarriesIsMesReportable()
+ {
+ // The MES-reportable flag rides AddTemplateAlarmCommand as a trailing
+ // optional (additive-only wire contract) and must reach the TemplateAlarm
+ // the handler hands to TemplateService — verified at the repository
+ // boundary, which is where the entity is persisted.
+ var template = new Template("T1") { Id = 1 };
+ _templateRepo.GetTemplateByIdAsync(1, Arg.Any()).Returns(template);
+ _templateRepo.GetAllTemplatesForAnalysisAsync(Arg.Any())
+ .Returns(new List { template });
+ _templateRepo.GetAllTemplatesAsync(Arg.Any())
+ .Returns(new List { template });
+
+ TemplateAlarm? added = null;
+ _templateRepo
+ .When(r => r.AddTemplateAlarmAsync(Arg.Any(), Arg.Any()))
+ .Do(ci => added = ci.Arg());
+ _services.AddScoped();
+
+ var actor = CreateActor();
+ var envelope = Envelope(
+ new AddTemplateAlarmCommand(1, "HighTemp", "HiLo", 500, null, null, false, IsMesReportable: true),
+ "Designer");
+
+ actor.Tell(envelope);
+
+ ExpectMsg(TimeSpan.FromSeconds(5));
+ Assert.NotNull(added);
+ Assert.True(added!.IsMesReportable);
+ }
+
+ [Fact]
+ public void UpdateTemplateAlarm_CarriesIsMesReportable()
+ {
+ var template = new Template("T1") { Id = 1 };
+ var existing = new TemplateAlarm("HighTemp")
+ {
+ Id = 5,
+ TemplateId = 1,
+ TriggerType = AlarmTriggerType.HiLo,
+ PriorityLevel = 100,
+ IsMesReportable = false
+ };
+ _templateRepo.GetTemplateAlarmByIdAsync(5, Arg.Any()).Returns(existing);
+ _templateRepo.GetTemplateByIdAsync(1, Arg.Any()).Returns(template);
+ _templateRepo.GetAllTemplatesAsync(Arg.Any())
+ .Returns(new List { template });
+
+ TemplateAlarm? updated = null;
+ _templateRepo
+ .When(r => r.UpdateTemplateAlarmAsync(Arg.Any(), Arg.Any()))
+ .Do(ci => updated = ci.Arg());
+ _services.AddScoped();
+
+ var actor = CreateActor();
+ var envelope = Envelope(
+ new UpdateTemplateAlarmCommand(5, "HighTemp", "HiLo", 500, null, null, false, IsMesReportable: true),
+ "Designer");
+
+ actor.Tell(envelope);
+
+ ExpectMsg(TimeSpan.FromSeconds(5));
+ Assert.NotNull(updated);
+ Assert.True(updated!.IsMesReportable);
+ }
+
+ [Fact]
+ public void SetInstanceAlarmOverride_PassesIsMesReportableOverride()
+ {
+ // false is the interesting case: it is distinct from the null "inherit"
+ // default, so a dropped argument would read as "inherit" rather than
+ // "explicitly not reportable".
+ var template = new Template("T1") { Id = 1 };
+ template.Alarms.Add(new TemplateAlarm("HighTemp")
+ {
+ Id = 5,
+ TemplateId = 1,
+ TriggerType = AlarmTriggerType.HiLo,
+ PriorityLevel = 100
+ });
+ var instance = new Instance("Inst1") { Id = 10, TemplateId = 1, SiteId = 1 };
+
+ _templateRepo.GetInstanceByIdAsync(10, Arg.Any()).Returns(instance);
+ _templateRepo.GetAllTemplatesAsync(Arg.Any())
+ .Returns(new List { template });
+ _templateRepo.GetAlarmOverrideAsync(10, "HighTemp", Arg.Any())
+ .Returns((InstanceAlarmOverride?)null);
+
+ InstanceAlarmOverride? persisted = null;
+ _templateRepo
+ .When(r => r.AddInstanceAlarmOverrideAsync(Arg.Any(), Arg.Any()))
+ .Do(ci => persisted = ci.Arg());
+
+ var actor = CreateActor();
+ var envelope = Envelope(
+ new SetInstanceAlarmOverrideCommand(10, "HighTemp", null, null, IsMesReportableOverride: false),
+ "Deployer");
+
+ actor.Tell(envelope);
+
+ ExpectMsg(TimeSpan.FromSeconds(5));
+ Assert.NotNull(persisted);
+ Assert.False(persisted!.IsMesReportableOverride);
+ }
+
[Fact]
public void UpdateAttribute_ValidScalar_PersistsValueAndKeepsFixedFields()
{