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.

ClinicFlow’s domain layer is built around a small set of clearly bounded entities and a rich set of immutable value objects. Every entity extends BaseEntity, which supplies a Guid Id, a domain-event queue (AddDomainEvent / ClearDomainEvents), and audit infrastructure. Entities that support soft-deletion additionally extend SoftDeletableEntity, which exposes an IsDeleted flag managed through MarkAsDeleted() and UndoDeletion().

Entities

The table below summarises every entity, its most important properties, and the lifecycle methods that enforce domain invariants.
EntityKey PropertiesKey Methods
UserRole, Email, PhoneNumber, PasswordHash, IsActive, IsPhoneVerified, FailedLoginAttempts, LockoutEndCreate, MarkPhoneAsVerified, RecordLogin, RecordFailedLogin, ChangePassword, Deactivate, Reactivate
DoctorUserId, FullName, MedicalSpecialtyId, LicenseNumber, Biography, ConsultationRoomCreate, UpdateProfile, Suspend, Reactivate
PatientUserId, FullName, RelationshipToUser, DateOfBirth, BloodType, Allergies, ChronicConditions, EmergencyContactCreateSelf, CreateFamilyMember, UpdateMedicalProfile, UpdateEmergencyContact, EnsureCompleteProfile, GetAge, CloseAccount, RemoveFamilyMember
AppointmentPatientId, DoctorId, AppointmentTypeId, ScheduledDate, TimeRange, Status, PatientNotes, ReceptionistNotes, RescheduleCount, CancellationReasonSchedule (factory), Reschedule, Cancel, CancelLate, CheckIn, Start, Complete, MarkAsRequiresReassignment, Reassign, CancelDueToSystemTimeout, MarkAsNoShowByStaff, MarkAsNoShowByDoctor
ScheduleDoctorId, DayOfWeek, TimeRange, IsActiveCreate, Deactivate, CoversTimeRange, EnsureNoDuplicateDay
AppointmentTypeDefinitionCategory, Name, Description, Duration, AgePolicy, IsUnrestrictedBySpecialty, AllowedSpecialtyIds, RequiredTemplatesCreate, UpdateDetails, RestrictToSpecialties, MakeUnrestricted, AddAllowedSpecialty, RemoveAllowedSpecialty, ChangeAgePolicy, AddRequiredTemplate, RemoveRequiredTemplate, ValidatePatientEligibility, Deactivate, Reactivate
MedicalRecordPatientId, DoctorId, AppointmentId, ChiefComplaint, ClinicalDetailsCreate, AddClinicalDetail
PatientPenaltyPatientId, AppointmentId (nullable), Type, Reason, BlockedUntil (nullable), IsRemovedCreateAutomaticWarning, CreateAutomaticBlock, CreateManualBlock, Remove
ClinicalFormTemplateCode, Name, Description, JsonSchemaDefinitionCreate, UpdateDetails, UpdateSchema, Deactivate, Reactivate
MedicalSpecialtyName, Description, TypicalDuration, CancellationPolicyCreate, UpdateDetails, Deactivate, Reactivate, IsCancellationAllowed

User

User represents an authentication account. It holds credentials, contact details, and a UserRole. The account tracks failed login attempts and enforces a 15-minute lockout after five consecutive failures (MaxFailedLoginAttempts = 5). Phone verification is a hard gate for patient-initiated scheduling.

Doctor

Doctor is linked 1-to-1 with a User account and belongs to a MedicalSpecialty. It holds a MedicalLicenseNumber and a ConsultationRoom. Calling Suspend() soft-deletes the doctor and raises a DoctorSuspendedEvent that automatically flags all future scheduled appointments as RequiresReassignment.

Patient

Patient represents a person receiving care. A user account may own multiple patient profiles — one Self profile and any number of family-member profiles (Child, Spouse, Parent, Sibling, Other). The domain enforces that BloodType and EmergencyContact must be present before the patient can book (EnsureCompleteProfile). GetAge(referenceDate) computes the patient’s age on any given date for eligibility checks.

