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.

In ClinicFlow, a patient is not the same as a user. A user account represents the person who logs in; a patient profile represents the person who receives medical care. One user can own multiple patient profiles — their own (Self) and any number of family members. This separation enables a parent to book appointments for a child, or a spouse to manage care for a partner, all under a single login.

The Patient Entity

public class Patient : SoftDeletableEntity
{
    public Guid UserId { get; init; }                     // Owning user account
    public PersonName FullName { get; private set; }      // Value object; 2–100 chars
    public PatientRelationship RelationshipToUser { get; private set; }
    public DateOnly DateOfBirth { get; private set; }
    public BloodType BloodType { get; private set; }      // Null until medical profile is set
    public string Allergies { get; private set; }
    public string ChronicConditions { get; private set; }
    public EmergencyContact EmergencyContact { get; private set; } // Null until set
}
Patient extends SoftDeletableEntity, meaning closed or removed patients are never physically deleted from the database — they are flagged with IsDeleted = true.
Soft-deletion is used throughout the patient lifecycle. Calling RemoveFamilyMember or CloseAccount sets IsDeleted = true on the patient record. All repository queries that list active patients automatically filter these out. The deleted record can be reactivated internally if the same name and date of birth are later re-registered under the same user (see ReactivateAsPrimary / ReactivateAsFamilyMember).

PatientRelationship Enum

Every patient profile declares its relationship to the owning user account:
ValueIntegerMeaning
Self0The primary profile for the user (one per account)
Child1A dependent child
Spouse2Married partner
Parent3Parent or guardian
Sibling4Brother or sister
Other5Any other relationship
The Self relationship carries special domain rules: it is the only profile that can close its own account, and it cannot be removed as a family member.

Value Objects

PersonName

PersonName wraps a full name string with validation:
public record PersonName
{
    public const int MinimumLength = 2;
    public const int MaximumLength = 100;
    public string FullName { get; }

    public static PersonName Create(string fullName); // trims whitespace, enforces length
}

BloodType

BloodType is validated against a fixed set of exactly eight accepted values:
// Valid values (all 8 ABO/Rh combinations):
"A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"
Input is normalised to uppercase before validation. Any other string throws BusinessRuleValidationException.

EmergencyContact

EmergencyContact combines a PersonName and a PhoneNumber into a single value object:
public record EmergencyContact
{
    public PersonName Name { get; }
    public PhoneNumber PhoneNumber { get; }

    internal static EmergencyContact Create(string name, string phoneNumber);
}
Both fields are required. The value object is stored as an owned type on the Patient entity.

Creating Patient Profiles

CreatePatientProfileCommand — Basic Self Profile

Creates the primary patient profile for a user with just identity information:
public sealed record CreatePatientProfileCommand(
    Guid UserId,
    string FirstName,
    string LastName,
    DateOnly DateOfBirth
) : IRequest<Guid>;
This calls Patient.CreateSelf(...) internally. DateOfBirth must not be in the future. Medical profile data (BloodType, Allergies, ChronicConditions) and EmergencyContact are not set yet and must be added via UpdatePatientProfile before the patient can book any appointment.

CreateCompletePatientProfileCommand — Full Profile in One Step

An alternative command that collects all required fields upfront:
public sealed record CreateCompletePatientProfileCommand(
    Guid UserId,
    string FirstName,
    string LastName,
    DateOnly DateOfBirth,
    string BloodType,           // e.g. "O+"
    string Allergies,           // free text
    string ChronicConditions,   // free text
    string EmergencyContactName,
    string EmergencyContactPhone
) : IRequest<Guid>;
This command creates the patient profile and immediately calls UpdateMedicalProfile and UpdateEmergencyContact so the patient is booking-eligible from the moment of creation. Prefer this command for registration flows where all information is collected in a single form.

Adding Family Members

AddFamilyMemberCommand

Adds a non-Self patient profile linked to an existing user account:
public sealed record AddFamilyMemberCommand(
    Guid UserId,
    string FirstName,
    string LastName,
    DateOnly DateOfBirth,
    PatientRelationship Relationship   // Must not be PatientRelationship.Self
) : IRequest<Guid>;
The handler calls Patient.CreateFamilyMember(...). Passing Relationship = Self throws a DomainValidationException — use CreatePatientProfileCommand to create the primary profile.

AddCompleteFamilyMemberCommand

