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 models each physician as a Doctor entity that is tightly linked to a user account and a medical specialty. The domain enforces license uniqueness, validates consultation room bounds, and propagates suspension side-effects to future appointments through a domain event — keeping scheduling always consistent with the doctor’s active status.

The Doctor Entity

Doctor extends SoftDeletableEntity, which adds IsDeleted, DeletedAt, and the MarkAsDeleted / UndoDeletion helpers used by suspension and reactivation.
public class Doctor : SoftDeletableEntity
{
    public Guid UserId { get; init; }
    public PersonName FullName { get; private set; }
    public Guid MedicalSpecialtyId { get; init; }
    public MedicalLicenseNumber LicenseNumber { get; private set; }
    public string Biography { get; private set; }
    public ConsultationRoom ConsultationRoom { get; private set; }
}
FieldTypeNotes
UserIdGuidForeign key to the User account
FullNamePersonNameValue object — wraps a full name string (2–100 chars)
MedicalSpecialtyIdGuidReferences the MedicalSpecialty aggregate
LicenseNumberMedicalLicenseNumberValidated value object (4–15 chars)
BiographystringFree-form text; mutable via UpdateProfile
ConsultationRoomConsultationRoomValue object with bounds-checked number and floor

Value Objects

MedicalLicenseNumber

MedicalLicenseNumber is a record that enforces a length constraint on the raw string. It is used as a natural key — the system prevents two doctors from sharing the same license number.
public record MedicalLicenseNumber
{
    public const int MinimumLength = 4;
    public const int MaximumLength = 15;

    public string Value { get; }

    internal static MedicalLicenseNumber Create(string licenseNumber)
    {
        // Trims whitespace, then validates MinimumLength and MaximumLength
        ...
        return new MedicalLicenseNumber(trimmed);
    }

    public override string ToString() => Value;
}
MedicalLicenseNumber.Create is internal, so it may only be called from within ClinicFlow.Domain. The application layer passes the raw string through the command, and the handler constructs the value object before calling DoctorRegistrationService.Register.

ConsultationRoom

ConsultationRoom captures the physical location of a doctor’s room and validates that the room number and floor fall within the clinic’s physical layout.
public record ConsultationRoom
{
    public const int MinimumNumber = 1;
    public const int MaximumNumber = 35;
    public const int MinimumFloor  = 1;
    public const int MaximumFloor  = 8;

    public int    Number { get; }
    public string Name   { get; }
    public int    Floor  { get; }

    public static ConsultationRoom Create(int number, string name, int floor) { ... }

    public override string ToString() => $"Room {Number} - {Name} (Floor {Floor})";
}
Room numbers must be between 1 and 35; floor numbers must be between 1 and 8. Passing values outside these ranges throws a DomainValidationException.

DoctorRegistrationService

DoctorRegistrationService.Register is a static domain service that guards against duplicate license numbers. Before calling it, the application handler queries the repository with GetIncludingDeletedByLicenseNumberAsync — this intentionally retrieves soft-deleted (previously suspended) doctors too.
public static class DoctorRegistrationService
{
    public static Doctor Register(DoctorRegistrationArgs args, DoctorRegistrationContext context)
    {
        if (context.ExistingDoctor is not null)
            throw new DomainValidationException(DomainErrors.Doctor.InactiveProfileExists);

        return Doctor.Create(
            args.UserId,
            args.FullName,
            args.LicenseNumber,
            args.MedicalSpecialtyId,
            args.Biography,
            args.ConsultationRoom
        );
    }
}
If any doctor record (active or soft-deleted) with the same license number already exists, registration is blocked with InactiveProfileExists. To bring a previously suspended physician back, use ReactivateDoctorProfile instead (see below).

Commands

CreateDoctorProfile

Creates a new doctor profile. The handler builds the MedicalLicenseNumber and ConsultationRoom value objects, runs the duplicate-license check via DoctorRegistrationService.Register, and persists the new entity.
public sealed record CreateDoctorProfileCommand(
    Guid   UserId,
    string FirstName,
    string LastName,
    string LicenseNumber,
    Guid   MedicalSpecialtyId,
    string Biography,
    int    ConsultationRoomNumber,
    string ConsultationRoomName,
    int    ConsultationRoomFloor
) : IRequest<Guid>;
Returns: Guid — the new Doctor.Id. FluentValidation rules:
  • FirstName / LastName — required, length within PersonName.MinimumLength / PersonName.MaximumLength
  • LicenseNumber — required, 4–15 characters
  • ConsultationRoomNumber — 1–35
  • ConsultationRoomFloor — 1–8
  • MedicalSpecialtyId — non-empty Guid

