queue booking
# Test Plan: Queue & Appointment BookingModule: Clinic App — Queue Management & Appointment Booking Code Audit Date: 2026-06-17 Status: Pending
Code Audit Summary
Pages & Components
| Page | URL | Component | Purpose |
|---|---|---|---|
| Queue Page | /cms/queue | QueuePage.tsx | Today's patient queue with filters, status changes, actions |
| Appointments Page | /cms/appointments | AppointmentsPage.tsx | Calendar (Month/Week/Day) + listing sidebar |
| Add Booking Modal | — | AddAppointmentModal/index.tsx | 2-step modal (Doctor+Time → Patient Info) |
APIs
| API | Method | Purpose |
|---|---|---|
/clinical/facilities/:id/queue | GET | Fetch queue (server-side: date, search, pagination) |
/clinical/appointments | POST | Create appointment (via AddAppointmentModal) |
/clinical/appointments/:id | PATCH | Update status (via QueueRow status dropdown) |
/clinical/appointments | GET | List appointments (for Calendar view) |
Key Hooks
| Hook | Purpose |
|---|---|
useQueueFilters | Date, practitioner, search, status filter state |
useQueueData | Fetch queue + client-side filter (practitioner, status) |
useQueueActions | Status change with terminal confirmation dialog |
useQueuePagination | Page/size state management |
Queue Row Actions (per patient)
| Action | Condition | Component/Modal |
|---|---|---|
| Start Encounter | Not past date, not encounter-driven status, not terminal | Navigate to encounter |
| Billing | canManageBilling permission | BillingPanel slide-in |
| Next Visit | Always available | NextVisitModal |
| Notes (Add/Edit) | Always available | NotesModal |
| Admit to IPD | Always available | AdmissionForm modal |
| Preview/Print | Always available | PatientPreviewModal |
| Cancel/Remove | Always available | Status change to 'cancelled' |
FLOW A: Queue Page
Component: QueuePage.tsx, QueueHeader.tsx, QueueTable.tsx, QueueRow.tsx
URL: /cms/queue
API: GET /clinical/facilities/{id}/queue?date=&page=&per_page=&search=
A.1 Page Load
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-01 | Load queue page | P0 | Page loads with today's date, queue table visible | ✅ PASS |
| Q-02 | Queue with patients | P0 | Active queue rows displayed with position #, name, phone, doctor, time, status | ✅ PASS |
| Q-03 | Queue empty — today | P0 | "No patients in queue today" message + "Add Patient" button | ✅ PASS |
| Q-04 | Queue empty — past date | P1 | "No patients for this date" message, no Add Patient button | ✅ PASS |
| Q-05 | Loading state | P0 | LoadingSpinner overlay with "Loading queue for [date]..." text | ✅ PASS |
| Q-06 | Error state | P1 | EmptyState with AlertTriangle: "Failed to load queue" | ✅ PASS |
| Q-07 | Overnight stale date fix | P2 | Tab left open overnight → date auto-corrects to today on visibility change | ⏭️ Not Tested |
A.2 Queue Header — Date Navigation
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-10 | Today button | P0 | Click Today → resets date to today, refetches queue | ✅ PASS |
| Q-11 | Previous day | P0 | Click ← → moves date back 1 day, refetches | ✅ PASS |
| Q-12 | Next day | P0 | Click → → moves date forward 1 day, refetches | ⚠️ Disabled when no future queue data |
| Q-13 | Date picker | P0 | Click calendar icon → date picker opens, select date → refetches | ✅ PASS |
| Q-14 | Past date — status disabled | P1 | On past date, status dropdowns are read-only badges | ✅ PASS |
| Q-15 | Past date — Start Encounter hidden | P1 | On past date, Start Encounter button not shown | ✅ PASS |
A.3 Queue Header — Search
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-20 | Search by name | P0 | Type patient name → debounced 500ms → API call with search= param → filtered results | ✅ PASS |
| Q-21 | Search by phone | P0 | Type phone digits → matches patients by phone | ✅ PASS |
| Q-22 | Search partial match | P0 | Type partial name → returns matching patients | ✅ PASS |
| Q-23 | Clear search | P0 | Clear search field → resets to full queue | ✅ PASS |
| Q-24 | Search — no results | P0 | Type non-matching term → "No patients found" + "Clear filters" link | ✅ PASS |
| Q-25 | Debounce behavior | P1 | Rapid typing → only 1 API call after 500ms pause | ✅ PASS |
A.4 Queue Header — Practitioner Filter
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-30 | Filter by practitioner | P0 | Select doctor from dropdown → only that doctor's patients shown | ✅ PASS |
| Q-31 | Clear practitioner filter | P0 | Select "All" → shows all practitioners | ✅ PASS |
| Q-32 | Practitioner filter + search combined | P1 | Filter doctor + type search → both filters applied | ✅ PASS |
| Q-33 | No patients for practitioner | P1 | Select doctor with no queue → empty state with filters message | ✅ PASS |
A.5 Queue Header — Status Filter
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-40 | Filter — All | P0 | Default: shows all statuses | ✅ PASS |
| Q-41 | Filter — Waiting | P0 | Select Waiting → only patients with waiting status | ✅ PASS |
| Q-42 | Filter — In Progress | P0 | Select In Progress → only in-progress patients | ✅ PASS |
| Q-43 | Filter — Completed | P0 | Select Completed → only completed patients | ✅ PASS |
| Q-44 | Combined filters | P1 | Practitioner + Status + Search → all three filters applied | ✅ PASS |
A.6 Queue Header — Statistics
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-50 | Stats — with patients | P0 | Shows 4 stats: Total, Waiting, In Progress, Completed | ✅ PASS |
| Q-51 | Stats — empty queue | P0 | All stats show 0 | ✅ PASS |
| Q-52 | Stats — update on status change | P1 | Change a patient status → stats counters update | ✅ PASS |
| Q-53 | Stats — update on new booking | P1 | Add a patient → total count increments | ✅ PASS |
A.7 Queue Table — Active Queue
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-60 | Queue position numbers | P0 | Patients numbered sequentially starting at 1 | ✅ PASS |
| Q-61 | Patient name + note indicator | P0 | Name displayed, FileText icon shown if notes exist | ✅ PASS |
| Q-62 | Patient age + gender | P0 | "M, 45y" format below name | ✅ PASS |
| Q-63 | Note preview | P1 | If patient has note, first 2 lines shown below name in muted text | ✅ PASS |
| Q-64 | Phone display | P0 | Phone icon + number displayed | ✅ PASS |
| Q-65 | Practitioner display | P0 | Doctor name shown, "-" if no practitioner | ✅ PASS |
| Q-66 | Time display | P0 | Appointment time formatted (e.g., "10:30 AM") with type icon | ✅ PASS |
| Q-67 | Wait time display | P0 | "Wait: 15 min" shown for active patients (not terminal statuses) | ✅ PASS |
| Q-68 | Wait time — hidden for completed/cancelled | P1 | No wait time shown for completed/cancelled/no-show patients | ✅ PASS |
A.8 Queue Table — Out of Queue Section
How OoQ works: Patients manually marked "Completed" (fulfilled) via status dropdown move to
out_of_queue[]. BUT encounter-driven "finished" stays in the main queue (read-only badge), and "cancelled" disappears from both lists entirely. The split is inconsistent — see Bug #48.
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-70 | Manual completed → moves to OoQ | P0 | Select "Completed" from dropdown → patient moves to out_of_queue section with separator + "Completed" badge | ✅ PASS |
| Q-71 | Finished (encounter) → stays in queue | P0 | Finish encounter → status "finished" shown as read-only badge in main queue, NOT moved to OoQ | ✅ PASS |
| Q-72 | Cancelled → disappears from both | P0 | Cancel appointment → patient removed from queue list entirely, not shown in OoQ | ✅ PASS |
A.9 Queue Table — Empty States
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-80 | Empty — today, no filters | P0 | "No patients in queue today" + "Add a patient to get started" + Add Patient button | ✅ PASS |
| Q-81 | Empty — past/future date, no filters | P0 | "No patients for this date" message, no Add Patient button | ✅ PASS |
| Q-82 | Empty — active filters | P0 | "No patients found" + "Try adjusting your filters" + "Clear filters" link | ✅ PASS |
| Q-83 | Clear filters link | P1 | Click "Clear filters" → resets all filters, refetches queue | ✅ PASS |
A.10 Queue Table — Pagination
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-90 | Pagination — shown when needed | P0 | Pagination controls visible when queue > per_page | ✅ PASS |
| Q-91 | Next/Previous page | P0 | Navigate between pages | ✅ PASS |
| Q-92 | Page size change | P0 | Change per_page (10/25/50) → refetch with new size | ✅ PASS |
| Q-93 | Page reset on filter change | P0 | Change date/filter/search → page resets to 1 | ✅ PASS |
| Q-94 | Page reset on practitioner change | P0 | Change practitioner → page resets to 1 | ✅ PASS |
A.11 Queue Row — Status Change
Status Types:
status(API field) = FHIRAppointmentStatus: proposed, pending, booked, arrived, fulfilled, cancelled, noshowqueue_status(API field) = combined status from backend including encounter state
Valid Transitions (from VALID_STATUS_TRANSITIONS):
proposed → pending, booked, cancelled
pending → booked, cancelled, noshow
booked → arrived, cancelled, noshow
arrived → fulfilled, noshow, cancelled
fulfilled → terminal (read-only badge)
cancelled → terminal (read-only badge)
noshow → terminal (read-only badge)Encounter-driven (ENCOUNTER_DRIVEN_STATUSES): When an active encounter exists, queue_status reflects the encounter (in_progress, finished, etc.) and the status dropdown becomes a read-only badge.
Terminal confirmation: fulfilled, cancelled, noshow require confirmation dialog with optional reason.
Past dates: Status shown as read-only badge, no dropdown.
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-100 | Status dropdown for booked appointment | P0 | Shows valid transitions: arrived, cancelled, noshow | ✅ PASS |
| Q-101 | Change to arrived (non-terminal) | P0 | Select "Arrived" → immediate update, toast: "Status updated to Arrived" | ✅ PASS |
| Q-102 | Change to fulfilled (terminal) | P0 | Select "Completed" → confirmation dialog opens with patient name | ✅ PASS |
| Q-103 | Confirm terminal status | P0 | Click Confirm → status updated, dialog closes | ✅ PASS |
| Q-104 | Cancel terminal status dialog | P0 | Click Cancel → status unchanged | ✅ PASS |
| Q-105 | Cancel with reason | P1 | Change to "Cancelled", enter reason → reason saved | ✅ PASS |
| Q-106 | Encounter-driven — read-only badge | P0 | When encounter is active (in_progress) → status shown as read-only badge, no dropdown | ✅ PASS |
| Q-107 | Encounter-driven — finished badge | P0 | When encounter finished → shows "Finished" badge, read-only | ✅ PASS |
| Q-108 | Terminal status — read-only badge | P0 | fulfilled/cancelled/noshow → read-only badge, no dropdown | ✅ PASS |
| Q-109 | Past date — read-only badge | P1 | Past date → read-only badge | ✅ PASS |
| Q-110 | Status change — loading state | P1 | During mutation → LoadingSpinner in dropdown, "Updating" text | ✅ PASS |
| Q-111 | Status change — error handling | P1 | API fails → status reverts, error toast | ✅ PASS |
| Q-112 | Only valid transitions shown | P0 | Dropdown only shows transitions valid for current status | ✅ PASS |
A.12 Queue Row — Start Encounter
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-110 | Start encounter button visible | P0 | "Stethoscope" button shown for active (non-terminal) patients on today | ✅ PASS |
| Q-111 | Click Start Encounter | P0 | Click → API call → navigate to /cms/encounters/{id} | ✅ PASS |
| Q-112 | Start Encounter hidden — terminal status | P1 | No button shown for completed/cancelled patients | ✅ PASS |
| Q-113 | Start Encounter hidden — past date | P1 | No button shown on past dates | ✅ PASS |
| Q-114 | Start Encounter — click row | P0 | Click patient row → same as Start Encounter (shortcut) | ✅ PASS |
| Q-115 | Start Encounter — error | P1 | API fails → error toast shown | ✅ PASS |
A.13 Queue Row — Billing
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-120 | Billing button — with permission | P0 | "Plus" button shown when canManageBilling is true | ✅ PASS |
| Q-121 | Billing button — without permission | P1 | Button disabled/greyed out when no billing permission | ✅ PASS |
| Q-122 | Click Billing | P0 | Click → BillingPanel slides in with patient details | ✅ PASS |
| Q-123 | Close Billing panel | P0 | Close panel → returns to queue | ✅ PASS |
A.14 Queue Row — Actions Dropdown
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-130 | Open dropdown | P0 | Click ⋮ → dropdown with: Next Visit, Notes, Admit to IPD, Preview, Cancel | ✅ PASS |
| Q-131 | Next Visit | P0 | Click → NextVisitModal opens with patient pre-filled | ✅ PASS |
| Q-132 | Add Notes (no existing note) | P0 | Click "Add Notes" → NotesModal opens empty | ✅ PASS |
| Q-133 | Edit Notes (existing note) | P1 | Click "Edit Notes" (label changes) → NotesModal with existing note | ✅ PASS |
| Q-134 | Admit to IPD | P1 | Click → AdmissionForm modal with patient pre-filled | ✅ PASS |
| Q-135 | Preview / Print | P0 | Click → PatientPreviewModal opens | ✅ PASS |
| Q-136 | Cancel / Remove from Queue | P0 | Click → confirmation dialog + status changes to cancelled | ✅ PASS |
A.15 Add Patient (from Queue)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| Q-140 | Open Add Patient modal | P0 | Click "Add Patient" → AddAppointmentModal opens with mode='queue' (WALKIN) | ✅ PASS |
| Q-141 | Complete booking | P0 | Fill steps → Submit → patient appears in queue, stats update | ✅ PASS |
| Q-142 | Close modal without booking | P0 | Close modal → no patient added, queue unchanged | ✅ PASS |
| Q-143 | Refetch after booking | P0 | After successful booking → queue refetches automatically | ✅ PASS |
FLOW B: Add Booking Modal — Queue Mode (WALKIN)
Component: AddAppointmentModal/index.tsx (mode='queue')
Steps: 2-step wizard (Doctor & Time → Patient Info)
Form: react-hook-form + Zod (addAppointmentFormSchema)
API: POST /clinical/appointments
Modal Behavior
| Behavior | Detail |
|---|---|
| Size | 550px × 800px, max height calc(100vh-2rem) |
| Close prevention | Outside click blocked, Escape key blocked |
| Reset on close | Form + steps reset to Step 1 |
| Tenant name | Shows current tenant name in header (Building2 icon) |
| Cancel button | Step 1: "Cancel" ghost button, Step 2: "Cancel" ghost + "Back" outline |
B.1 Modal Open & Reset
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-01 | Open from Queue page | P0 | Click "Add Patient" → modal opens, appointment_type = WALKIN | ✅ PASS |
| B-02 | Modal title | P0 | Header shows "Add Appointment" + tenant name | ✅ PASS |
| B-03 | Stepper visible | P0 | 2-step stepper: "Doctor & Time" → "Patient Info", starts at Step 1 | ✅ PASS |
| B-04 | Close prevention — outside click | P1 | Click outside modal → does NOT close | ✅ PASS |
| B-05 | Close prevention — Escape key | P1 | Press Escape → does NOT close | ✅ PASS |
| B-06 | Close via Cancel button | P0 | Click "Cancel" on Step 1 → modal closes, form resets | ✅ PASS |
| B-07 | Reopen after close — form reset | P0 | Close modal → reopen → form is blank, Step 1 | ✅ PASS |
B.2 Step 1 — Doctor Selection
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-10 | Doctor dropdown — populated | P0 | Shows active practitioners for current facility (useFacilityPractitioners with active_only:true) | ✅ PASS |
| B-11 | Doctor dropdown — loading | P1 | LoadingSpinner + "Loading doctors..." while fetching | ✅ PASS |
| B-12 | Doctor dropdown — error | P1 | AlertCircle + "Failed to load doctors" if API fails | ✅ PASS |
| B-13 | Doctor dropdown — empty | P1 | "No doctors available" if no active practitioners | ✅ PASS |
| B-14 | Select doctor from dropdown | P0 | Click doctor → selected, displayed in trigger | ✅ PASS |
| B-15 | Doctor details shown | P0 | After selection: doctor name, specialization if available | ✅ PASS |
| B-16 | Change doctor | P0 | Select different doctor → time slot cleared, new doctor selected | ✅ PASS |
B.3 Step 1 — Appointment Type
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-20 | Appointment type — radio buttons | P0 | Three options: Walk-in, On Call, Telemedicine (RadioGroup) | ✅ PASS |
| B-21 | Queue mode — default WALKIN | P0 | mode='queue' → WALKIN pre-selected | ✅ PASS |
| B-22 | Switch to ONCALL | P0 | Select ONCALL → Date picker + Time slot + Duration fields appear | ✅ PASS |
| B-23 | Switch to TELEMEDICINE | P0 | Select TELEMEDICINE → Date + Time + Duration + Payment option appear | ✅ PASS |
| B-24 | Switch back to WALKIN | P0 | Select WALKIN → Date/Time/Duration/Payment fields hide | ✅ PASS |
B.4 Step 1 — Date & Time (ONCALL/TELEMEDICINE only)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-30 | Date picker — opens | P0 | Click date button → calendar popover opens | ✅ PASS |
| B-31 | Date picker — past dates disabled | P0 | Dates before today are greyed out | ✅ PASS |
| B-32 | Select date | P0 | Click a date → date button shows "Jun 17" format, calendar closes | ✅ PASS |
| B-33 | Date picker — today default | P1 | First open: date defaults to today (if no date set) | ✅ PASS |
| B-34 | Change date — clears time slot | P0 | Select new date → time slot selection resets | ✅ PASS |
B.5 Step 1 — Time Slots (ONCALL/TELEMEDICINE only)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-40 | Time slot dropdown — loading | P1 | LoadingSpinner in dropdown while fetching (usePractitionerTimeSlots) | ✅ PASS |
| B-41 | Time slot dropdown — disabled until doctor+date | P0 | Greyed out before doctor and date selected | ✅ PASS |
| B-42 | Time slot dropdown — available slots | P0 | Shows available time slots in 12h format (e.g., "10:00 AM") | ✅ PASS |
| B-43 | Time slot — booked slots disabled | P1 | Booked slots shown as disabled with "Booked" label + destructive styling | ✅ PASS |
| B-44 | Time slot — no slots available | P0 | "No slots available" message when API returns empty | ✅ PASS |
| B-45 | Time slot — select doctor+date first message | P1 | Before selecting: "Select date and doctor first" message | ✅ PASS |
| B-46 | Select time slot | P0 | Click available slot → selected | ✅ PASS |
| B-47 | Time slot validation message | P1 | Before selecting: red text "Time selection is required for appointments" | ✅ PASS |
B.6 Step 1 — Duration
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-50 | Duration — default from doctor schedule | P0 | Shows doctor's slot_duration (e.g., 15 min) from schedule | ✅ PASS |
| B-51 | Duration — accepts any value | P1 | Type any number → form accepts ≥5 min (Zod max(120) not enforced, backend also no cap) | Bug #44 ⚠️ |
| B-52 | Duration — invalid input | P1 | Enter letters or <5 → validation error | ✅ PASS |
B.7 Step 1 — Payment Option (TELEMEDICINE only)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-55 | Payment options — visible for TELEMEDICINE | P0 | Select TELEMEDICINE → payment dropdown appears (BEFORE/AFTER/NO) | ✅ PASS |
| B-56 | Payment options — hidden for WALKIN/ONCALL | P0 | WALKIN or ONCALL → no payment option field | ✅ PASS |
B.8 Step 1 — Reason for Visit
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-60 | Reason for visit — textarea | P1 | Optional textarea (2 rows, no resize) visible for all types | ✅ PASS |
| B-61 | Reason for visit — accepts text | P1 | Type multi-line reason → stored | ✅ PASS |
B.9 Step 1 — Continue Button
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-65 | Continue — disabled without doctor | P0 | No doctor selected → Continue button disabled (greyed out) | ✅ PASS |
| B-66 | Continue — disabled without time (ONCALL/TELEMEDICINE) | P0 | ONCALL/TELEMEDICINE without time slot → disabled | ✅ PASS |
| B-67 | Continue — enabled for WALKIN with doctor | P0 | WALKIN + doctor selected → enabled immediately (no time needed) | ✅ PASS |
| B-68 | Continue — toast validation on click | P1 | Click disabled Continue → toast: "Doctor selection is required" or "Time slot is required" | ✅ PASS |
| B-69 | Continue → advances to Step 2 | P0 | Click enabled Continue → Step 2 (Patient Info) shown | ✅ PASS |
B.10 Step 2 — Federated Patient Search (FederatedPatientPicker)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-70 | Search input visible | P0 | "Search existing patients" input at top of Step 2 | ✅ PASS |
| B-71 | Search — In Clinic bucket | P0 | Type existing patient name → shows in "In this clinic" bucket | ✅ PASS |
| B-72 | Search — In Kulcare bucket | P1 | Type patient from other clinic → shows in "Elsewhere on Kulcare" bucket | ✅ PASS |
| B-73 | Search — No match → New Patient | P0 | Type unknown name → both buckets empty → "Create new patient" option | ✅ PASS |
| B-74 | Select In Clinic patient | P0 | Click in-clinic patient → form auto-fills: name, phone, gender, DOB, email | ✅ PASS |
| B-75 | Select In Kulcare patient | P1 | Click in-kulcare patient → form auto-fills: name, phone (no DOB — known Bug #32) | ✅ PASS |
| B-76 | Create new patient | P0 | Click "Create new" → form fields cleared for manual entry | ✅ PASS |
B.11 Step 2 — Patient Form Fields
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-80 | first_name + last_name — 2-col grid | P0 | Two input fields side by side | ✅ PASS |
| B-81 | Country code + Phone — 2-col grid | P0 | RegionSelect dropdown + phone input (10 digits) | ✅ PASS |
| B-82 | Email — full width | P1 | Email input below phone row | ✅ PASS |
| B-83 | Gender — radio group | P1 | M / F / O radio buttons | ✅ PASS |
| B-84 | DOB — date input | P1 | Date input, max=today | ✅ PASS |
| B-85 | Fields disabled — existing patient | P0 | When patient selected from search → all fields disabled (greyed out, not editable) | ✅ PASS |
| B-86 | Fields enabled — new patient | P0 | New patient mode → all fields editable | ✅ PASS |
B.12 Step 2 — Couple Session
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-90 | Couple session — checkbox | P2 | Collapsible section at bottom: "Couple Session" with checkbox | ✅ PASS |
| B-91 | Enable couple session | P2 | Check → spouse details section expands (first_name, last_name, phone, email, gender, DOB) | ✅ PASS |
| B-92 | Disable couple session | P2 | Uncheck → spouse section collapses, spouse data cleared | ✅ PASS |
B.13 Step 2 — Validation
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-95 | First name — empty | P0 | Submit without first_name → Zod error: "First name is required" (toast on Continue click) | Pending |
| B-96 | Phone — empty | P0 | Submit without phone → Zod error: "Phone number is required" | Pending |
| B-97 | Phone — invalid format | P0 | Enter "abc" or "12345" → Zod regex: "Phone number must be 10 digits" | Pending |
| B-98 | Phone — 10 digits valid | P0 | Enter "9889988998" → passes validation | Pending |
| B-99 | Email — invalid format | P1 | Enter "notanemail" → superRefine: "Invalid email format" | Pending |
| B-100 | DOB — future date | P1 | Enter tomorrow's date → Zod refine: "Date of birth cannot be in the future" | Pending |
| B-101 | Couple session — spouse required | P2 | Enable couple session, leave spouse empty → Zod: "Spouse details are required" | Pending |
| B-102 | Spouse phone — 10 digits (frontend Zod) | P2 | Enter invalid phone → Zod regex: "Spouse phone must be 10 digits" | Pending |
| B-103 | Spouse — backend validation | P2 | Backend: phone_number + country_code required for new spouse | Pending |
B.14 Step 2 — Navigation
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-105 | Back to Step 1 | P0 | Click "Back" → returns to Step 1, all data preserved | ✅ PASS |
| B-106 | Change doctor in Step 1 → affects Step 2 | P1 | Go back, change doctor, advance → Step 2 data preserved, doctor updated | ✅ PASS |
B.15 Submit Booking
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| B-110 | Submit — existing patient (WALKIN) | P0 | Fill all fields → click "Add Appointment" → API call with patient_id | ✅ PASS |
| B-111 | Submit — new patient (WALKIN) | P0 | Fill all fields → click "Add Appointment" → API call with patient_params | ✅ PASS |
| B-112 | Submit — ONCALL with time slot | P0 | ONCALL mode, date+time+doctor → API call with start_time (UTC, clinic timezone) | ✅ PASS |
| B-113 | Submit — TELEMEDICINE with payment | P1 | TELEMEDICINE + payment option → API call includes payment_option | ✅ PASS |
| B-114 | Submit — success toast (existing patient) | P0 | Toast: "Appointment created successfully" + "The appointment has been booked." | ✅ PASS |
| B-115 | Submit — success toast (new patient) | P0 | Toast: "Appointment created successfully" + "New patient profile created and appointment booked." | ✅ PASS |
| B-116 | Submit — success toast (cross-tenant) | P1 | In-Kulcare patient: "Patient imported from another clinic and appointment booked." | ✅ PASS |
| B-117 | Submit — button loading state | P0 | Click Submit → spinner + "Adding..." text, button disabled | ✅ PASS |
| B-118 | Submit — modal closes after success | P0 | 500ms after success toast → modal auto-closes, queue refetches | ✅ PASS |
| B-119 | Submit — API error | P1 | API returns error → error toast, form stays open for retry | ✅ PASS |
| B-120 | Submit — facility not available | P1 | No facility_id → toast: "Facility information not available" | ✅ PASS |
| B-121 | Submit — couple session | P2 | Enable couple session, fill spouse → API includes couple_session + spouse_details | ✅ PASS |
FLOW C: Appointments Calendar Page
Component: AppointmentsPage.tsx
URL: /cms/appointments
C.1 Calendar Views
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| C-01 | Month view — default | P0 | Page loads with month calendar view | ✅ PASS |
| C-02 | Week view | P0 | Switch to Week view → shows 7-day grid | ✅ PASS |
| C-03 | Day view | P0 | Switch to Day view → shows hourly slots | ✅ PASS |
| C-04 | Today button | P0 | Click Today → resets to current date | ✅ PASS |
| C-05 | Navigate months | P0 | ← → arrows navigate between months/weeks | ✅ PASS |
| C-06 | Date with appointments | P0 | Dates with appointments show indicators/dots | ✅ PASS |
C.2 Doctor Filter (Calendar)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| C-10 | Filter by doctor | P0 | Select doctor → calendar shows only that doctor's appointments | ✅ PASS |
| C-11 | Clear doctor filter | P0 | Select "All" → shows all practitioners | ✅ PASS |
C.3 Appointments Listing Sidebar
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| C-20 | Click date → listing | P0 | Click a date → right sidebar shows appointments for that date | ✅ PASS |
| C-21 | Listing — appointment details | P0 | Shows patient name, time, type, status for each appointment | ✅ PASS |
| C-22 | Listing — empty date | P0 | Click date with no appointments → "No appointments" message | ✅ PASS |
| C-23 | Listing — search | P1 | Search within appointment listing | ✅ PASS |
C.4 Add Booking from Calendar (Scheduled Mode)
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| C-30 | Click empty slot | P0 | Click empty time slot → AddAppointmentModal opens with mode='scheduled' | ✅ PASS |
| C-31 | Modal pre-fills date/time | P0 | Selected date + time slot pre-filled in Step 1 | ✅ PASS |
| C-32 | Scheduled mode — time slot required | P0 | ONCALL mode: must select time slot to proceed | ✅ PASS |
| C-33 | Submit scheduled booking | P0 | Fill steps → Submit → appointment appears on calendar | ✅ PASS |
C.5 Edit Appointment
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| C-40 | Click appointment → edit | P0 | Click existing appointment → UpdateAppointmentModal opens | ✅ PASS |
| C-41 | Edit appointment fields | P0 | Update time/doctor/status → Save | ✅ PASS |
| C-42 | Cancel edit | P0 | Close modal without saving → no changes | ✅ PASS |
FLOW D: Multi-Clinic Scoping (Queue & Appointments)
Scope: All components use currentFacilityId from useProfile() for API calls.
Switch: Admin switches facility via header dropdown → currentFacilityId changes → all data refetches.
D.1 Queue — Facility Scoping
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| MC-01 | Queue shows only current facility patients | P0 | Patients in queue belong to current facility only | ✅ PASS |
| MC-02 | Switch facility → queue changes | P0 | Switch Clinic A → Clinic B → different patients shown | ✅ PASS |
| MC-03 | Queue stats per facility | P0 | Stats (Total/Waiting/In Progress/Completed) are facility-scoped | ✅ PASS |
| MC-04 | Search scoped to facility | P0 | Search in Clinic A → only finds Clinic A patients | ✅ PASS |
| MC-05 | Practitioner filter — facility doctors only | P0 | Doctor dropdown lists only practitioners in current facility | ✅ PASS |
| MC-06 | Past date queue per facility | P1 | Switch facility → past date shows only that facility's history | ✅ PASS |
| MC-07 | Pagination per facility | P1 | Clinic A page 2 ≠ Clinic B page 2 | ✅ PASS |
D.2 Add Booking — Facility Assignment
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| MC-10 | Booking created in current facility | P0 | Book in Clinic A → appointment appears only in Clinic A queue | ✅ PASS |
| MC-11 | Booking from Clinic B → not in Clinic A | P0 | Book in Clinic B → switch to Clinic A → patient NOT visible | ✅ PASS |
| MC-12 | Modal shows current facility tenant name | P1 | Modal header displays tenant name | ✅ PASS |
| MC-13 | Same patient — separate facility bookings | P1 | Book same patient in both clinics → separate appointments per clinic | ✅ PASS |
D.3 Appointments Calendar — Facility Scoping
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| MC-20 | Calendar shows only current facility appointments | P0 | Switch facility → calendar appointments change | ✅ PASS |
| MC-21 | Doctor filter per facility | P0 | Calendar doctor dropdown is facility-scoped | ✅ PASS |
D.4 Queue Actions — Facility Scoping
| # | Test Case | Priority | Expected | Status |
|---|---|---|---|---|
| MC-30 | Start Encounter — correct facility | P0 | Start encounter from Clinic A → encounter in Clinic A | ✅ PASS |
| MC-31 | Billing — correct facility context | P1 | Billing panel uses current facility | ✅ PASS |
| MC-32 | Status change — per facility | P0 | Change status in Clinic A → only affects Clinic A appointment | ✅ PASS |
Test Summary
| Flow | Test Cases | Tested | Passed | Failed |
|---|---|---|---|---|
| A — Queue Page | 63 | 0 | 0 | 0 |
| B — Add Booking Modal | 83 | 0 | 0 | 0 |
| C — Appointments Calendar | 17 | 0 | 0 | 0 |
| D — Multi-Clinic Scoping | 16 | 0 | 0 | 0 |
| Total | 179 | 0 | 0 | 0 |
Test Execution Order
- Flow B: Add Booking Modal (B-01 to B-121) — 83 tests ✅ Done
- Flow A: Queue Page (Q-01 to Q-143) — 63 tests ← NOW
- Flow D: Multi-Clinic Scoping (MC-01 to MC-32) — 16 tests
- Flow C: Appointments Calendar (C-01 to C-42) — 17 tests
Status: Pending Last Updated: 2026-06-17