mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 10:43:13 +00:00
Reactive/Emergency work orders with a SEV 1-5 level are timed from their
creation against the SEV Respond deadline (2/4/8/24/72 hours, one backend
table). From 50% they are at risk: a dismissable High row in the "SLA at
Risk" section and an entry in the feed's slaAtRisk set with the server
clock (start, deadline, percent) for the banner and toast. From 100% they
are a Critical acknowledge row that only acknowledging removes.
POST /api/notifications/sla/{id}/acknowledge records who and when as a
work-order audit entry ("SLA breach acknowledged by <name>"), scoped to the
caller's feed audience: 404 outside it, 409 before the deadline, 204 when
recorded or already recorded. A later severity change is a new breach.
76 lines
3.4 KiB
C#
76 lines
3.4 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.DataServices.Models;
|
|
using SeaHaven.Services.Exceptions;
|
|
using SeaHaven.Services.Helpers;
|
|
using SeaHaven.Services.Interfaces;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class SlaBreachAcknowledgementService : ISlaBreachAcknowledgementService
|
|
{
|
|
private readonly INotificationFeedDataService _feedData;
|
|
private readonly IWorkOrderAuditDataService _auditData;
|
|
private readonly IUserDataService _userData;
|
|
private readonly IWorkOrderAccountResolver _accountResolver;
|
|
private readonly TimeProvider _timeProvider;
|
|
|
|
public SlaBreachAcknowledgementService(
|
|
INotificationFeedDataService feedData,
|
|
IWorkOrderAuditDataService auditData,
|
|
IUserDataService userData,
|
|
IWorkOrderAccountResolver accountResolver,
|
|
TimeProvider timeProvider)
|
|
{
|
|
_feedData = feedData;
|
|
_auditData = auditData;
|
|
_userData = userData;
|
|
_accountResolver = accountResolver;
|
|
_timeProvider = timeProvider;
|
|
}
|
|
|
|
public async Task<SlaBreachAcknowledgementOutcome> AcknowledgeAsync(
|
|
ClaimsPrincipal user, int workOrderId, CancellationToken cancellationToken)
|
|
{
|
|
// The same audience as the feed: a row the caller cannot see cannot be acknowledged.
|
|
var accountId = _accountResolver.ResolveAccountFilter(user);
|
|
var (dispatcherId, _) = NotificationAudience.Resolve(user);
|
|
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
throw new WorkOrderBoardValidationException("Forbidden", "Acknowledging requires a signed-in user.");
|
|
|
|
var candidate = await _feedData.GetSlaCandidateAsync(
|
|
new NotificationFeedScope(accountId, dispatcherId), workOrderId, cancellationToken);
|
|
if (candidate == null)
|
|
return SlaBreachAcknowledgementOutcome.NotFound;
|
|
|
|
var now = _timeProvider.GetUtcNow().UtcDateTime;
|
|
var clock = SlaResponseWindows.Evaluate(candidate.Severity, candidate.CreatedAt, now);
|
|
if (clock is not { State: SlaState.Breached })
|
|
return SlaBreachAcknowledgementOutcome.NotBreached;
|
|
if (candidate.BreachAcknowledged)
|
|
return SlaBreachAcknowledgementOutcome.AlreadyAcknowledged;
|
|
|
|
var names = await _userData.GetDisplayNamesByIdsAsync(new[] { userId });
|
|
var name = names.TryGetValue(userId, out var displayName) && !string.IsNullOrWhiteSpace(displayName)
|
|
? displayName
|
|
: user.FindFirstValue(ClaimTypes.Name) ?? userId;
|
|
|
|
_auditData.EnqueueAuditLog(new WorkOrderAuditLog
|
|
{
|
|
WorkOrderId = workOrderId,
|
|
UserId = userId,
|
|
FieldName = SlaBreachAcknowledgementAudit.FieldName,
|
|
OldValue = clock.DeadlineAt.ToString("o"),
|
|
NewValue = candidate.Severity,
|
|
Action = $"SLA breach acknowledged by {name}",
|
|
EventType = "system",
|
|
ActorType = "internal",
|
|
CreatedAt = now
|
|
});
|
|
await _auditData.PersistAsync(cancellationToken);
|
|
return SlaBreachAcknowledgementOutcome.Acknowledged;
|
|
}
|
|
}
|
|
}
|