An alternative that creates a family member profile with all medical data in one step, mirroring CreateCompletePatientProfileCommand for self profiles:
public sealed record AddCompleteFamilyMemberCommand(
    Guid UserId,
    string FirstName,
    string LastName,
    DateOnly DateOfBirth,
    string BloodType,
    string Allergies,
    string ChronicConditions,
    string EmergencyContactName,
    string EmergencyContactPhone,
    PatientRelationship Relationship
) : IRequest<Guid>;
Family member profiles created with AddFamilyMemberCommand start without medical data. You will need to call UpdatePatientProfile for each such member before they can book appointments. Use AddCompleteFamilyMemberCommand to skip that step.

Updating Patient Data

UpdatePatientProfileCommand — Medical Profile & Emergency Contact

public sealed record UpdatePatientProfileCommand(
    Guid PatientId,
    string BloodType,
    string Allergies,
    string ChronicConditions,
    string EmergencyContactName,
    string EmergencyContactPhone
) : IRequest;
The handler calls patient.UpdateMedicalProfile(bloodType, allergies, chronicConditions) and patient.UpdateEmergencyContact(emergencyContact) in the same transaction. After this command succeeds, EnsureCompleteProfile() will pass for that patient.

Booking Eligibility — EnsureCompleteProfile

Before any appointment is created for a patient (via patient-initiated or staff-initiated scheduling), the domain calls:
internal void EnsureCompleteProfile()
{
    if (BloodType is null || EmergencyContact is null)
        throw new IncompleteProfileException(DomainErrors.Patient.ProfileIncomplete);
}
Both BloodType and EmergencyContact must be non-null. Attempting to book for a patient whose profile is incomplete throws IncompleteProfileException before any slot availability check is performed.
Doctor-initiated scheduling does not call EnsureCompleteProfile — doctors can book for patients with incomplete profiles. Only patient-initiated and staff-initiated scheduling enforce this rule.

Removing Family Members

public sealed record RemoveFamilyMemberCommand(
    Guid PatientId,   // The family member's patient ID
    Guid UserId       // Must be the owning user
) : IRequest;
The handler calls patient.RemoveFamilyMember(initiatorUserId), which enforces two rules:
  1. UserId must match the patient’s UserId — you can only remove your own family members.
  2. RelationshipToUser must not be Self — the primary profile cannot be removed this way.
On success, the patient record is soft-deleted (IsDeleted = true).

Closing an Account

public sealed record ClosePatientAccountCommand(Guid UserId) : IRequest;
CloseAccount can only be called on the user’s Self patient profile. The handler checks IAppointmentRepository.HasActiveAppointmentsForUserAsync and passes the result as hasPendingAppointments:
public void CloseAccount(bool hasPendingAppointments)
{
    if (RelationshipToUser is not PatientRelationship.Self)
        throw new DomainValidationException(...);

    if (hasPendingAppointments)
        throw new DomainValidationException(...);

    MarkAsDeleted();
}
A user with any active or future appointments cannot close their account. All pending appointments must be cancelled first.

Querying Patients

GetPatientsByUserId

public sealed record GetPatientsByUserIdQuery(Guid UserId)
    : IRequest<IReadOnlyList<PatientDto>>;
Returns all active (non-deleted) patient profiles owned by a user as a list of PatientDto:
public sealed record PatientDto(
    Guid Id,
    Guid UserId,
    string FullName,
    PatientRelationship RelationshipToUser,
    DateOnly DateOfBirth,
    string? BloodType,              // null if medical profile not yet set
    string? Allergies,
    string? ChronicConditions,
    string? EmergencyContactName,   // null if emergency contact not yet set
    string? EmergencyContactPhone
);
Nullable fields (BloodType, EmergencyContactName, EmergencyContactPhone) indicate an incomplete profile. Consumers should prompt the user to fill in missing data before allowing appointment booking.

Command Reference

CommandActorRestrictions
CreatePatientProfileCommandUser (self-registration)One Self profile per user; DateOfBirth must not be in future
CreateCompletePatientProfileCommandUser (self-registration)Same as above; also validates BloodType and emergency contact fields
AddFamilyMemberCommandUserRelationship must not be Self; no limit on family member count
AddCompleteFamilyMemberCommandUserSame as above; also sets medical profile and emergency contact immediately
UpdatePatientProfileCommandUser / StaffUpdates medical data and emergency contact for any patient the user owns
RemoveFamilyMemberCommandUserUserId must own the patient; cannot remove Self
ClosePatientAccountCommandUserMust be Self; no pending appointments allowed

Build docs developers (and LLMs) love