Appointment

Appointment is the central aggregate. It is created exclusively through the Appointment.Schedule(...) internal factory and transitions through a well-defined status machine (see Status Lifecycle below). The domain limits rescheduling to one occurrence per appointment (RescheduleCount >= 1 throws).
// Appointment.cs — internal factory method
internal static Appointment Schedule(
    Guid patientId,
    Guid doctorId,
    Guid appointmentTypeId,
    DateOnly scheduledDate,
    TimeRange timeRange,
    string? patientNotes = null
)
{
    if (patientId == Guid.Empty)
        throw new DomainValidationException(DomainErrors.Validation.ValueRequired);
    if (doctorId == Guid.Empty)
        throw new DomainValidationException(DomainErrors.Validation.ValueRequired);
    if (appointmentTypeId == Guid.Empty)
        throw new DomainValidationException(DomainErrors.Validation.ValueRequired);
    if (timeRange is null)
        throw new DomainValidationException(DomainErrors.General.RequiredFieldNull);

    var appointment = new Appointment(
        patientId, doctorId, appointmentTypeId, scheduledDate, timeRange, patientNotes
    );

    appointment.AddDomainEvent(new AppointmentScheduledEvent(appointment));

    return appointment;
}
Status transitions enforced by individual methods:
// Scheduled → CheckedIn
public void CheckIn(DateOnly checkedInAt, string? receptionistNotes = null)
{
    if (Status is not AppointmentStatus.Scheduled)
        throw new DomainValidationException(DomainErrors.Appointment.CannotCheckIn);
    Status = AppointmentStatus.CheckedIn;
    // ...
}

// CheckedIn → InProgress  (only by the owning doctor)
public void Start(Guid initiatorDoctorId, DateTime startedAt)
{
    if (initiatorDoctorId != DoctorId)
        throw new DomainValidationException(DomainErrors.Appointment.UnauthorizedDoctor);
    if (Status is not AppointmentStatus.CheckedIn)
        throw new DomainValidationException(DomainErrors.Appointment.CannotStart);
    Status = AppointmentStatus.InProgress;
    // ...
}

// InProgress → Completed
public void Complete(DateTime completedAt)
{
    if (Status is not AppointmentStatus.InProgress)
        throw new DomainValidationException(DomainErrors.Appointment.CannotComplete);
    Status = AppointmentStatus.Completed;
    // ...
}

// Scheduled → RequiresReassignment (when the doctor is suspended)
internal void MarkAsRequiresReassignment()
{
    if (Status is not AppointmentStatus.Scheduled)
        throw new DomainValidationException(DomainErrors.Appointment.CannotReassign);
    Status = AppointmentStatus.RequiresReassignment;
}

Schedule

Schedule defines a recurring weekly availability slot for a doctor on a given DayOfWeek. Availability is checked via CoversTimeRange(requested), which returns true only when the slot is active and its TimeRange fully contains the requested time window.

AppointmentTypeDefinition

AppointmentTypeDefinition configures the clinical rules for a kind of appointment: category, expected duration, specialty restrictions, age eligibility, and required clinical form templates. It can be scoped to specific specialties via AllowedSpecialtyIds or set as globally unrestricted.

MedicalRecord

MedicalRecord is created when a doctor opens an encounter and captures the ChiefComplaint plus a collection of typed IClinicalDetailRecord entries (e.g., cardiology flags, dental odontograms). Each template code may appear at most once per record.

PatientPenalty

PatientPenalty records a single disciplinary event against a patient — either a Warning or a TemporaryBlock. Automatic warnings always link to an AppointmentId; manual blocks do not. Staff can call Remove() to set IsRemoved = true, which excludes the penalty from all active-block checks.

ClinicalFormTemplate

ClinicalFormTemplate defines a dynamic JSON-schema-backed form. It carries an immutable Code (the natural key used by external systems and historical records) alongside a mutable Name, Description, and JsonSchemaDefinition.

