mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +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.
151 lines
6.1 KiB
C#
151 lines
6.1 KiB
C#
namespace SeaHaven.Services.DTOs
|
|
{
|
|
/// <summary>
|
|
/// Feed reasons in their fixed tie-break order. Every reason the Notification Center
|
|
/// can show is listed here so a new producer only adds items, never a contract change.
|
|
/// </summary>
|
|
public static class NotificationReasons
|
|
{
|
|
public const string SeverityCritical = "severity-critical";
|
|
public const string Sla = "sla";
|
|
public const string Mentions = "mentions";
|
|
public const string Unassigned = "unassigned";
|
|
public const string DueSoon = "due-soon";
|
|
public const string NoVendor = "no-vendor";
|
|
public const string AvetaMissing = "aveta-missing";
|
|
public const string Activity = "activity";
|
|
public const string VendorConflict = "vendor-conflict";
|
|
|
|
public static readonly IReadOnlyList<string> Order = new[]
|
|
{
|
|
SeverityCritical, Sla, Mentions, Unassigned, DueSoon, NoVendor, AvetaMissing, Activity, VendorConflict
|
|
};
|
|
|
|
public static readonly IReadOnlyDictionary<string, string> Labels = new Dictionary<string, string>
|
|
{
|
|
[SeverityCritical] = "Critical severity",
|
|
[Sla] = "SLA at Risk",
|
|
[Mentions] = "Mentions",
|
|
[Unassigned] = "Unassigned",
|
|
[DueSoon] = "Due Soon",
|
|
[NoVendor] = "No Vendor",
|
|
[AvetaMissing] = "Aveta Missing",
|
|
[Activity] = "Recent activity",
|
|
[VendorConflict] = "Vendor Conflict"
|
|
};
|
|
}
|
|
|
|
public static class NotificationSeverities
|
|
{
|
|
public const string Critical = "Critical";
|
|
public const string High = "High";
|
|
public const string Medium = "Medium";
|
|
public const string Low = "Low";
|
|
|
|
public static int Rank(string severity) => severity switch
|
|
{
|
|
Critical => 3,
|
|
High => 2,
|
|
Medium => 1,
|
|
_ => 0
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// How a row leaves the feed. <see cref="Dismissable"/> rows are hidden client-side for the
|
|
/// session; <see cref="Acknowledge"/> rows (a missed response deadline) are excluded from
|
|
/// dismiss and dismiss-all and leave only through their own acknowledge action.
|
|
/// </summary>
|
|
public static class NotificationRowTypes
|
|
{
|
|
public const string Dismissable = "dismissable";
|
|
public const string Acknowledge = "acknowledge";
|
|
}
|
|
|
|
public static class NotificationTargetKinds
|
|
{
|
|
public const string WorkOrder = "workOrder";
|
|
public const string Queue = "queue";
|
|
}
|
|
|
|
/// <summary>Named work-order queues a grouped item opens.</summary>
|
|
public static class NotificationQueues
|
|
{
|
|
public const string Unassigned = "unassigned";
|
|
/// <summary>The signed-in user's own open work orders.</summary>
|
|
public const string AssignedToMe = "assigned-to-me";
|
|
}
|
|
|
|
/// <summary>Work-order detail tabs a single-work-order item can open on.</summary>
|
|
public static class NotificationWorkOrderTabs
|
|
{
|
|
public const string Info = "info";
|
|
public const string Comments = "comments";
|
|
public const string Extras = "extras";
|
|
public const string Uplifts = "uplifts";
|
|
}
|
|
|
|
public sealed class NotificationTargetDto
|
|
{
|
|
public string Kind { get; init; } = NotificationTargetKinds.WorkOrder;
|
|
public int? WorkOrderId { get; init; }
|
|
public string? Tab { get; init; }
|
|
public string? Queue { get; init; }
|
|
}
|
|
|
|
public sealed class NotificationWorkOrderRefDto
|
|
{
|
|
public int Id { get; init; }
|
|
public string? Number { get; init; }
|
|
}
|
|
|
|
public sealed class NotificationItemDto
|
|
{
|
|
/// <summary>Stable across reads while the condition holds; the client dismisses by it.</summary>
|
|
public string Id { get; init; } = "";
|
|
public string Reason { get; init; } = "";
|
|
public string Severity { get; init; } = NotificationSeverities.Low;
|
|
public string RowType { get; init; } = NotificationRowTypes.Dismissable;
|
|
public string Title { get; init; } = "";
|
|
/// <summary>Work orders this item represents.</summary>
|
|
public int Count { get; init; }
|
|
/// <summary>When the condition started; drives recency ordering.</summary>
|
|
public DateTime TriggeredAt { get; init; }
|
|
public NotificationTargetDto Target { get; init; } = new();
|
|
public IReadOnlyList<NotificationWorkOrderRefDto> WorkOrders { get; init; } = Array.Empty<NotificationWorkOrderRefDto>();
|
|
}
|
|
|
|
public sealed class NotificationSectionDto
|
|
{
|
|
public string Reason { get; init; } = "";
|
|
public string Label { get; init; } = "";
|
|
/// <summary>Highest item severity in the section.</summary>
|
|
public string Severity { get; init; } = NotificationSeverities.Low;
|
|
/// <summary>Work orders the whole section represents, including items beyond the returned slice.</summary>
|
|
public int Count { get; init; }
|
|
public IReadOnlyList<NotificationItemDto> Items { get; init; } = Array.Empty<NotificationItemDto>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A work order past half of its SEV response window and not yet past the whole window, with the
|
|
/// server-computed clock. Drives the top-of-app banner and the one-time 50% toast.
|
|
/// </summary>
|
|
public sealed class NotificationSlaWorkOrderDto
|
|
{
|
|
public int Id { get; init; }
|
|
public string? Number { get; init; }
|
|
public int Severity { get; init; }
|
|
/// <summary>The work order's creation time; the response clock never starts at the scheduled date.</summary>
|
|
public DateTime StartedAt { get; init; }
|
|
public DateTime DeadlineAt { get; init; }
|
|
public int PercentElapsed { get; init; }
|
|
}
|
|
|
|
public sealed class NotificationFeedDto
|
|
{
|
|
public DateTime GeneratedAt { get; init; }
|
|
public IReadOnlyList<NotificationSectionDto> Sections { get; init; } = Array.Empty<NotificationSectionDto>();
|
|
/// <summary>Every at-risk work order in the feed's scope, oldest first; not capped like section items.</summary>
|
|
public IReadOnlyList<NotificationSlaWorkOrderDto> SlaAtRisk { get; init; } = Array.Empty<NotificationSlaWorkOrderDto>();
|
|
}
|
|
}
|