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.

Appointments in ClinicFlow follow a strict lifecycle enforced at the domain level. Every status transition is gated by invariants on the Appointment aggregate. Commands are dispatched via MediatR and are grouped by the actor initiating the action — Patient, Doctor, or Staff (Receptionist/Admin). All write commands commit via IUnitOfWork and raise domain events consumed by registered INotificationHandler implementations. The AppointmentStatus enum governs the full lifecycle:
ValueNameDescription
1ScheduledNewly booked, awaiting check-in
3InProgressDoctor has started the consultation
4CompletedConsultation finished
5CancelledCancelled within the allowed window — no penalty
6NoShowPatient did not attend
7LateCancellationCancelled after the allowed window — penalty applied
8CheckedInPatient arrived and checked in by staff
9RequiresReassignmentDoctor was suspended; awaiting reassignment

Scheduling Commands

ScheduleByPatientCommand

Schedules a new appointment initiated by a patient (or a guardian acting on behalf of a patient).Returns: IRequest<Guid> — the new appointment’s Id.
public sealed record ScheduleByPatientCommand(
    Guid InitiatorUserId,
    Guid TargetPatientId,
    Guid DoctorId,
    Guid AppointmentTypeId,
    DateOnly ScheduledDate,
    TimeOnly StartTime,
    TimeOnly EndTime,
    string? PatientNotes = null
) : IRequest<Guid>;
InitiatorUserId
Guid
required
The UserId of the logged-in patient (or guardian) dispatching the command.
TargetPatientId
Guid
required
The Patient.Id for whom the appointment is being booked. May differ from the initiator if a guardian is acting on behalf of a dependent.
DoctorId
Guid
required
The Doctor.Id to book with.
AppointmentTypeId
Guid
required
The AppointmentTypeDefinition.Id defining the type of consultation.
ScheduledDate
DateOnly
required
The appointment date. Must be today or in the future (validated against TimeProvider.GetUtcNow()).
StartTime
TimeOnly
required
Appointment start time.
EndTime
TimeOnly
required
Appointment end time. Must be after StartTime.
PatientNotes
string?
Optional patient-supplied notes. Maximum 500 characters.
Handler behaviour:
  1. Resolves TargetPatient, InitiatorPatient (via patientRepository.GetByUserIdAsync), Doctor, User (for IsPhoneVerified), and AppointmentType.
  2. Loads active patient penalties from IPatientPenaltyRepository.
  3. Fetches the doctor’s schedule for the day via IScheduleRepository.GetByDoctorAndDayAsync.
  4. Checks for slot conflicts via IAppointmentRepository.HasConflictAsync. Throws AppointmentConflictException on conflict.
  5. Calls IRegionalSchedulingService.EnforceSchedulingRegulations for regional compliance.
  6. Delegates to AppointmentSchedulingService.ScheduleByPatient(...) which validates phone verification, patient–guardian relationships, age eligibility, penalty eligibility, and slot fit.
  7. Persists and returns the new Appointment.Id.
Domain event raised: AppointmentScheduledEvent
The initiating patient’s phone must be verified (IsPhoneVerified = true) before this command succeeds. Use SendPhoneVerificationCommand and VerifyPhoneCommand to complete verification first.

Rescheduling Commands

RescheduleByPatientCommand

Reschedules an existing appointment at the patient’s request. Patients are limited to one reschedule per appointment (RescheduleCount >= 1 throws).Returns: IRequest (no return value)
public sealed record RescheduleByPatientCommand(
    Guid InitiatorUserId,
    Guid AppointmentId,
    DateOnly NewDate,
    TimeOnly NewStartTime,
    TimeOnly NewEndTime,
    string? NewPatientNotes = null
) : IRequest;
InitiatorUserId
Guid
required
The UserId of the patient initiating the reschedule.
AppointmentId
Guid
required
The appointment to reschedule.
NewDate
DateOnly
required
New appointment date. Must be in the future.
NewStartTime
TimeOnly
required
New start time.
NewEndTime
TimeOnly
required
New end time. Must be after NewStartTime.
NewPatientNotes
string?
Updated patient notes. Maximum 500 characters.
Handler behaviour: Resolves appointment, patient (target and initiator), doctor, appointment type, and penalties. Checks conflict on new slot. Delegates to AppointmentReschedulingService.RescheduleByPatient.Domain event raised: AppointmentRescheduledEvent (carries previousDate and previousTimeRange)

Cancellation Commands

CancelAppointmentByPatientCommand

Cancels an appointment at the patient’s request. The handler applies regional late-cancellation rules; if cancelled too close to the appointment time, status becomes LateCancellation (penalty applied) rather than Cancelled.Returns: IRequest (no return value)
public sealed record CancelAppointmentByPatientCommand(
    Guid AppointmentId,
    Guid InitiatorUserId,
    string? Reason
) : IRequest;
AppointmentId
Guid
required
The appointment to cancel.
InitiatorUserId
Guid
required
The UserId of the patient (or guardian) initiating the cancellation.
Reason
string?
Optional free-text cancellation reason stored on the appointment.
Handler behaviour: Resolves appointment, patient, appointment type, doctor, and specialty. Calls AppointmentCancellationService.CancelByPatient(...) with a context containing the specialty and category — used by the regional service to determine the late-cancellation window.Domain events raised: AppointmentCancelledEvent (normal) or AppointmentLateCancelledEvent (late). The AppointmentLateCancelledEventHandler processes penalty creation.

