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