Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0Crazy-0/ClinicFlow/llms.txt

Use this file to discover all available pages before exploring further.

A Schedule in ClinicFlow represents a single recurring availability block for a doctor on a specific day of the week. Schedules are the gating mechanism that the booking system checks before any appointment is created or rescheduled — a patient cannot book a slot that falls outside an active schedule. This page explains the Schedule entity, the two commands for creating schedules, and the deactivation flow that triggers appointment reassignment downstream.

The Schedule Entity

public class Schedule : BaseEntity
{
    public Guid DoctorId { get; init; }

    public DayOfWeek DayOfWeek { get; private set; }

    public TimeRange TimeRange { get; private set; }  // Start and End (TimeOnly)

    public bool IsActive { get; private set; }
}
Every schedule belongs to one doctor (DoctorId) and covers one day of the week (DayOfWeek). The availability window is captured by a TimeRange value object — an immutable record with Start and End as TimeOnly values.

TimeRange Value Object

public record TimeRange
{
    public TimeOnly Start { get; }
    public TimeOnly End  { get; }
    public TimeSpan Duration => End - Start;

    public static TimeRange Create(TimeOnly start, TimeOnly end);  // throws if start >= end
    public bool OverlapsWith(TimeRange other);
    public bool Covers(TimeRange other);  // true when this range fully contains 'other'
}
During booking, the domain calls schedule.CoversTimeRange(requestedRange), which delegates to TimeRange.Covers. This verifies that the requested appointment window sits entirely within the doctor’s available window for that day.

Creating Schedules

CreateScheduleCommand — Single Slot

Use CreateScheduleCommand when adding one availability slot at a time:
public sealed record CreateScheduleCommand(
    Guid DoctorId,
    DayOfWeek DayOfWeek,    // e.g. DayOfWeek.Monday
    TimeOnly StartTime,     // e.g. new TimeOnly(9, 0)
    TimeOnly EndTime        // e.g. new TimeOnly(17, 0); must be after StartTime
) : IRequest<Guid>;
The handler calls Schedule.EnsureNoDuplicateDay against the doctor’s existing schedules before persisting. On success it returns the new Schedule ID.

SetupWeeklyScheduleCommand — Batch Creation

SetupWeeklyScheduleCommand lets administrators configure all working days for a doctor in one request:
public sealed record ScheduleSlot(
    DayOfWeek DayOfWeek,
    TimeOnly StartTime,
    TimeOnly EndTime
);

public sealed record SetupWeeklyScheduleCommand(
    Guid DoctorId,
    IReadOnlyList<ScheduleSlot> Slots   // one entry per working day
) : IRequest<IReadOnlyList<Guid>>;
Internally the handler delegates to WeeklyScheduleSetupService.SetupWeeklySchedule:
public static IEnumerable<Schedule> SetupWeeklySchedule(
    Guid doctorId,
    IReadOnlyList<Schedule> existingSchedules,
    IReadOnlyList<WeeklyScheduleSlot> slots
)
The service iterates each slot, calling Schedule.EnsureNoDuplicateDay and Schedule.Create in sequence before persisting the entire batch via IScheduleRepository.CreateRangeAsync. The command returns a list of the newly created IDs, one per slot.

Duplicate-Day Protection

Schedule.EnsureNoDuplicateDay is a static guard that prevents two active schedules from existing on the same day for the same doctor:
internal static void EnsureNoDuplicateDay(
    IReadOnlyList<Schedule> existingSchedules,
    Guid doctorId,
    DayOfWeek dayOfWeek
)
It scans existingSchedules for any entry where s.DayOfWeek == dayOfWeek && s.IsActive. If a match is found, ScheduleAlreadyExistsException is thrown. Inactive (deactivated) schedules are excluded from this check, meaning you can replace a deactivated Monday slot by creating a fresh one without error.

Updating a Schedule

UpdateScheduleCommand replaces the time range of an existing schedule slot for the given doctor and day:
public sealed record UpdateScheduleCommand(
    Guid DoctorId,
    DayOfWeek DayOfWeek,
    TimeOnly StartTime,
    TimeOnly EndTime
) : IRequest<Guid>;
The handler resolves the schedule by (DoctorId, DayOfWeek) and updates the TimeRange. The schedule must be active — attempting to update a deactivated slot results in an error.

