fix(abdm): unify opd consultation care contexts, prevent duplicates and premature fhir ready on unfilled booking

This commit is contained in:
DevSoftTech Server 2026-10-06 15:00:33 +05:30
parent 045f4c5116
commit d1daefde4e
4 changed files with 368 additions and 66 deletions

View File

@ -6266,16 +6266,38 @@ class AbdmGateway extends BaseController
if (! empty($pr)) {
$visitDate = ! empty($pr['date_opd_visit']) ? $pr['date_opd_visit'] : date('Y-m-d', strtotime((string) ($pr['p_datetime'] ?? 'now')));
$dateStr = date('d M Y', strtotime((string) $visitDate));
$cleanDate = str_replace('-', '', (string) $visitDate);
$sessionId = ! empty($pr['session_id']) ? $pr['session_id'] : $pr['id'];
$ccRef = 'OPD-' . $patientId . '-S' . $sessionId . '-' . str_replace('-', '', (string) $visitDate);
$display = 'OPConsultRecord - ' . $dateStr;
$ccRef = 'OPD-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
$docName = '';
$opdStatus = 0;
$opdId = ! empty($pr['opd_id']) ? (int) $pr['opd_id'] : 0;
if ($opdId > 0 && $this->db->tableExists('opd_master')) {
$om = $this->db->table('opd_master')->select('doc_name, opd_status')->where('opd_id', $opdId)->get(1)->getRowArray();
if (! empty($om)) {
$docName = trim((string) ($om['doc_name'] ?? ''));
$opdStatus = (int) ($om['opd_status'] ?? 0);
}
}
$display = 'OPConsultRecord - ' . ($docName !== '' ? 'Dr. ' . $docName . ' - ' : '') . $dateStr;
$hasFindings = trim((string) ($pr['complaints'] ?? '')) !== ''
|| trim((string) ($pr['diagnosis'] ?? '')) !== ''
|| trim((string) ($pr['Provisional_diagnosis'] ?? '')) !== ''
|| trim((string) ($pr['Finding_Examinations'] ?? '')) !== ''
|| trim((string) ($pr['advice'] ?? '')) !== ''
|| (! empty($pr['diagnosis_json']) && trim((string) $pr['diagnosis_json']) !== '[]');
$isReady = ($opdStatus === 2) || $hasFindings;
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'display' => $display,
'record_type' => 'OPConsultRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_fhir_ready' => $isReady,
'is_primary' => true,
]);
}
@ -6473,16 +6495,161 @@ class AbdmGateway extends BaseController
}
}
// 4. Query opd_prescription (OPD consultations, prescriptions & wellness records)
if ($this->db->tableExists('opd_prescription')) {
$prescRows = $this->db->table('opd_prescription')
// 4. Query OPD Consultations (unified from opd_master & opd_prescription)
$processedOpdIds = [];
if ($this->db->tableExists('opd_master')) {
$omFields = $this->db->getFieldNames('opd_master') ?? [];
$selectCols = ['opd_id', 'p_id'];
if (in_array('apointment_date', $omFields, true)) $selectCols[] = 'apointment_date';
if (in_array('opd_book_date', $omFields, true)) $selectCols[] = 'opd_book_date';
if (in_array('doc_name', $omFields, true)) $selectCols[] = 'doc_name';
if (in_array('doc_id', $omFields, true)) $selectCols[] = 'doc_id';
if (in_array('opd_status', $omFields, true)) $selectCols[] = 'opd_status';
$opdMasterRows = $this->db->table('opd_master')
->select(implode(', ', $selectCols))
->where('p_id', $patientId)
->orderBy('id', 'DESC')
->orderBy('opd_id', 'DESC')
->limit(30)
->get()
->getResultArray();
foreach ($prescRows as $pr) {
foreach ($opdMasterRows as $om) {
$opdId = (int) $om['opd_id'];
$processedOpdIds[$opdId] = true;
$visitRaw = ! empty($om['apointment_date']) && $om['apointment_date'] !== '0000-00-00 00:00:00'
? $om['apointment_date']
: (! empty($om['opd_book_date']) ? $om['opd_book_date'] : date('Y-m-d'));
$visitDate = date('Y-m-d', strtotime((string) $visitRaw));
$dateStr = date('d M Y', strtotime($visitDate));
$cleanDate = str_replace('-', '', $visitDate);
// Find associated prescription row if exists
$pr = null;
if ($this->db->tableExists('opd_prescription')) {
$pr = $this->db->table('opd_prescription')
->where('opd_id', $opdId)
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
}
$sessionId = ! empty($pr['session_id']) ? (int) $pr['session_id'] : (! empty($pr['id']) ? (int) $pr['id'] : $opdId);
// Primary care context reference matches storePrescriptionFhirBundle
$ccRef = 'OPD-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
// Alias reference prevents duplicate if opd_id differs from session_id
$aliasRef = 'OPD-' . $patientId . '-S' . $opdId . '-' . $cleanDate;
$docName = trim((string) ($om['doc_name'] ?? ''));
$display = 'OPConsultRecord - ' . ($docName !== '' ? 'Dr. ' . $docName . ' - ' : '') . $dateStr;
// Determine FHIR readiness: check stored doc, health_records, status, or clinical findings
$hasStoredDoc = false;
if ($this->db->tableExists('opd_fhir_documents')) {
$hasStoredDoc = $this->db->table('opd_fhir_documents')
->groupStart()
->where('opd_id', $opdId)
->orWhere('opd_session_id', $sessionId)
->orWhere('opd_session_id', $opdId)
->groupEnd()
->whereIn('bundle_type', ['OPConsultRecord', 'MedicationRequestBundle', 'PrescriptionRecord'])
->countAllResults() > 0;
}
$hasHr = false;
if ($this->db->tableExists('health_records')) {
$hasHr = $this->db->table('health_records')
->groupStart()
->where('care_context_reference', $ccRef)
->orWhere('care_context_reference', $aliasRef)
->groupEnd()
->countAllResults() > 0;
}
$statusVal = (int) ($om['opd_status'] ?? 0);
$isVisitDone = ($statusVal === 2);
$hasFindings = false;
if (! empty($pr)) {
$hasFindings = trim((string) ($pr['complaints'] ?? '')) !== ''
|| trim((string) ($pr['diagnosis'] ?? '')) !== ''
|| trim((string) ($pr['Provisional_diagnosis'] ?? '')) !== ''
|| trim((string) ($pr['Finding_Examinations'] ?? '')) !== ''
|| trim((string) ($pr['advice'] ?? '')) !== ''
|| trim((string) ($pr['investigation'] ?? '')) !== ''
|| (! empty($pr['diagnosis_json']) && trim((string) $pr['diagnosis_json']) !== '[]')
|| (! empty($pr['complaint_snomed_json']) && trim((string) $pr['complaint_snomed_json']) !== '[]');
}
// Check prescribed medications
$hasMeds = false;
$tblPresc = $this->db->tableExists('opd_prescrption_prescribed') ? 'opd_prescrption_prescribed' : ($this->db->tableExists('opd_prescription_prescribed') ? 'opd_prescription_prescribed' : null);
if ($tblPresc !== null && ! empty($pr['id'])) {
$hasPreIdCol = $this->db->fieldExists('opd_pre_id', $tblPresc);
$hasPrescIdCol = $this->db->fieldExists('prescription_id', $tblPresc);
$col = $hasPreIdCol ? 'opd_pre_id' : ($hasPrescIdCol ? 'prescription_id' : null);
if ($col !== null) {
$hasMeds = ($this->db->table($tblPresc)->where($col, (int) $pr['id'])->countAllResults() > 0);
}
}
$isReady = $hasStoredDoc || $hasHr || $isVisitDone || $hasFindings || $hasMeds;
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'alias_reference' => $aliasRef,
'display' => $display,
'record_type' => 'OPConsultRecord',
'patient_id' => $patientId,
'is_fhir_ready' => $isReady,
'is_primary' => false,
]);
$seenRefs[$aliasRef] = true;
// 4b. Prescription Record (if prescribed medicines exist)
if ($hasMeds) {
$prescCcRef = 'PRESC-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
$prescDisplay = 'Prescription - ' . ($docName !== '' ? 'Dr. ' . $docName . ' - ' : '') . $dateStr;
$addContext([
'careContextId' => $prescCcRef,
'referenceNumber' => $prescCcRef,
'display' => $prescDisplay,
'record_type' => 'PrescriptionRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
// 4c. Wellness Record (if vitals exist)
$hasVitals = ! empty($pr) && (! empty($pr['bp']) || ! empty($pr['pulse']) || ! empty($pr['temp']) || ! empty($pr['spo2']) || ! empty($pr['weight']) || ! empty($pr['height']));
if ($hasVitals) {
$wellCcRef = 'WELLNESS-' . $sessionId . '-' . $cleanDate;
$wellDisplay = 'Wellness Record - ' . $dateStr;
$addContext([
'careContextId' => $wellCcRef,
'referenceNumber' => $wellCcRef,
'display' => $wellDisplay,
'record_type' => 'WellnessRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
}
}
// 4d. Fallback: Any standalone opd_prescription not already covered by opd_master
if ($this->db->tableExists('opd_prescription')) {
$builder = $this->db->table('opd_prescription')->where('p_id', $patientId);
if (! empty($processedOpdIds)) {
$builder->whereNotIn('opd_id', array_keys($processedOpdIds));
}
$orphanPrescs = $builder->orderBy('id', 'DESC')->limit(10)->get()->getResultArray();
foreach ($orphanPrescs as $pr) {
$visitDate = ! empty($pr['date_opd_visit'])
? $pr['date_opd_visit']
: date('Y-m-d', strtotime((string) ($pr['p_datetime'] ?? 'now')));
@ -6490,7 +6657,9 @@ class AbdmGateway extends BaseController
$cleanDate = str_replace('-', '', $visitDate);
$sessionId = ! empty($pr['session_id']) ? $pr['session_id'] : $pr['id'];
// 4a. Consultation Record
$hasFindings = trim((string) ($pr['complaints'] ?? '')) !== ''
|| trim((string) ($pr['diagnosis'] ?? '')) !== ''
|| trim((string) ($pr['Provisional_diagnosis'] ?? '')) !== '';
$ccRef = 'OPD-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
$display = 'OPConsultRecord - ' . $dateStr;
$addContext([
@ -6499,14 +6668,14 @@ class AbdmGateway extends BaseController
'display' => $display,
'record_type' => 'OPConsultRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_fhir_ready' => $hasFindings,
'is_primary' => false,
]);
// 4b. Prescription Record (if prescribed medicines exist)
// Also check if prescribed medicines exist for orphan
$hasMeds = false;
$tblPresc = $this->db->tableExists('opd_prescrption_prescribed') ? 'opd_prescrption_prescribed' : ($this->db->tableExists('opd_prescription_prescribed') ? 'opd_prescription_prescribed' : null);
if ($tblPresc !== null) {
if ($tblPresc !== null && ! empty($pr['id'])) {
$hasPreIdCol = $this->db->fieldExists('opd_pre_id', $tblPresc);
$hasPrescIdCol = $this->db->fieldExists('prescription_id', $tblPresc);
$col = $hasPreIdCol ? 'opd_pre_id' : ($hasPrescIdCol ? 'prescription_id' : null);
@ -6528,7 +6697,7 @@ class AbdmGateway extends BaseController
]);
}
// 4c. Wellness Record (if vitals exist)
// Also check if vitals exist for orphan
$hasVitals = ! empty($pr['bp']) || ! empty($pr['pulse']) || ! empty($pr['temp']) || ! empty($pr['spo2']) || ! empty($pr['weight']) || ! empty($pr['height']);
if ($hasVitals) {
$wellCcRef = 'WELLNESS-' . $sessionId . '-' . $cleanDate;
@ -6546,39 +6715,6 @@ class AbdmGateway extends BaseController
}
}
// 4b. Query opd_master (OPD consultation appointments/visits not already in prescriptions)
if ($this->db->tableExists('opd_master')) {
$opdMasterRows = $this->db->table('opd_master')
->select('opd_id, p_id, apointment_date, opd_book_date, doc_name')
->where('p_id', $patientId)
->orderBy('opd_id', 'DESC')
->limit(30)
->get()
->getResultArray();
foreach ($opdMasterRows as $om) {
$visitRaw = ! empty($om['apointment_date']) && $om['apointment_date'] !== '0000-00-00 00:00:00'
? $om['apointment_date']
: (! empty($om['opd_book_date']) ? $om['opd_book_date'] : date('Y-m-d'));
$visitDate = date('Y-m-d', strtotime((string) $visitRaw));
$dateStr = date('d M Y', strtotime($visitDate));
$opdId = (int) $om['opd_id'];
$ccRef = 'OPD-' . $patientId . '-S' . $opdId . '-' . str_replace('-', '', $visitDate);
$docName = trim((string) ($om['doc_name'] ?? ''));
$display = 'OPConsultRecord - ' . ($docName !== '' ? 'Dr. ' . $docName . ' - ' : '') . $dateStr;
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'display' => $display,
'record_type' => 'OPConsultRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
}
// 5. Query ipd_master (Discharge summaries)
if ($this->db->tableExists('ipd_master')) {
$ipdRows = $this->db->table('ipd_master')
@ -11670,12 +11806,15 @@ class AbdmGateway extends BaseController
foreach ($careContextsFull as $cc) {
$ref = $cc['referenceNumber'] ?? $cc['careContextId'] ?? '';
$isLinked = in_array($ref, $linkedRefs, true);
if (! $isLinked && ! empty($cc['alias_reference'])) {
$isLinked = in_array($cc['alias_reference'], $linkedRefs, true);
}
$contextsList[] = [
'ref' => $ref,
'display' => $cc['display'] ?? $ref,
'hi_type' => $cc['record_type'] ?? 'OPConsultRecord',
'is_linked' => $isLinked,
'is_fhir_ready' => (bool) ($cc['is_fhir_ready'] ?? true),
'is_fhir_ready' => (bool) ($cc['is_fhir_ready'] ?? false),
'is_primary' => (bool) ($cc['is_primary'] ?? false),
];
}

View File

@ -5013,13 +5013,93 @@ class Opd_prescription extends BaseController
}
$row = $builder->orderBy('id', 'DESC')->get(1)->getRowArray();
if (empty($row) && $sessionId > 0) {
$row = $this->db->table('opd_fhir_documents')
->where('opd_id', (int) $sessionId)
->whereIn('bundle_type', ['OPConsultRecord', 'MedicationRequestBundle', 'PrescriptionRecord'])
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
}
if (empty($row) && $sessionId > 0) {
$row = $this->db->table('opd_fhir_documents')
->where('opd_session_id', (int) $sessionId)
->whereIn('bundle_type', ['OPConsultRecord', 'MedicationRequestBundle', 'PrescriptionRecord'])
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
}
if (empty($row) && $this->db->tableExists('health_records')) {
$hr = $this->db->table('health_records')
->groupStart()
->where('entity_id', (string) $opdId)
->orWhere('entity_id', (string) $sessionId)
->orLike('care_context_reference', 'S' . $sessionId . '-')
->orLike('care_context_reference', 'S' . $opdId . '-')
->groupEnd()
->whereIn('hi_type', ['OPConsultRecord', 'PrescriptionRecord'])
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
if (! empty($hr['record_data'])) {
$dec = json_decode((string) $hr['record_data'], true);
if (is_array($dec)) {
$row = [
'bundle_json' => $hr['record_data'],
'bundle_type' => $hr['hi_type'] ?? 'OPConsultRecord',
'opd_id' => (int) ($hr['entity_id'] ?? $opdId),
'opd_session_id' => $sessionId,
];
}
}
}
if (empty($row)) {
return $this->response->setStatusCode(404)->setJSON([
'status' => 'error',
'message' => 'FHIR bundle not found.',
'opd_id' => (int) $opdId,
'opd_session_id' => (int) $sessionId,
]);
$candidateOpdId = ($opdId > 0 && $this->db->tableExists('opd_master') && $this->db->table('opd_master')->where('opd_id', $opdId)->countAllResults() > 0)
? $opdId
: ($sessionId > 0 && $this->db->tableExists('opd_master') && $this->db->table('opd_master')->where('opd_id', $sessionId)->countAllResults() > 0 ? $sessionId : 0);
if ($candidateOpdId > 0) {
$hasFindings = false;
$pRow = $this->db->table('opd_prescription')->where('opd_id', $candidateOpdId)->orderBy('id', 'DESC')->get(1)->getRowArray();
if (! empty($pRow)) {
$hasFindings = trim((string) ($pRow['complaints'] ?? '')) !== ''
|| trim((string) ($pRow['diagnosis'] ?? '')) !== ''
|| trim((string) ($pRow['Provisional_diagnosis'] ?? '')) !== ''
|| trim((string) ($pRow['advice'] ?? '')) !== '';
}
$omRow = $this->db->table('opd_master')->where('opd_id', $candidateOpdId)->get(1)->getRowArray();
$isDone = ((int) ($omRow['opd_status'] ?? 0) === 2);
if ($hasFindings || $isDone) {
$regen = $this->regenerateFhirBundleInternal($candidateOpdId, $sessionId > 0 ? $sessionId : (int) ($pRow['id'] ?? 0));
if (! empty($regen['ok']) && ! empty($regen['bundle'])) {
$row = [
'bundle_json' => json_encode($regen['bundle']),
'bundle_type' => $regen['bundle_type'] ?? 'OPConsultRecord',
'opd_id' => $candidateOpdId,
'opd_session_id' => $sessionId,
];
}
} else {
return $this->response->setStatusCode(404)->setJSON([
'status' => 'draft',
'message' => 'OPD Consultation is pending. Clinical findings have not been documented by the doctor yet.',
'opd_id' => (int) $opdId,
'opd_session_id' => (int) $sessionId,
]);
}
}
if (empty($row)) {
return $this->response->setStatusCode(404)->setJSON([
'status' => 'error',
'message' => 'FHIR bundle not found.',
'opd_id' => (int) $opdId,
'opd_session_id' => (int) $sessionId,
]);
}
}
$bundleJson = (string) ($row['bundle_json'] ?? '{}');

View File

@ -1317,24 +1317,28 @@
var fhirBadge = isFhir
? '<span class="badge bg-info"><i class="bi bi-file-earmark-code me-1"></i>Ready</span>'
: '<span class="badge bg-light text-muted border">Pending</span>';
: '<span class="badge bg-warning-subtle text-dark border border-warning small"><i class="bi bi-clock me-1"></i>Pending Consult</span>';
// Resolve preview button
// Resolve preview button (only when FHIR bundle is ready)
var fhirPreviewBtn = '';
var opdMatch = ref.match(/^OPD-(\d+)/i);
if (opdMatch) {
var opdId = opdMatch[1];
var sMatch = ref.match(/-S(\d+)-/i);
var sId = sMatch ? sMatch[1] : 0;
var previewUrl = sId > 0
? '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId + '/' + sId
: '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId;
fhirPreviewBtn = '<button type="button" class="btn btn-sm btn-outline-primary py-0 px-2 ms-1 insp-preview-fhir" data-url="' + previewUrl + '" data-title="Care Context ' + ref + ' FHIR"><i class="bi bi-eye me-1"></i>Preview FHIR</button>';
if (isFhir) {
var opdMatch = ref.match(/^OPD-(\d+)/i);
if (opdMatch) {
var opdId = opdMatch[1];
var sMatch = ref.match(/-S(\d+)-/i);
var sId = sMatch ? sMatch[1] : 0;
var previewUrl = sId > 0
? '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId + '/' + sId
: '<?= base_url('Opd_prescription/fhir_bundle_preview') ?>/' + opdId;
fhirPreviewBtn = '<button type="button" class="btn btn-sm btn-outline-primary py-0 px-2 ms-1 insp-preview-fhir" data-url="' + previewUrl + '" data-title="Care Context ' + ref + ' FHIR"><i class="bi bi-eye me-1"></i>Preview FHIR</button>';
}
}
var actionBtn = '';
if (isLinked) {
actionBtn = '<span class="badge bg-success-subtle text-success border border-success-subtle py-1 px-2"><i class="bi bi-check-all me-1"></i>Active</span>';
} else if (!isFhir) {
actionBtn = '<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-2" disabled title="Clinical findings must be documented before linking to ABDM"><i class="bi bi-clock me-1"></i>Draft / Incomplete</button>';
} else {
actionBtn = '<button type="button" class="btn btn-sm btn-outline-success py-0 px-2 btn-insp-link" data-ref="' + hesc(ref) + '" data-type="' + hesc(hiType) + '" data-patient-id="' + pat.id + '" data-patient-name="' + hesc(pat.name) + '" data-abha="' + hesc(pat.abha_address || abhaAddress) + '"><i class="bi bi-link-45deg me-1"></i>Link Context</button>';
}

View File

@ -50,7 +50,15 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
temp TEXT,
spo2 TEXT,
weight TEXT,
height TEXT
height TEXT,
complaints TEXT,
diagnosis TEXT,
Provisional_diagnosis TEXT,
advice TEXT,
investigation TEXT,
Finding_Examinations TEXT,
diagnosis_json TEXT,
complaint_snomed_json TEXT
)");
$db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('opd_prescrption_prescribed') . " (
@ -65,7 +73,8 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
p_id INTEGER,
apointment_date TEXT,
opd_book_date TEXT,
doc_name TEXT
doc_name TEXT,
opd_status INTEGER
)");
$db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('health_records') . " (
@ -532,6 +541,76 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
$this->assertSame('WellnessRecord', $primary['record_type']);
$this->assertTrue($primary['is_primary']);
}
public function testUnfilledOpdConsultShowsSingleDraftRecordWithDoctorName(): void
{
$db = \Config\Database::connect();
$db->table('opd_master')->insert([
'opd_id' => 33793,
'p_id' => 15353,
'apointment_date' => '2026-10-06 14:00:00',
'opd_book_date' => '2026-10-06 14:00:00',
'doc_name' => 'Jayashri Joshi',
'opd_status' => 1, // Booked / waiting (not done)
]);
$db->table('opd_prescription')->insert([
'id' => 33727,
'opd_id' => 33793,
'p_id' => 15353,
'session_id' => 0,
'date_opd_visit' => '2026-10-06',
'complaints' => '',
'diagnosis' => '',
]);
$refGateway = new ReflectionClass($this->gateway);
$method = $refGateway->getMethod('findCareContextsForPatient');
$method->setAccessible(true);
[$v3, $full] = $method->invoke($this->gateway, 15353, 'P26081015353', 'DHAIRYA SINGH BISHT');
// Verify only ONE OPD consult record is returned (no duplicate!)
$opdConsults = array_values(array_filter($full, fn ($c) => ($c['record_type'] ?? '') === 'OPConsultRecord'));
$this->assertCount(1, $opdConsults, 'Should return exactly 1 consolidated OPD consultation record, not duplicates');
$consult = $opdConsults[0];
$this->assertStringContainsString('Dr. Jayashri Joshi', $consult['display'], 'Should include the doctor name');
$this->assertFalse($consult['is_fhir_ready'], 'Unfilled booking should NOT be marked FHIR ready');
}
public function testCompletedOpdConsultShowsFhirReady(): void
{
$db = \Config\Database::connect();
$db->table('opd_master')->insert([
'opd_id' => 33794,
'p_id' => 15353,
'apointment_date' => '2026-10-06 15:00:00',
'opd_book_date' => '2026-10-06 15:00:00',
'doc_name' => 'Jayashri Joshi',
'opd_status' => 2, // Visit Done
]);
$db->table('opd_prescription')->insert([
'id' => 33728,
'opd_id' => 33794,
'p_id' => 15353,
'session_id' => 0,
'date_opd_visit' => '2026-10-06',
'complaints' => 'Fever and Cough',
'diagnosis' => 'Viral Pharyngitis',
]);
$refGateway = new ReflectionClass($this->gateway);
$method = $refGateway->getMethod('findCareContextsForPatient');
$method->setAccessible(true);
[$v3, $full] = $method->invoke($this->gateway, 15353, 'P26081015353', 'DHAIRYA SINGH BISHT');
$opdConsults = array_values(array_filter($full, fn ($c) => ($c['record_type'] ?? '') === 'OPConsultRecord' && str_contains($c['careContextId'], '33728')));
$this->assertCount(1, $opdConsults);
$this->assertTrue($opdConsults[0]['is_fhir_ready'], 'Completed consultation with findings must be marked FHIR ready');
}
}