Lifecycle Transition Commands

CheckInAppointmentByStaffCommand

Records a patient’s arrival and moves the appointment from ScheduledCheckedIn. Returns: IRequest (no return value)
public sealed record CheckInAppointmentByStaffCommand(
    Guid AppointmentId,
    string? ReceptionistNotes = null
) : IRequest;
AppointmentId
Guid
required
The appointment to check in. Must be in Scheduled status.
ReceptionistNotes
string?
Optional arrival notes stored on Appointment.ReceptionistNotes.
Handler behaviour: Calls appointment.CheckIn(today, receptionistNotes). Throws DomainValidationException (DomainErrors.Appointment.CannotCheckIn) if not in Scheduled status. Records CheckedInAt date. Domain event raised: AppointmentCheckedInEvent

StartAppointmentByDoctorCommand

Starts a consultation, transitioning the appointment from CheckedInInProgress. Only the appointment’s assigned doctor can start it. Returns: IRequest (no return value)
public sealed record StartAppointmentByDoctorCommand(Guid AppointmentId, Guid InitiatorUserId)
    : IRequest;
AppointmentId
Guid
required
The appointment to start. Must be in CheckedIn status.
InitiatorUserId
Guid
required
The UserId of the doctor. Resolved to Doctor; doctor.Id must match appointment.DoctorId, otherwise DomainValidationException (DomainErrors.Appointment.UnauthorizedDoctor) is thrown.
Domain event raised: AppointmentStartedEvent

No-Show Commands

MarkAppointmentAsNoShowByDoctorCommand

Marks a Scheduled appointment as a no-show, initiated by the treating doctor. Only the assigned doctor can use this variant. Returns: IRequest (no return value)
public sealed record MarkAppointmentAsNoShowByDoctorCommand(
    Guid AppointmentId,
    Guid InitiatorUserId
) : IRequest;
AppointmentId
Guid
required
The appointment. Must be in Scheduled status.
InitiatorUserId
Guid
required
The UserId of the doctor. doctor.Id must match appointment.DoctorId; otherwise AppointmentNoShowUnauthorizedException is thrown.
Domain event raised: AppointmentMarkedAsNoShowEvent. The AppointmentMarkedAsNoShowEventHandler creates a patient penalty record.

MarkAppointmentAsNoShowByStaffCommand

Marks a Scheduled appointment as a no-show, initiated by staff. No doctor-ownership check is performed. Returns: IRequest (no return value)
public sealed record MarkAppointmentAsNoShowByStaffCommand(Guid AppointmentId) : IRequest;
AppointmentId
Guid
required
The appointment. Must be in Scheduled status.
Domain event raised: AppointmentMarkedAsNoShowEvent

ReassignAppointmentCommand

Reassigns a RequiresReassignment appointment to a new doctor with a new slot. Used after a doctor suspension. Returns: IRequest (no return value)
public sealed record ReassignAppointmentCommand(
    Guid AppointmentId,
    Guid NewDoctorId,
    DateOnly NewDate,
    TimeOnly NewStartTime,
    TimeOnly NewEndTime
) : IRequest;
AppointmentId
Guid
required
The appointment in RequiresReassignment status.
NewDoctorId
Guid
required
The Doctor.Id to assign the appointment to.
NewDate
DateOnly
required
New appointment date.
NewStartTime
TimeOnly
required
New start time.
NewEndTime
TimeOnly
required
New end time. Must be after NewStartTime.
Handler behaviour: Validates the new doctor exists, fetches their schedule for the day, checks for slot conflicts (no overbook allowed), then calls AppointmentReassignmentService.Reassign(...) which transitions status back to Scheduled with the new doctor and slot. Domain event raised: AppointmentReassignedEvent (carries previousDoctorId)

CleanExpiredDisplacedAppointmentsCommand

Background/scheduled command that cancels any RequiresReassignment appointments whose ScheduledDate has already passed (system timeout). No patient penalty is applied as the clinic bears responsibility. Returns: IRequest (no return value)
public sealed record CleanExpiredDisplacedAppointmentsCommand : IRequest;
This record has no parameters — it is intended to be dispatched by a background job (e.g., a Hangfire cron or a .NET IHostedService). Handler behaviour:
  1. Gets all expired displaced appointments via IAppointmentRepository.GetExpiredDisplacedAppointmentsAsync(now).
  2. Calls appointment.CancelDueToSystemTimeout(today) on each — sets CancellationReason = "System timeout: Displaced appointment was not reassigned." and status to Cancelled.
  3. Persists all changes in one SaveChangesAsync call.