Deactivating a Schedule

DeactivateScheduleCommand marks an active schedule slot as unavailable for new bookings:
public sealed record DeactivateScheduleCommand(Guid ScheduleId) : IRequest;
Internally, the handler calls Schedule.Deactivate():
public void Deactivate()
{
    if (!IsActive)
        throw new DomainValidationException(DomainErrors.Schedule.AlreadyInactive);

    IsActive = false;

    AddDomainEvent(new ScheduleDeactivatedEvent(Id, DoctorId, DayOfWeek));
}
ScheduleDeactivatedEvent carries the ScheduleId, DoctorId, and the affected DayOfWeek. A downstream event handler (ScheduleDeactivatedEventHandler) listens for this event and calls appointment.MarkAsRequiresReassignment() on every future Scheduled appointment for that doctor whose ScheduledDate.DayOfWeek matches the deactivated day.
Deactivating a schedule does not cancel any existing appointments. Appointments that fall within the deactivated slot are transitioned to RequiresReassignment status. Staff must then use ReassignAppointment to move each affected appointment to a new doctor and time slot before the scheduled date. See the Appointments page for the full reassignment flow.

Availability Check During Booking

Every scheduling path — patient, doctor, and staff — calls the private helper EnsureDoctorIsAvailable inside AppointmentSchedulingService:
private static void EnsureDoctorIsAvailable(
    Schedule schedule,
    Guid doctorId,
    DateOnly scheduledDate,
    TimeRange timeRange
)
{
    if (!schedule.CoversTimeRange(timeRange))
        throw new DoctorNotAvailableException(...);
}
The schedule passed in is fetched by IScheduleRepository.GetByDoctorAndDayAsync(doctorId, scheduledDate.DayOfWeek). If no active schedule exists for that day, the repository returns null and the handler throws EntityNotFoundException before the domain check is even reached. Doctor and staff actors can bypass this check by setting IsOverbook = true in their respective scheduling args, which skips the EnsureDoctorIsAvailable call entirely.

Querying Schedules

GetSchedulesByDoctorId

public sealed record GetSchedulesByDoctorIdQuery(Guid DoctorId)
    : IRequest<IReadOnlyList<ScheduleDto>>;
Returns all schedules for a doctor — both active and inactive — as a list of ScheduleDto:
public sealed record ScheduleDto(
    Guid Id,
    Guid DoctorId,
    DayOfWeek DayOfWeek,
    TimeOnly StartTime,
    TimeOnly EndTime,
    bool IsActive
);
Consumers should filter by IsActive == true to display only bookable slots. The GetScheduleByDoctorAndDay query is also available for targeted lookups when the day is already known.

Command Reference

CommandActorEffect
CreateScheduleCommandAdmin / StaffCreates one schedule slot; rejects duplicates on the same active day
SetupWeeklyScheduleCommandAdmin / StaffCreates multiple slots in a single batch; duplicate check applies per slot
UpdateScheduleCommandAdmin / StaffReplaces the time range of an existing active slot
DeactivateScheduleCommandAdmin / StaffSets IsActive = false; fires ScheduleDeactivatedEvent → appointments become RequiresReassignment

Repository API

IScheduleRepository provides the full persistence contract:
public interface IScheduleRepository
{
    Task<Schedule?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
    Task<Schedule?> GetByDoctorAndDayAsync(Guid doctorId, DayOfWeek day, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<Schedule>> GetByDoctorIdAsync(Guid doctorId, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<Schedule>> GetActiveByDoctorIdAsync(Guid doctorId, CancellationToken cancellationToken = default);
    Task CreateAsync(Schedule schedule, CancellationToken cancellationToken = default);
    Task CreateRangeAsync(IReadOnlyList<Schedule> schedules, CancellationToken cancellationToken = default);
}
Use GetActiveByDoctorIdAsync when building availability calendars — it excludes deactivated slots automatically.

Build docs developers (and LLMs) love