MedicalSpecialty

MedicalSpecialty groups doctors by discipline and owns the cancellation policy for its appointments via a CancellationLimit value object. TypicalDuration is a statistical reference — the binding duration for scheduling comes from AppointmentTypeDefinition.Duration.

Value Objects

Value objects are C# record types — structurally equal, immutable after creation, and validated in their factory methods.

TimeRange

Wraps a TimeOnly Start and TimeOnly End. Provides OverlapsWith(other) and Covers(other). Creation fails if Start >= End.

AgeEligibilityPolicy

Holds optional MinimumAge, MaximumAge (0–120), and a RequiresLegalGuardian flag. ValidatePatientEligibility(age, hasGuardianConsent) throws typed domain errors for out-of-range ages or missing guardian consent for patients under 18.

PenaltyHistory

Wraps a IReadOnlyList<PatientPenalty>. Exposes HasPriorWarnings, TotalHistoricalBlocks, IsCurrentlyBlocked(date), DetermineNextBlockDuration(), and EnsureNotBlocked(date).

SchedulingClearance

A token record with no data properties. Created only by SchedulingClearance.Granted(), which is called exclusively by IRegionalSchedulingService. Passing it to a scheduling service proves regulations have been evaluated.

CancellationLimit

Defines the minimum notice window (in hours) for a penalty-free cancellation. Only pre-approved values are valid: 0, 12, 24, 48, 72. Created via CancellationLimit.FromHours(int).

BloodType

Wraps a validated ABO/Rh string (A+, A-, B+, B-, AB+, AB-, O+, O-). Normalised to upper-case on creation.

PersonName

Enforces a 2–100 character trimmed full-name string.

EmailAddress

Normalised to lower-case, max 254 chars, validated with a regex pattern ^[^@\s]+@[^@\s]+\.[^@\s]+$.

PhoneNumber

7–20 characters, validated with a permissive international phone regex. Accepts +, dashes, spaces, and parentheses.

MedicalLicenseNumber

4–15 character alphanumeric string. Acts as the doctor’s verifiable credential in the system.

ConsultationRoom

Compound value: Number (1–35), Name (non-empty string), Floor (1–8). Renders as "Room {Number} - {Name} (Floor {Floor})".

EmergencyContact

Composes a PersonName and a PhoneNumber. Both sub-values are validated on creation.

EncounterDuration

An integer Minutes value constrained to 10–90 minutes in 5-minute increments. Created via EncounterDuration.FromMinutes(int).

Enumerations

AppointmentStatus

Represents the full lifecycle of an appointment.
ValueIntDescription
Scheduled1Booked and awaiting the visit date
InProgress3The doctor has started the consultation
Completed4The encounter has been closed
Cancelled5Cancelled within the allowed window — no penalty
NoShow6Patient did not attend
LateCancellation7Cancelled after the notice deadline — triggers a penalty
CheckedIn8Patient has arrived and checked in at the front desk
RequiresReassignment9Doctor was suspended; appointment awaits a new doctor

AppointmentCategory

ValueDescription
FirstConsultationInitial visit with a new patient
FollowUpReturn visit to review a previous diagnosis or treatment
EmergencyUrgent, unscheduled visit requiring immediate attention
CheckupRoutine preventive health examination
ProcedureMedical or surgical procedure
Patients cannot cancel Procedure appointments. Staff or doctors must handle those. Emergency appointments can only be cancelled by the patient themselves or by a parent if the patient is under 18.

UserRole

ValueDescription
PatientCan book and manage their own appointments
DoctorCan schedule, reschedule, start, and complete encounters
ReceptionistFront-desk staff with scheduling and administrative access
AdminFull system access

PatientRelationship

Defines how a patient profile relates to the owning user account: Self, Child, Spouse, Parent, Sibling, Other. A Self patient is the primary profile and is the only relationship that can close an account.

Build docs developers (and LLMs) love