Domain event raised per appointment: AppointmentSystemCancelledEvent

Notes Update Commands

UpdatePatientNotesByPatientCommand

Updates the patient-visible notes on an appointment, initiated by the patient (or guardian). Returns: IRequest (no return value)
public sealed record UpdatePatientNotesByPatientCommand(
    Guid AppointmentId,
    Guid InitiatorUserId,
    string? Notes
) : IRequest;
AppointmentId
Guid
required
The appointment to update. Must be in Scheduled or RequiresReassignment status.
InitiatorUserId
Guid
required
The UserId of the patient initiating the update. PatientAccessService.EnsureCanActOnBehalfOf validates that the initiator is the patient or their guardian.
Notes
string?
New notes. null clears the field.

UpdatePatientNotesByStaffCommand

Updates the patient notes on an appointment, initiated by staff. No ownership check; staff can update any patient’s notes. Returns: IRequest (no return value)
public sealed record UpdatePatientNotesByStaffCommand(Guid AppointmentId, string? Notes) : IRequest;
AppointmentId
Guid
required
The appointment. Must be in Scheduled or RequiresReassignment status.
Notes
string?
New patient notes. null clears the field.

UpdateReceptionistNotesByStaffCommand

Updates the internal receptionist notes on an appointment. Only allowed when the appointment is in CheckedIn status. Returns: IRequest (no return value)
public sealed record UpdateReceptionistNotesByStaffCommand(Guid AppointmentId, string? Notes)
    : IRequest;
AppointmentId
Guid
required
The appointment. Must be in CheckedIn status. Throws DomainValidationException (DomainErrors.Appointment.CannotUpdateNotes) otherwise.
Notes
string?
New receptionist notes. null clears the field.

Queries

GetAppointmentByIdQuery

Retrieves a single appointment by its Id. Returns: IRequest<AppointmentDto>
public sealed record GetAppointmentByIdQuery(Guid AppointmentId) : IRequest<AppointmentDto>;
AppointmentId
Guid
required
The appointment’s primary key. Throws EntityNotFoundException if not found.

GetAppointmentsByPatientIdQuery

Retrieves a paginated list of all appointments for a specific patient. Returns: IRequest<PaginatedList<AppointmentDto>>
public sealed record GetAppointmentsByPatientIdQuery(Guid PatientId, int PageNumber, int PageSize)
    : IRequest<PaginatedList<AppointmentDto>>;
PatientId
Guid
required
The Patient.Id (not the UserId) to filter by.
PageNumber
int
required
1-based page index.
PageSize
int
required
Records per page.

GetAppointmentsByDoctorIdQuery

Retrieves a paginated list of a doctor’s appointments filtered to a specific date. Returns: IRequest<PaginatedList<AppointmentDto>>
public sealed record GetAppointmentsByDoctorIdQuery(
    Guid DoctorId,
    DateOnly Date,
    int PageNumber,
    int PageSize
) : IRequest<PaginatedList<AppointmentDto>>;
DoctorId
Guid
required
The Doctor.Id to filter by.
Date
DateOnly
required
The specific date to retrieve appointments for.
PageNumber
int
required
1-based page index.
PageSize
int
required
Records per page.

GetAppointmentsByDateRangeQuery

Retrieves a paginated list of all appointments within an inclusive date range across all doctors and patients. Returns: IRequest<PaginatedList<AppointmentDto>>
public sealed record GetAppointmentsByDateRangeQuery(
    DateOnly StartDate,
    DateOnly EndDate,
    int PageNumber,
    int PageSize
) : IRequest<PaginatedList<AppointmentDto>>;
StartDate
DateOnly
required
Inclusive range start date.
EndDate
DateOnly
required
Inclusive range end date.
PageNumber
int
required
1-based page index.
PageSize
int
required
Records per page.

AppointmentDto Response Shape

All appointment queries return AppointmentDto or PaginatedList<AppointmentDto>.
public sealed record AppointmentDto(
    Guid Id,
    Guid PatientId,
    Guid DoctorId,
    Guid AppointmentTypeId,
    DateOnly ScheduledDate,
    TimeOnly StartTime,
    TimeOnly EndTime,
    AppointmentStatus Status,
    string PatientNotes,
    string ReceptionistNotes
);
Id
Guid
The appointment’s primary key.
PatientId
Guid
The Patient entity’s Id (not the UserId).
DoctorId
Guid
The Doctor entity’s Id.
AppointmentTypeId
Guid
The AppointmentTypeDefinition.Id.
ScheduledDate
DateOnly
The date the appointment is scheduled for.
StartTime
TimeOnly
Start time of the appointment slot (from TimeRange.Start).
EndTime
TimeOnly
End time of the appointment slot (from TimeRange.End).
Status
AppointmentStatus
Current lifecycle status. See the status table at the top of this page.
PatientNotes
string
Patient-supplied notes. Empty string if none.
ReceptionistNotes
string
Staff/receptionist notes added at check-in. Empty string if none.

Build docs developers (and LLMs) love