UpdateDoctorProfile

Mutates the Biography and ConsultationRoom on an existing, active doctor. The LicenseNumber and MedicalSpecialtyId are immutable after creation.
public sealed record UpdateDoctorProfileCommand(
    Guid   DoctorId,
    string Biography,
    int    ConsultationRoomNumber,
    string ConsultationRoomName,
    int    ConsultationRoomFloor
) : IRequest;

SuspendDoctorProfile

Marks the doctor as soft-deleted and raises DoctorSuspendedEvent. A downstream event handler reads the event and marks all of the doctor’s future scheduled appointments as RequiresReassignment.
public sealed record SuspendDoctorProfileCommand(Guid DoctorId) : IRequest;
The suspension is implemented inside the Doctor entity:
/// <remarks>
/// Emitting <see cref="DoctorSuspendedEvent"/> triggers a downstream handler that automatically
/// flags all of the doctor's future scheduled appointments as requiring reassignment.
/// </remarks>
public void Suspend()
{
    if (IsDeleted)
        throw new BusinessRuleValidationException(DomainErrors.Doctor.AlreadySuspended);

    MarkAsDeleted();
    AddDomainEvent(new DoctorSuspendedEvent(Id));
}
Suspending a doctor is not reversible without data impact. The DoctorSuspendedEvent immediately displaces every future Scheduled appointment associated with that doctor, setting their status to RequiresReassignment. Receptionist staff must manually reassign those appointments before patients can be seen.

ReactivateDoctorProfile

Lifts the soft-delete flag and refreshes the biography and consultation room. Reactivation requires re-providing the room details because they may have changed during the suspension period.
public sealed record ReactivateDoctorProfileCommand(
    Guid   DoctorId,
    string Biography,
    int    ConsultationRoomNumber,
    string ConsultationRoomName,
    int    ConsultationRoomFloor
) : IRequest;
Internally calls Doctor.Reactivate(biography, consultationRoom), which calls UndoDeletion() and throws AlreadyActive if the doctor is not currently suspended.

Command Summary

CommandTypical ActorEffect
CreateDoctorProfileAdminCreates a new Doctor; blocks duplicate license numbers
UpdateDoctorProfileAdmin / DoctorUpdates Biography and ConsultationRoom
SuspendDoctorProfileAdminSoft-deletes doctor; raises DoctorSuspendedEvent → all future appointments set to RequiresReassignment
ReactivateDoctorProfileAdminRestores soft-deleted doctor; refreshes room details

Queries

GetDoctorsBySpecialtyId

Returns a paginated list of active doctors filtered by MedicalSpecialtyId.
public sealed record GetDoctorsBySpecialtyIdQuery(
    Guid SpecialtyId,
    int  PageNumber,
    int  PageSize
) : IRequest<PaginatedList<DoctorDto>>;
Response DTO:
public sealed record DoctorDto(
    Guid   Id,
    Guid   UserId,
    string FullName,
    Guid   MedicalSpecialtyId,
    string LicenseNumber,
    string Biography,
    int    ConsultationRoomNumber,
    string ConsultationRoomName,
    int    ConsultationRoomFloor
);
Additional queries available in the same namespace:
  • GetDoctorByIdQuery(Guid DoctorId) — fetch a single doctor by primary key.
  • GetDoctorByUserIdQuery(Guid UserId) — fetch the doctor profile linked to a given user account; useful after login to resolve the acting doctor.

Soft-Delete Behaviour

Because Doctor extends SoftDeletableEntity, the global EF Core query filter excludes rows where IsDeleted = true from all standard queries. This means suspended doctors are invisible to GetDoctorsBySpecialtyId and GetDoctorById. Only the specialised GetIncludingDeletedByLicenseNumberAsync repository method bypasses the filter — and only for the duplicate-license check during registration.

Build docs developers (and LLMs) love