mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 06:03:12 +00:00
56 lines
2.6 KiB
C#
56 lines
2.6 KiB
C#
|
|
namespace SeaHaven.Services.Helpers
|
|||
|
|
{
|
|||
|
|
public enum SlaState
|
|||
|
|
{
|
|||
|
|
OnTrack,
|
|||
|
|
/// <summary>At least half of the response window has elapsed.</summary>
|
|||
|
|
AtRisk,
|
|||
|
|
/// <summary>The whole response window has elapsed: the response deadline was missed.</summary>
|
|||
|
|
Breached
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>Where one work order stands against its SEV response window.</summary>
|
|||
|
|
public sealed record SlaClock(int Severity, DateTime StartedAt, DateTime DeadlineAt, int PercentElapsed, SlaState State);
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Respond deadlines from the client's SEV table, the same values the severity badge tooltip shows.
|
|||
|
|
/// They apply to Reactive and Emergency work orders with a severity, and the clock starts when the
|
|||
|
|
/// work order is created, never at its scheduled date. The table's "next day if after hours" note
|
|||
|
|
/// is not modelled: the clock always counts the plain hours.
|
|||
|
|
/// </summary>
|
|||
|
|
public static class SlaResponseWindows
|
|||
|
|
{
|
|||
|
|
public static readonly IReadOnlyDictionary<int, TimeSpan> Respond = new Dictionary<int, TimeSpan>
|
|||
|
|
{
|
|||
|
|
[1] = TimeSpan.FromHours(2),
|
|||
|
|
[2] = TimeSpan.FromHours(4),
|
|||
|
|
[3] = TimeSpan.FromHours(8),
|
|||
|
|
[4] = TimeSpan.FromHours(24),
|
|||
|
|
[5] = TimeSpan.FromHours(72)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/// <summary>The latest creation time at which a work order of this level has used half its window.</summary>
|
|||
|
|
public static DateTime AtRiskCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity] / 2;
|
|||
|
|
|
|||
|
|
/// <summary>The latest creation time at which a work order of this level has missed its deadline.</summary>
|
|||
|
|
public static DateTime BreachedCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity];
|
|||
|
|
|
|||
|
|
/// <summary>Null when the severity is not a SEV 1–5 level.</summary>
|
|||
|
|
public static SlaClock? Evaluate(string? severity, DateTime createdAt, DateTime now)
|
|||
|
|
{
|
|||
|
|
var level = WorkOrderSeverityRules.ParseLevel(severity);
|
|||
|
|
if (level is not int sev)
|
|||
|
|
return null;
|
|||
|
|
|
|||
|
|
var window = Respond[sev];
|
|||
|
|
var startedAt = DateTime.SpecifyKind(createdAt, DateTimeKind.Utc);
|
|||
|
|
var elapsed = now - startedAt;
|
|||
|
|
var state = elapsed >= window
|
|||
|
|
? SlaState.Breached
|
|||
|
|
: elapsed >= window / 2 ? SlaState.AtRisk : SlaState.OnTrack;
|
|||
|
|
var percent = (int)Math.Floor(Math.Max(elapsed.Ticks, 0) * 100d / window.Ticks);
|
|||
|
|
return new SlaClock(sev, startedAt, startedAt + window, percent, state);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|