up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
namespace StellaOps.Notifier.Worker.Escalation;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing on-call schedules.
|
||||
/// </summary>
|
||||
public interface IOnCallScheduleService
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists all on-call schedules for a tenant.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<OnCallSchedule>> ListSchedulesAsync(
|
||||
string tenantId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific on-call schedule.
|
||||
/// </summary>
|
||||
Task<OnCallSchedule?> GetScheduleAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates an on-call schedule.
|
||||
/// </summary>
|
||||
Task<OnCallSchedule> UpsertScheduleAsync(
|
||||
OnCallSchedule schedule,
|
||||
string? actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an on-call schedule.
|
||||
/// </summary>
|
||||
Task<bool> DeleteScheduleAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
string? actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current on-call user(s) for a schedule.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<OnCallUser>> GetCurrentOnCallAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
DateTimeOffset? atTime = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets on-call coverage for a time range.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<OnCallShift>> GetCoverageAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an override for a schedule.
|
||||
/// </summary>
|
||||
Task<OnCallOverride> CreateOverrideAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
OnCallOverride @override,
|
||||
string? actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an override.
|
||||
/// </summary>
|
||||
Task<bool> DeleteOverrideAsync(
|
||||
string tenantId,
|
||||
string scheduleId,
|
||||
string overrideId,
|
||||
string? actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An on-call schedule defining rotation coverage.
|
||||
/// </summary>
|
||||
public sealed record OnCallSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique schedule ID.
|
||||
/// </summary>
|
||||
public required string ScheduleId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Tenant ID.
|
||||
/// </summary>
|
||||
public required string TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name.
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Description.
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timezone for the schedule (IANA format).
|
||||
/// </summary>
|
||||
public required string Timezone { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the schedule is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation layers (combined to determine on-call).
|
||||
/// </summary>
|
||||
public required IReadOnlyList<RotationLayer> Layers { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Active overrides.
|
||||
/// </summary>
|
||||
public IReadOnlyList<OnCallOverride>? Overrides { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When created.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Who created.
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When last updated.
|
||||
/// </summary>
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Who last updated.
|
||||
/// </summary>
|
||||
public string? UpdatedBy { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A rotation layer in an on-call schedule.
|
||||
/// </summary>
|
||||
public sealed record RotationLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Layer name.
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Layer priority (lower = higher priority for conflicts).
|
||||
/// </summary>
|
||||
public int Priority { get; init; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Users in this rotation.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<OnCallUser> Users { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Rotation type.
|
||||
/// </summary>
|
||||
public required RotationType Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Handoff time (when rotations change).
|
||||
/// </summary>
|
||||
public required TimeOnly HandoffTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Rotation interval.
|
||||
/// </summary>
|
||||
public required TimeSpan RotationInterval { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When this layer's rotation starts.
|
||||
/// </summary>
|
||||
public required DateTimeOffset RotationStart { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Days/times this layer is active (null = always).
|
||||
/// </summary>
|
||||
public IReadOnlyList<ScheduleRestriction>? Restrictions { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this layer is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of rotation.
|
||||
/// </summary>
|
||||
public enum RotationType
|
||||
{
|
||||
/// <summary>Daily rotation.</summary>
|
||||
Daily,
|
||||
|
||||
/// <summary>Weekly rotation.</summary>
|
||||
Weekly,
|
||||
|
||||
/// <summary>Custom interval rotation.</summary>
|
||||
Custom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A restriction on when a rotation layer is active.
|
||||
/// </summary>
|
||||
public sealed record ScheduleRestriction
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of restriction.
|
||||
/// </summary>
|
||||
public required RestrictionType Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Days of week (0=Sunday, 6=Saturday) for weekly restrictions.
|
||||
/// </summary>
|
||||
public IReadOnlyList<int>? DaysOfWeek { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Start time for the restriction.
|
||||
/// </summary>
|
||||
public TimeOnly? StartTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// End time for the restriction.
|
||||
/// </summary>
|
||||
public TimeOnly? EndTime { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of schedule restriction.
|
||||
/// </summary>
|
||||
public enum RestrictionType
|
||||
{
|
||||
/// <summary>Restrict to specific days of week.</summary>
|
||||
DaysOfWeek,
|
||||
|
||||
/// <summary>Restrict to specific time range.</summary>
|
||||
TimeOfDay,
|
||||
|
||||
/// <summary>Restrict to specific days and times.</summary>
|
||||
DaysAndTime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A user in an on-call schedule.
|
||||
/// </summary>
|
||||
public sealed record OnCallUser
|
||||
{
|
||||
/// <summary>
|
||||
/// User ID.
|
||||
/// </summary>
|
||||
public required string UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name.
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Email address.
|
||||
/// </summary>
|
||||
public string? Email { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Phone number.
|
||||
/// </summary>
|
||||
public string? Phone { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Preferred notification channel.
|
||||
/// </summary>
|
||||
public string? PreferredChannelId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Position in rotation order.
|
||||
/// </summary>
|
||||
public int Order { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An override for an on-call schedule.
|
||||
/// </summary>
|
||||
public sealed record OnCallOverride
|
||||
{
|
||||
/// <summary>
|
||||
/// Override ID.
|
||||
/// </summary>
|
||||
public required string OverrideId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// User taking over on-call.
|
||||
/// </summary>
|
||||
public required OnCallUser User { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the override starts.
|
||||
/// </summary>
|
||||
public required DateTimeOffset StartsAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the override ends.
|
||||
/// </summary>
|
||||
public required DateTimeOffset EndsAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason for the override.
|
||||
/// </summary>
|
||||
public string? Reason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Who created the override.
|
||||
/// </summary>
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When created.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A computed on-call shift.
|
||||
/// </summary>
|
||||
public sealed record OnCallShift
|
||||
{
|
||||
/// <summary>
|
||||
/// User on call.
|
||||
/// </summary>
|
||||
public required OnCallUser User { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the shift starts.
|
||||
/// </summary>
|
||||
public required DateTimeOffset StartsAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the shift ends.
|
||||
/// </summary>
|
||||
public required DateTimeOffset EndsAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Layer this shift comes from.
|
||||
/// </summary>
|
||||
public required string LayerName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is from an override.
|
||||
/// </summary>
|
||||
public bool IsOverride { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user