Session 9: Patient Management - List Page
Date: 2026-06-17
Module: Clinic App - Patient Management (Section 1: Patient List)
URL: https://practice-stg-new.kulcare.com/cms/patients
Test Plan: test-plans/clinic-app/patient-management.md
Status: ✅ Complete
📊 Test Results Summary
| Section | Tested | ✅ Passed | ❌ Failed | ⏭️ Skipped | Pending |
|---|---|---|---|---|---|
| 1.1 List Display | 5 | 5 | 0 | 0 | 0 |
| 1.2 Search & Filter | 6 | 4 | 2 | 0 | 1 |
| 1.3 Date Range | 2 | 0 | 1 | 1 | 0 |
| 1.4 Pagination | 5 | 5 | 0 | 0 | 0 |
| 1.5 Table Actions | 3 | 2 | 1 | 0 | 0 |
| 1.6 Header Actions | 4 | 3 | 1 | 0 | 2 |
| 1.7 Table Density | 2 | 2 | 0 | 0 | 0 |
| Total | 27 | 21 | 5 | 1 | 3 |
🐛 Bugs Found
| Bug # | Test ID | Description | Severity | Status |
|---|---|---|---|---|
| 31 | LIST-11 | Partial name works, full name with space returns no results | 🟡 High | Open |
| 35 | LIST-20 | Date range filter UI not wired to API params | 🟠 Medium | Open |
| 36 | LIST-12 | Phone search returns no results — search only queries name fields | 🟡 High | NEW |
| 37 | LIST-51 | Export report API fails — EFS permission denied on staging | 🟡 High | NEW |
| 38 | LIST-42 | Cross-clinic patients miss patient_facility_id — View link broken | 🟡 High | NEW |
🔍 Detailed Test Results
1.1 List Display (5/5 Passed ✅)
| # | Test Case | Result | Notes |
|---|---|---|---|
| LIST-01 | View patient list | ✅ PASS | Shows paginated list from GET /clinical/patients |
| LIST-02 | View stats cards | ✅ PASS | Shows Total, New, Active, Avg Visits cards |
| LIST-03 | Empty state - no patients | ✅ PASS | Shows "No patients" message when API returns empty |
| LIST-04 | Loading state | ✅ PASS | Shows loading spinner during API fetch |
| LIST-05 | Error state | ✅ PASS | Shows error message with retry on API failure |
1.2 Search & Filter (4/6 Passed, 2 Failed)
| # | Test Case | Result | Bug | Notes |
|---|---|---|---|---|
| LIST-10 | Search by name | ✅ PASS | - | Returns matching patients by name |
| LIST-11 | Search by partial name | ❌ FAIL | #31 | "moh" works, "mohit" works, "mohit goyal" (with space) returns no results |
| LIST-12 | Search by phone | ❌ FAIL | #36 | ?search=988 returns no results — phone column not in search scope |
| LIST-13 | Search by MRN | ⏭️ Pending | - | Not tested |
| LIST-14 | Clear search | ✅ PASS | - | Clear search param resets to full list |
| LIST-15 | No results | ✅ PASS | - | Invalid search shows "No results" message |
| LIST-16 | Debounce search | ✅ PASS | - | ~300ms debounce reduces excessive API calls |
1.3 Date Range Filter (0/2 Passed)
| # | Test Case | Result | Bug | Notes |
|---|---|---|---|---|
| LIST-20 | Filter by birth date range | ❌ FAIL | #35 | Date filter UI exists but birth_date_from/birth_date_to params NOT sent to API |
| LIST-21 | Clear date filter | ⏭️ Skipped | - | Can't test clear when filter was never set (params not wired) |
1.4 Pagination (5/5 Passed ✅)
| # | Test Case | Result | Notes |
|---|---|---|---|
| LIST-30 | Next page | ✅ PASS | Click Next → Shows page 2 |
| LIST-31 | Previous page | ✅ PASS | Click Previous → Returns to page 1 |
| LIST-32 | Jump to page | ✅ PASS | Enter page number → Shows that page |
| LIST-33 | Change page size | ✅ PASS | Select 10/25/50/100 → Updates results |
| LIST-34 | Total count displayed | ✅ PASS | Shows "Showing X of Y" in pagination |
1.5 Table Actions (2/3 Passed, 1 Failed)
| # | Test Case | Result | Bug | Notes |
|---|---|---|---|---|
| LIST-40 | Click Edit (pencil) | ✅ PASS | - | Uses patient.id in URL — works cross-clinic |
| LIST-41 | Click Print (printer) | ✅ PASS | - | Opens PatientPreviewModal |
| LIST-42 | Click View (eye) | ❌ FAIL | #38 | Uses patient.patient_facility_id → null for cross-clinic patients |
1.6 Header Actions (3/4 Passed, 1 Failed)
| # | Test Case | Result | Bug | Notes |
|---|---|---|---|---|
| LIST-50 | Click Export | ✅ PASS | - | Opens ExportReportsModal |
| LIST-51 | Confirm export | ❌ FAIL | #37 | API returns 422 — EFS permission denied on staging |
| LIST-52 | Click Upload | ✅ PASS | - | Opens PatientUploadModal (button exists in PatientsPage.tsx) |
| LIST-53 | Upload valid file | ⏭️ Pending | - | Not tested |
| LIST-54 | Upload invalid file | ⏭️ Pending | - | Not tested |
| LIST-55 | Click Add Patient | ✅ PASS | - | Opens AddPatientModal |
1.7 Table Density (2/2 Passed ✅)
| # | Test Case | Result | Notes |
|---|---|---|---|
| LIST-60 | Compact view | ✅ PASS | Toggle shows more rows, less spacing |
| LIST-61 | Comfortable view | ✅ PASS | Toggle shows less rows, more spacing |
🐛 Bug Details
Bug #31: Full Name Search with Space Returns No Results (Reconfirmed)
| Field | Value |
|---|---|
| Severity | 🟡 High |
| Bug # | 31 |
| Status | Open |
Description: Previously found in federated search (Session 08). Reconfirmed on patient list search. Searching by partial name ("moh", "mohit") works, but adding a space ("mohit goyal") returns no results.
Root Cause: Search scope uses first_name ILIKE '%q%' OR last_name ILIKE '%q%' — splitting on space means "mohit goyal" becomes two separate LIKE clauses that don't match any single column.
Impact: Users who type full names (common behavior) get no results even when patient exists.
Bug #35: Date Range Filter Not Wired to API (Reconfirmed)
| Field | Value |
|---|---|
| Severity | 🟠 Medium |
| Bug # | 35 |
| Status | Open |
Description:
Previously documented in Session 08. Reconfirmed — date filter UI exists on the patient list but birth_date_from and birth_date_to params are not included in the API query params sent by PatientsPage.tsx.
🆕 Bug #36: Phone Search Returns No Results
| Field | Value |
|---|---|
| Severity | 🟡 High |
| Bug # | 36 |
| Status | Open |
Description:
Searching by phone number on the patient list returns no results. The patient list search API (GET /clinical/patients?search=) only queries first_name and last_name — phone_number is not in the search scope.
API Evidence:
curl 'https://api-stg.kulcare.com/clinical/patients?search=988&page=1&per_page=10' \
-H 'Authorization: Bearer <token>'
# Returns: empty results, even though patients with phone containing "988" existRoot Cause:
Clinical::PatientsController#index search scope:
# Current (suspected):
scope.where('first_name ILIKE :q OR last_name ILIKE :q', q: "%#{search}%")
# Should be:
scope.where('first_name ILIKE :q OR last_name ILIKE :q OR phone_number ILIKE :q', q: "%#{search}%")Impact: Staff/doctors cannot find patients by phone number — the most common search method in clinics.
🆕 Bug #37: Export Report API Fails — EFS Permission Denied
| Field | Value |
|---|---|
| Severity | 🟡 High |
| Bug # | 37 |
| Status | Open |
Description:
Patient export report (POST /clinical/patients/generate_report) fails on staging due to filesystem permission error. Ruby's Dir.mkdir cannot create the EFS directory.
API Response (422):
{
"data": null,
"error": {
"code": "validation_failed",
"message": "Permission denied @ dir_s_mkdir - /opt/kulcare-efs-stg/patient_reports_staging",
"details": [{
"field": "base",
"message": "Permission denied @ dir_s_mkdir - /opt/kulcare-efs-stg/patient_reports_staging"
}]
}
}Root Cause:
Staging infrastructure — /opt/kulcare-efs-stg/patient_reports_staging either not mounted or Rails process (deploy user) lacks write permissions. This is not a code bug — the API flow is correct, the filesystem is misconfigured only on staging.
Fix (DevOps):
# On staging Rails server:
sudo mkdir -p /opt/kulcare-efs-stg/patient_reports_staging
sudo chown deploy:deploy /opt/kulcare-efs-stg/patient_reports_stagingCode-level hardening (optional):
# Add fallback to /tmp if EFS unavailable
def report_dir
dir = Rails.root.join('tmp', 'patient_reports')
FileUtils.mkdir_p(dir)
dir
rescue Errno::EACCES
Rails.logger.warn("EFS unavailable, falling back to /tmp")
dir = Rails.root.join('tmp', 'patient_reports_fallback')
FileUtils.mkdir_p(dir)
dir
end✅ Confirmed Working Features
| Feature | Status | Notes |
|---|---|---|
| Patient list with pagination | ✅ Works | GET /clinical/patients returns paginated results |
| Stats cards (Total, New, Active, Avg Visits) | ✅ Works | Displayed correctly |
| Empty/loading/error states | ✅ Works | All three states handled properly |
| Name search | ✅ Works | Partial name matching works |
| Clear search | ✅ Works | Resets to full list |
| Debounce search | ✅ Works | ~300ms delay prevents excessive calls |
| Pagination (next/prev/jump/size) | ✅ Works | All pagination controls functional |
| Total count display | ✅ Works | "Showing X of Y" format |
| Edit action (pencil) | ✅ Works | Navigates to edit page |
| Print action (printer) | ✅ Works | Opens PatientPreviewModal |
| View action (eye) | ✅ Works | Opens patient detail page |
| Export modal | ✅ Works | Modal opens correctly |
| Upload modal | ✅ Works | Modal opens (PatientUploadModal in code) |
| Add Patient modal | ✅ Works | Opens AddPatientModal |
| Compact/Comfortable density | ✅ Works | Toggle functions correctly |
📋 Pending Tests (Section 1)
| Test ID | Reason |
|---|---|
| LIST-13 | Search by MRN — not tested |
| LIST-53 | Upload valid file — not tested |
| LIST-54 | Upload invalid file — not tested |
📝 Notes
- Upload flow exists in code —
PatientUploadModalis imported and wired inPatientsPage.tsx(line 47, 529-534). The upload button may be in the header actions area. - Bug #31 and #36 share a root cause — the patient list search scope is not comprehensive. Both name-with-space and phone search fail because the query doesn't handle multi-word names or phone columns.
- Bug #37 is staging-only — the export API code is correct; it's a deployment/filesystem issue. Production may not be affected.
Session Status: ✅ Complete Last Updated: 2026-06-17 Next: Phase 2 — Patient View Page (VIEW-01 to VIEW-51)
🆕 Bug #38: Cross-Clinic Patient Flow Incomplete — No Facility Record Created
| Field | Value |
|---|---|
| Severity | 🟡 High |
| Bug # | 38 |
| Test ID | LIST-42 |
| Status | Open |
Description: Cross-clinic flow is incomplete. When a patient from Clinic A appears in Clinic B's list:
- View (👁️) broken:
patient_facility_id: null→/cms/patient/{id}/null - Appointment booked without facility context:
FindOrCreateServicereturnspatient_facility: nilfor cross-clinic patients. Appointment model accepts it as optional, so booking "succeeds" but has no facility link. - Edit (✏️) works: uses
patient.idonly
Root Cause: No auto-registration — Clinical::PatientFacility record is never created for the current clinic when a cross-clinic patient is accessed.
Recommended Fix: Auto-create facility record in FindOrCreateService#find_by_id when find_patient_facility_by_patient returns nil. Standard healthcare pattern: providing care = establishing relationship.