diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index 272c657..039b073 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -6241,6 +6241,70 @@ class AbdmGateway extends BaseController 'is_primary' => true, ]); } + } elseif ($taskType === 'immunization_record_publish' && $this->db->tableExists('immunization_records')) { + $imm = $this->db->table('immunization_records') + ->select('id, patient_id, vaccine_name, given_date, abdm_care_context_reference') + ->where('id', $taskEntityId) + ->get(1) + ->getRowArray(); + if (! empty($imm)) { + $admDate = ! empty($imm['given_date']) && strtotime((string) $imm['given_date']) > 0 + ? date('Y-m-d', strtotime((string) $imm['given_date'])) + : date('Y-m-d'); + $dateStr = date('d M Y', strtotime($admDate)); + $ccRef = ! empty($imm['abdm_care_context_reference']) + ? trim((string) $imm['abdm_care_context_reference']) + : ('IMM-' . $taskEntityId); + $display = 'ImmunizationRecord - ' . ($imm['vaccine_name'] ?? 'Vaccine') . ' (' . $dateStr . ')'; + + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'display' => $display, + 'record_type' => 'ImmunizationRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => true, + ]); + } + } elseif ($taskType === 'health_document_publish') { + if ($this->db->tableExists('patient_doc')) { + $pdoc = $this->db->table('patient_doc')->where('id', $taskEntityId)->get(1)->getRowArray(); + if (! empty($pdoc)) { + $docDate = ! empty($pdoc['date_issue']) ? $pdoc['date_issue'] : (! empty($pdoc['created_at']) ? $pdoc['created_at'] : date('Y-m-d')); + $dateStr = date('d M Y', strtotime((string) $docDate)); + $ccRef = 'DOC-' . $taskEntityId . '-' . date('Ymd', strtotime((string) $docDate)); + $display = 'HealthDocumentRecord - ' . (! empty($pdoc['doc_name']) ? $pdoc['doc_name'] : 'Medical Document') . ' (' . $dateStr . ')'; + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'display' => $display, + 'record_type' => 'HealthDocumentRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => true, + ]); + } + } + } elseif ($taskType === 'wellness_record_publish') { + if ($this->db->tableExists('opd_prescription')) { + $wopd = $this->db->table('opd_prescription')->where('id', $taskEntityId)->get(1)->getRowArray(); + if (! empty($wopd)) { + $vDate = ! empty($wopd['date_opd_visit']) ? $wopd['date_opd_visit'] : date('Y-m-d'); + $dateStr = date('d M Y', strtotime((string) $vDate)); + $ccRef = 'WELLNESS-' . $patientId . '-S' . $taskEntityId . '-' . date('Ymd', strtotime((string) $vDate)); + $display = 'WellnessRecord - ' . $dateStr; + $addContext([ + 'careContextId' => $ccRef, + 'referenceNumber' => $ccRef, + 'display' => $display, + 'record_type' => 'WellnessRecord', + 'patient_id' => $patientId, + 'is_fhir_ready' => true, + 'is_primary' => true, + ]); + } + } } } @@ -6419,20 +6483,33 @@ class AbdmGateway extends BaseController // 6. Query immunization_records / patient_immunization if exists if ($this->db->tableExists('immunization_records')) { - $immRows = $this->db->table('immunization_records') - ->select('id, patient_id, vaccine_name, given_date, abdm_care_context_reference') - ->where('patient_id', $patientId) - ->orderBy('id', 'DESC') - ->limit(10) + $immFields = $this->db->getFieldNames('immunization_records') ?? []; + $hasStatus = in_array('status', $immFields, true); + $builder = $this->db->table('immunization_records') + ->select('id, patient_id, vaccine_name, given_date, abdm_care_context_reference' . ($hasStatus ? ', status' : '')) + ->where('patient_id', $patientId); + + if ($hasStatus) { + // Prioritize completed records, and don't omit administered ones + $builder->orderBy("CASE WHEN status = 'completed' THEN 1 WHEN given_date IS NOT NULL AND given_date > '1970-01-01' THEN 2 ELSE 3 END", 'ASC', false); + } + $immRows = $builder->orderBy('id', 'DESC') + ->limit(30) ->get() ->getResultArray(); foreach ($immRows as $imm) { - $admDate = ! empty($imm['given_date']) ? date('Y-m-d', strtotime((string) $imm['given_date'])) : date('Y-m-d'); + // If status is present and 'due' (and not given), skip unadministered vaccines + if ($hasStatus && strtolower((string) ($imm['status'] ?? '')) === 'due' && (empty($imm['given_date']) || $imm['given_date'] === '0000-00-00 00:00:00')) { + continue; + } + $admDate = ! empty($imm['given_date']) && strtotime((string) $imm['given_date']) > 0 + ? date('Y-m-d', strtotime((string) $imm['given_date'])) + : date('Y-m-d'); $dateStr = date('d M Y', strtotime($admDate)); $ccRef = ! empty($imm['abdm_care_context_reference']) ? trim((string) $imm['abdm_care_context_reference']) - : ('IMM-' . $imm['id'] . '-' . str_replace('-', '', $admDate)); + : ('IMM-' . $imm['id']); $display = 'ImmunizationRecord - ' . ($imm['vaccine_name'] ?? 'Vaccine') . ' (' . $dateStr . ')'; $addContext([ diff --git a/app/Controllers/AbdmTaskBoard.php b/app/Controllers/AbdmTaskBoard.php index 33bcfa8..a48f499 100644 --- a/app/Controllers/AbdmTaskBoard.php +++ b/app/Controllers/AbdmTaskBoard.php @@ -147,7 +147,7 @@ class AbdmTaskBoard extends BaseController unset($task); } - // 2. Lookup health_records by entity_id + // 2. Lookup health_records by entity_id with strict entity_type / patient_id scoping $latestHr = []; if ($db->tableExists('health_records')) { $entityIds = []; @@ -161,23 +161,93 @@ class AbdmTaskBoard extends BaseController if ($entityIds !== []) { $rows = $db->table('health_records') - ->select('id, entity_type, entity_id, push_status, care_context_reference, linked_at, abdm_txn_id') + ->select('id, patient_id, entity_type, hi_type, entity_id, push_status, care_context_reference, linked_at, abdm_txn_id') ->whereIn('entity_id', $entityIds) ->orderBy('id', 'DESC') ->get() ->getResultArray(); foreach ($rows as $row) { + $pId = (int) ($row['patient_id'] ?? 0); + $eType = strtolower(trim((string) ($row['entity_type'] ?? ''))); + $hiType = strtolower(trim((string) ($row['hi_type'] ?? ''))); $eid = (string) ($row['entity_id'] ?? ''); - if ($eid !== '' && ! isset($latestHr[$eid])) { - $latestHr[$eid] = $row; + if ($eid !== '') { + if ($pId > 0 && $eType !== '') { + $k1 = $pId . '_' . $eType . '_' . $eid; + if (! isset($latestHr[$k1])) { + $latestHr[$k1] = $row; + } + } + if ($pId > 0 && $hiType !== '') { + $k2 = $pId . '_' . $hiType . '_' . $eid; + if (! isset($latestHr[$k2])) { + $latestHr[$k2] = $row; + } + } + if ($eType !== '') { + $k3 = $eType . '_' . $eid; + if (! isset($latestHr[$k3])) { + $latestHr[$k3] = $row; + } + } + if (! isset($latestHr[$eid])) { + $latestHr[$eid] = $row; + } } } } } foreach ($tasks as &$task) { + $pId = (int) ($task['patient_id'] ?? 0); + $taskType = strtolower(trim((string) ($task['task_type'] ?? ''))); + $taskEntityType = strtolower(trim((string) ($task['entity_type'] ?? ''))); $eid = (string) ($task['entity_id'] ?? ''); - $hr = $latestHr[$eid] ?? null; + + // Map task type or entity type to standard health_record entity types / hi_types + $candidateTypes = array_values(array_filter([$taskEntityType])); + if ($taskType === 'immunization_record_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['immunization', 'immunizationrecord']))); + } elseif ($taskType === 'opd_prescription_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['opd', 'opd_prescription', 'opconsultrecord']))); + } elseif ($taskType === 'ipd_discharge_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['ipd', 'ipd_discharge', 'dischargesummaryrecord']))); + } elseif ($taskType === 'lab_report_publish' || $taskType === 'radiology_report_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['lab', 'radiology', 'diagnosticreportrecord']))); + } elseif ($taskType === 'wellness_record_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['opd_vitals', 'opd', 'wellness', 'wellnessrecord']))); + } elseif ($taskType === 'health_document_publish') { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['patient_document', 'doctor_document', 'file_upload_data', 'patient_doc', 'healthdocumentrecord']))); + } elseif (in_array($taskType, ['invoice_publish', 'invoice_record_publish'], true)) { + $candidateTypes = array_values(array_unique(array_merge($candidateTypes, ['invoice', 'opd_invoice', 'charges_invoice', 'ipd_invoice', 'invoicerecord']))); + } + + $hr = null; + // 1) Match with patient_id and entity/hi type + if ($pId > 0) { + foreach ($candidateTypes as $cType) { + $key = $pId . '_' . strtolower($cType) . '_' . $eid; + if (isset($latestHr[$key])) { + $hr = $latestHr[$key]; + break; + } + } + } + // 2) Match with entity/hi type without patient_id + if ($hr === null) { + foreach ($candidateTypes as $cType) { + $key = strtolower($cType) . '_' . $eid; + if (isset($latestHr[$key])) { + $hr = $latestHr[$key]; + break; + } + } + } + // 3) Only fallback to raw $eid if task has no specific taskType or candidateTypes + if ($hr === null && empty($candidateTypes) && isset($latestHr[$eid])) { + $hr = $latestHr[$eid]; + } + $pushStatus = strtolower(trim((string) ($hr['push_status'] ?? ''))); $task['bridge_health_record_id'] = (int) ($hr['id'] ?? 0); $task['bridge_push_status'] = $pushStatus; @@ -185,7 +255,7 @@ class AbdmTaskBoard extends BaseController $careContext = trim((string) ($hr['care_context_reference'] ?? '')); if ($careContext === '') { $resultText = (string) ($task['last_action_result'] ?? ''); - if (preg_match('/(RAD-\d+-\d+|LAB-\d+-\d+|OPD-\d+-\d+|INVOICE-\w+-\d+-\d+|IMM-\d+-\d+|DIS-\d+-\d+)/i', $resultText, $ccm)) { + if (preg_match('/((?:RAD|LAB|OPD|INVOICE|IMM|DISCHARGE|DIS|WELLNESS|DOC)-[A-Za-z0-9_-]+)/i', $resultText, $ccm)) { $careContext = $ccm[1]; } } diff --git a/app/Controllers/Opd_prescription.php b/app/Controllers/Opd_prescription.php index 9585471..4f4fa93 100644 --- a/app/Controllers/Opd_prescription.php +++ b/app/Controllers/Opd_prescription.php @@ -11887,6 +11887,7 @@ class Opd_prescription extends BaseController $masterTable = $this->findExistingTable(['opd_med_master']); $masterById = []; + $masterByName = []; if ($masterTable !== null) { $masterFields = $this->db->getFieldNames($masterTable) ?? []; $masterNameField = $this->resolveFirstField($masterFields, ['item_name', 'med_name']); @@ -11896,14 +11897,19 @@ class Opd_prescription extends BaseController $masterAtcField = $this->resolveFirstField($masterFields, ['atc_code', 'who_atc_code']); $medIds = []; + $medNames = []; foreach ($rows as $row) { $mid = (int) ($row['med_id'] ?? 0); if ($mid > 0) { $medIds[$mid] = $mid; } + $rawName = strtoupper(trim((string) ($row['med_name'] ?? ''))); + if ($rawName !== '') { + $medNames[$rawName] = $rawName; + } } - if (! empty($medIds)) { + if (! empty($medIds) || ! empty($medNames)) { $select = ['id']; if ($masterNameField !== null) { $select[] = $masterNameField . ' as item_name'; @@ -11921,17 +11927,54 @@ class Opd_prescription extends BaseController $select[] = $masterAtcField . ' as atc_code'; } - $masterRows = $this->db->table($masterTable) - ->select(implode(',', $select)) - ->whereIn('id', array_values($medIds)) - ->get() - ->getResultArray(); + $query = $this->db->table($masterTable)->select(implode(',', $select)); + if (! empty($medIds) && ! empty($medNames) && $masterNameField !== null) { + $query->groupStart() + ->whereIn('id', array_values($medIds)) + ->orWhereIn($masterNameField, array_values($medNames)) + ->groupEnd(); + } elseif (! empty($medIds)) { + $query->whereIn('id', array_values($medIds)); + } elseif (! empty($medNames) && $masterNameField !== null) { + $query->whereIn($masterNameField, array_values($medNames)); + } + + $masterRows = $query->get()->getResultArray(); foreach ($masterRows as $mrow) { $mid = (int) ($mrow['id'] ?? 0); if ($mid > 0) { $masterById[$mid] = $mrow; } + $iname = strtoupper(trim((string) ($mrow['item_name'] ?? ''))); + if ($iname !== '') { + $masterByName[$iname] = $mrow; + } + } + } + } + + // Secondary lookup in mst_items for SNOMED CT and generic name + $mstItemsByName = []; + if ($this->db->tableExists('mst_items')) { + $mstNames = []; + foreach ($rows as $row) { + $rawName = strtoupper(trim((string) ($row['med_name'] ?? ''))); + if ($rawName !== '') { + $mstNames[$rawName] = $rawName; + } + } + if (! empty($mstNames)) { + $mstRows = $this->db->table('mst_items') + ->select('item_name, generic_name, snomed_ct_code, snomed_display') + ->whereIn('item_name', array_values($mstNames)) + ->get() + ->getResultArray(); + foreach ($mstRows as $item) { + $k = strtoupper(trim((string) ($item['item_name'] ?? ''))); + if ($k !== '') { + $mstItemsByName[$k] = $item; + } } } } @@ -11944,11 +11987,29 @@ class Opd_prescription extends BaseController $medications = []; foreach ($rows as $row) { $dose = $this->resolveDoseMasterLabel($row['dosage'] ?? '', $doseMap); + if ($dose === '' && ! empty($row['dosage_str'])) { + $dose = trim((string) $row['dosage_str']); + } + $when = $this->resolveDoseMasterLabel($row['dosage_when'] ?? '', $whenMap); + if ($when === '' && ! empty($row['dosage_when_str'])) { + $when = trim((string) $row['dosage_when_str']); + } + $frequency = $this->resolveDoseMasterLabel($row['dosage_freq'] ?? '', $frequencyMap); + if ($frequency === '' && ! empty($row['dosage_freq_str'])) { + $frequency = trim((string) $row['dosage_freq_str']); + } + $route = $this->resolveDoseMasterLabel($row['dosage_where'] ?? '', $routeMap); + if ($route === '' && ! empty($row['dosage_where_str'])) { + $route = trim((string) $row['dosage_where_str']); + } + $quantity = trim((string) ($row['qty'] ?? '')); $duration = trim((string) ($row['no_of_days'] ?? '')); + $remark = trim((string) ($row['remark'] ?? '')); + if (stripos($when, 'week') !== false && stripos($frequency, 'week') !== false) { $frequency = ''; } @@ -11967,21 +12028,35 @@ class Opd_prescription extends BaseController $frequency, $duration, $quantity !== '' && $quantity !== '0' ? ('Qty: ' . $quantity) : '', + $remark !== '' ? ('Instructions: ' . $remark) : '', ]); $mid = (int) ($row['med_id'] ?? 0); + $rawDrugName = trim((string) ($row['med_name'] ?? '')); $master = $mid > 0 ? ($masterById[$mid] ?? []) : []; - $drugName = trim((string) ($row['med_name'] ?? '')); - if ($drugName === '') { - $drugName = trim((string) ($master['item_name'] ?? '')); + if (empty($master) && $rawDrugName !== '') { + $master = $masterByName[strtoupper($rawDrugName)] ?? []; } + $drugName = $rawDrugName !== '' ? $rawDrugName : trim((string) ($master['item_name'] ?? '')); $genericName = trim((string) ($master['genericname'] ?? '')); $medType = trim((string) ($row['med_type'] ?? '')); if ($medType === '') { $medType = trim((string) ($master['formulation'] ?? '')); } + $snomedCode = trim((string) ($master['snomed_code'] ?? '')); + $atcCode = strtoupper(trim((string) ($master['atc_code'] ?? ''))); + + // Fallback from mst_items if snomed_code is not in opd_med_master + if ($snomedCode === '' && ! empty($mstItemsByName[strtoupper($drugName)])) { + $mst = $mstItemsByName[strtoupper($drugName)]; + $snomedCode = trim((string) ($mst['snomed_ct_code'] ?? '')); + if ($genericName === '' && ! empty($mst['generic_name'])) { + $genericName = trim((string) $mst['generic_name']); + } + } + $medications[] = [ 'drug_name' => $drugName, 'status' => 'active', @@ -11991,8 +12066,8 @@ class Opd_prescription extends BaseController 'med_id' => $mid, 'generic_name' => $genericName, 'med_type' => $medType, - 'snomed_code' => trim((string) ($master['snomed_code'] ?? '')), - 'atc_code' => strtoupper(trim((string) ($master['atc_code'] ?? ''))), + 'snomed_code' => $snomedCode, + 'atc_code' => $atcCode, ]; } diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 599134a..7fb1c13 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -1113,6 +1113,7 @@ class Patient extends BaseController } $data['profile_file_path'] = $profile_file_path; + $data['abhaPhotoAvailable'] = $abha_profile_photo_base64 !== ''; if ($edit == 0) { return view('billing/Person_profile_V', $data); diff --git a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php index f32ace6..0ad4621 100644 --- a/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php +++ b/app/Libraries/Abdm/Sync/AbdmTaskBoardSyncService.php @@ -770,6 +770,12 @@ class AbdmTaskBoardSyncService }; $cleanVisitDate = str_replace('-', '', $visitDate); $careContextRef = $prefix . $entityId . '-' . $cleanVisitDate; + if ($taskType === 'immunization_record_publish' && $this->db->tableExists('immunization_records')) { + $immRec = $this->db->table('immunization_records')->select('abdm_care_context_reference')->where('id', (int) $entityId)->get(1)->getRowArray(); + if (! empty($immRec['abdm_care_context_reference'])) { + $careContextRef = trim((string) $immRec['abdm_care_context_reference']); + } + } $careContextDisplay = $hiType . ' ' . $visitDate; $pushData = [ diff --git a/app/Views/abdm/task_board.php b/app/Views/abdm/task_board.php index baee141..4aebfab 100644 --- a/app/Views/abdm/task_board.php +++ b/app/Views/abdm/task_board.php @@ -3685,6 +3685,8 @@ task_type: taskType, entity_id: entityId, onLinked: function (res) { + row.setAttribute('data-task-status', 'completed'); + row.setAttribute('data-is-linked', '1'); var statusCell = row.children[5]; if (statusCell) { var refHtml = ''; @@ -3693,6 +3695,10 @@ } statusCell.innerHTML = 'LINKED' + refHtml; } + var actionCell = row.children[6]; + if (actionCell) { + actionCell.innerHTML = 'Done'; + } } }); } diff --git a/app/Views/billing/Person_Edit_V.php b/app/Views/billing/Person_Edit_V.php index 87aee09..9cbe527 100644 --- a/app/Views/billing/Person_Edit_V.php +++ b/app/Views/billing/Person_Edit_V.php @@ -9,7 +9,11 @@ user(); + try { + $user = function_exists('auth') ? auth()->user() : null; + } catch (\Throwable $e) { + $user = null; + } $patientAbhaId = (string) ($data[0]->abha_id ?? $data[0]->abha_no ?? $data[0]->abha ?? $data[0]->abha_address ?? ''); $abhaAddress = trim((string) ($data[0]->abha_address ?? '')); $abhaVerifiedStatus = trim((string) ($data[0]->abha_verified_status ?? '')); @@ -17,6 +21,7 @@ $abhaKycVerified = (int) ($data[0]->abha_kyc_verified ?? 0) === 1; $abhaMobileVerified = (int) ($data[0]->abha_mobile_verified ?? 0) === 1; $abhaLinkedAt = trim((string) ($data[0]->abdm_linked_at ?? '')); + $abhaPhotoAvailable = isset($abhaPhotoAvailable) ? (bool) $abhaPhotoAvailable : (trim((string) ($data[0]->abha_profile_photo_base64 ?? '')) !== ''); $isAbhaVerified = in_array(strtoupper($abhaVerifiedStatus), ['VERIFIED', 'ACTIVE', 'LINKED', '1', 'YES', 'Y', 'TRUE'], true) || strtoupper($abhaVerificationType) === 'VERIFIED' || ($abhaKycVerified && $abhaMobileVerified); @@ -131,7 +136,7 @@
KYC Verified:
Mobile Verified:
-
ABHA Photo:
+
ABHA Photo:
diff --git a/app/Views/billing/Person_profile_V.php b/app/Views/billing/Person_profile_V.php index df3a749..a6aca25 100644 --- a/app/Views/billing/Person_profile_V.php +++ b/app/Views/billing/Person_profile_V.php @@ -56,7 +56,7 @@ $abhaKycVerified = (int) ($data[0]->abha_kyc_verified ?? 0) === 1; $abhaMobileVerified = (int) ($data[0]->abha_mobile_verified ?? 0) === 1; $abhaLinkedAt = trim((string) ($data[0]->abdm_linked_at ?? '')); - $abhaPhotoAvailable = trim((string) ($data[0]->abha_profile_photo_base64 ?? '')) !== ''; + $abhaPhotoAvailable = isset($abhaPhotoAvailable) ? (bool) $abhaPhotoAvailable : (trim((string) ($data[0]->abha_profile_photo_base64 ?? '')) !== ''); $hasExistingAbha = ($patientAbhaId !== '' || $abhaAddress !== '' || $abhaLinkedAt !== ''); $isAbhaStatusVerified = in_array(strtoupper($abhaVerifiedStatus), ['VERIFIED', 'LINKED', 'ACTIVE', '1', 'YES', 'Y', 'TRUE'], true) || strtoupper($abhaVerificationType) === 'VERIFIED' @@ -64,7 +64,11 @@ $isAbhaLinkedAndVerified = $hasExistingAbha && ($isAbhaStatusVerified || $abhaLinkedAt !== ''); ?> user(); + try { + $user = function_exists('auth') ? auth()->user() : null; + } catch (\Throwable $e) { + $user = null; + } $canEditOpd = is_object($user) && method_exists($user, 'can') ? $user->can('billing.opd.edit') : false; $canChargeEdit = is_object($user) && method_exists($user, 'can') ? $user->can('billing.charges.edit') : false; ?> @@ -119,11 +123,11 @@
OPD Visits
-
+
Invoices
-
+
Insurance
@@ -178,7 +182,7 @@
Gender / Age
-
xgender?> / dob ?? null, $data[0]->age ?? '', $data[0]->age_in_month ?? '', $data[0]->estimate_dob ?? '')) ?>
+
xgender ?? ((int) ($data[0]->gender ?? 0) === 1 ? 'Male' : 'Female')) ?> / dob ?? null, $data[0]->age ?? '', $data[0]->age_in_month ?? '', $data[0]->estimate_dob ?? '')) ?>
Relation
diff --git a/app/Views/partials/abdm_hip_link_modal.php b/app/Views/partials/abdm_hip_link_modal.php index 9838c4e..9381bdd 100644 --- a/app/Views/partials/abdm_hip_link_modal.php +++ b/app/Views/partials/abdm_hip_link_modal.php @@ -678,6 +678,75 @@ alertBox.innerHTML += '
FHIR Document Stored on Bridge: Invoice bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; } }).catch(function() {}); + } else if (ctxTaskType === 'immunization_record_publish') { + var pushData = new URLSearchParams(); + pushData.append('record_id', ctxEntityId); + pushData.append('patient_id', ctxPatientId); + pushData.append('abha_id', abhaAddress); + pushData.append('push_to_gateway', '1'); + if (firstRef) pushData.append('care_context_reference', firstRef); + pushData.append(csrf.name || 'csrf_hms', csrf.hash); + + fetch('', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + }, + body: pushData.toString() + }).then(function(r) { return r.json(); }).then(function(pRes) { + updateCsrf(pRes); + if (pRes && pRes.ok === 1 && alertBox) { + alertBox.innerHTML += '
FHIR Document Stored on Bridge: Immunization bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; + } + }).catch(function() {}); + } else if (ctxTaskType === 'ipd_discharge_publish') { + var pushData = new URLSearchParams(); + pushData.append('ipd_id', ctxEntityId); + pushData.append('patient_id', ctxPatientId); + pushData.append('abha_id', abhaAddress); + pushData.append('push_to_gateway', '1'); + if (firstRef) pushData.append('care_context_reference', firstRef); + pushData.append(csrf.name || 'csrf_hms', csrf.hash); + + fetch('', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + }, + body: pushData.toString() + }).then(function(r) { return r.json(); }).then(function(pRes) { + updateCsrf(pRes); + if (pRes && pRes.ok === 1 && alertBox) { + alertBox.innerHTML += '
FHIR Document Stored on Bridge: Discharge summary bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; + } + }).catch(function() {}); + } else if (ctxTaskType === 'wellness_record_publish') { + var pushData = new URLSearchParams(); + pushData.append('opd_id', ctxEntityId); + pushData.append('patient_id', ctxPatientId); + pushData.append('abha_id', abhaAddress); + pushData.append('push_to_gateway', '1'); + if (firstRef) pushData.append('care_context_reference', firstRef); + pushData.append(csrf.name || 'csrf_hms', csrf.hash); + + fetch('', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + }, + body: pushData.toString() + }).then(function(r) { return r.json(); }).then(function(pRes) { + updateCsrf(pRes); + if (pRes && pRes.ok === 1 && alertBox) { + alertBox.innerHTML += '
FHIR Document Stored on Bridge: Wellness bundle is active on Bridge for Care Context ' + (pRes.care_context_reference || firstRef) + '.
To view in PHR App: Open your PHR app (e.g. ABHA App) → tap Linked Facilities → tap "Fetch Records" / "Pull Records".
'; + } + }).catch(function() {}); } } diff --git a/tests/unit/Abdm/AbdmTaskBoardBackfillTest.php b/tests/unit/Abdm/AbdmTaskBoardBackfillTest.php index d872a03..5f0081a 100644 --- a/tests/unit/Abdm/AbdmTaskBoardBackfillTest.php +++ b/tests/unit/Abdm/AbdmTaskBoardBackfillTest.php @@ -73,6 +73,52 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase $this->assertSame('RAD-51450-20261001', $enriched[1]['bridge_care_context_reference']); } + public function testEnrichTasksWithHealthRecordStatePreventsCrossModuleCollision(): void + { + $db = \Config\Database::connect(); + if ($db->tableExists('health_records')) { + // Insert an IPD discharge health record with entity_id = 999 for patient 18 + $db->table('health_records')->insert([ + 'id' => 9999, + 'patient_id' => 18, + 'entity_type' => 'ipd', + 'hi_type' => 'DischargeSummaryRecord', + 'entity_id' => '999', + 'push_status' => 'queued', + 'care_context_reference' => 'DISCHARGE-999-S999-2026', + ]); + + $controller = (new ReflectionClass(AbdmTaskBoard::class))->newInstanceWithoutConstructor(); + $method = new ReflectionMethod(AbdmTaskBoard::class, 'enrichTasksWithHealthRecordState'); + $method->setAccessible(true); + + // Task is for patient 11 with entity_type = immunization and entity_id = 999 + $mockTasks = [ + [ + 'id' => 101, + 'task_code' => 'ABDM-TEST-001', + 'task_type' => 'immunization_record_publish', + 'patient_id' => 11, + 'entity_type' => 'immunization', + 'entity_id' => '999', + 'status' => 'pending', + 'last_action_result' => '', + ], + ]; + + $enriched = $method->invoke($controller, $mockTasks); + + // Should NOT match the IPD record for patient 18 + $this->assertSame(0, $enriched[0]['bridge_submitted']); + $this->assertSame('', $enriched[0]['bridge_care_context_reference']); + + // Clean up + $db->table('health_records')->where('id', 9999)->delete(); + } else { + $this->assertTrue(true); + } + } + public function testWorkTaskServiceHasGetTasksMethod(): void { $serviceClass = new ReflectionClass(\App\Libraries\AbdmWorkTaskService::class); diff --git a/tests/unit/Abdm/CareContextDiscoveryTest.php b/tests/unit/Abdm/CareContextDiscoveryTest.php index 2385704..d510aa0 100644 --- a/tests/unit/Abdm/CareContextDiscoveryTest.php +++ b/tests/unit/Abdm/CareContextDiscoveryTest.php @@ -309,6 +309,39 @@ final class CareContextDiscoveryTest extends CIUnitTestCase $this->assertTrue($foundImm, 'ImmunizationRecord should be discovered'); } + public function testImmunizationTaskReturnsPrimaryCareContextInDiscovery(): void + { + $db = \Config\Database::connect(); + $db->table('immunization_records')->insert([ + 'id' => 11, + 'patient_id' => 11, + 'vaccine_name' => 'PCV', + 'given_date' => '2026-10-06 01:54:00', + 'abdm_care_context_reference' => 'IMM-11', + ]); + + $reflector = new ReflectionClass($this->gateway); + $method = $reflector->getMethod('findCareContextsForPatient'); + $method->setAccessible(true); + + [$v3List, $fullList] = $method->invoke( + $this->gateway, + 11, + 'P-11', + 'DEVENDER SINGH', + 'immunization_record_publish', + '11' + ); + + $this->assertNotEmpty($fullList); + $primary = $fullList[0]; + $this->assertSame('IMM-11', $primary['careContextId']); + $this->assertSame('ImmunizationRecord', $primary['record_type']); + $this->assertTrue($primary['is_primary']); + $this->assertTrue($primary['is_fhir_ready']); + $this->assertStringContainsString('PCV', $primary['display']); + } + public function testShareDiagnosisReportBundleFormatsRadiologyCareContext(): void { $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; diff --git a/tests/unit/PatientEditViewTest.php b/tests/unit/PatientEditViewTest.php new file mode 100644 index 0000000..7eb01a5 --- /dev/null +++ b/tests/unit/PatientEditViewTest.php @@ -0,0 +1,123 @@ + 16, + 'p_fname' => 'John Doe', + 'p_code' => 'P16', + 'p_edit' => 1, + 'title' => 'Mr.', + 'gender' => 1, + 'udai_last4' => '1234', + 'abha_id' => '', + 'abha_no' => '', + 'abha' => '', + 'abha_address' => '', + 'abha_verified_status' => '', + 'abha_verification_type' => '', + 'abha_kyc_verified' => 0, + 'abha_mobile_verified' => 0, + 'abdm_linked_at' => '', + 'abha_profile_photo_base64' => '', + 'mphone1' => '9876543210', + 'estimate_dob' => 0, + 'age' => 30, + 'age_in_month' => 0, + 'dob' => '1995-01-01', + 'p_relative' => 'S/o', + 'p_rname' => 'Father Doe', + 'email1' => 'john@example.com', + 'blood_group' => 'O+', + 'referby' => '', + 'add1' => '123 Test St', + 'city' => 'City', + 'zip' => '123456', + 'district' => 'District', + 'state' => 'State', + ], $overrides); + + return [ + 'data' => [$patient], + 'blood_group' => [(object) ['blood_group' => 'O+'], (object) ['blood_group' => 'A+']], + 'refer_master' => [], + 'tag_master' => [], + 'patient_tag_list' => [], + ]; + } + + public function testViewRendersWithoutAbhaPhotoAvailableVariableInViewData(): void + { + $viewData = $this->makePatientData(); + // Do not set $viewData['abhaPhotoAvailable'] explicitly to test fallback in Person_Edit_V + $html = view('billing/Person_Edit_V', $viewData); + + $this->assertStringContainsString('ABHA Photo:', $html); + $this->assertStringContainsString('NOT AVAILABLE', $html); + $this->assertStringContainsString('Person Profile - John Doe P16', $html); + } + + public function testViewRendersWithAbhaPhotoAvailableWhenBase64Present(): void + { + $viewData = $this->makePatientData([ + 'abha_profile_photo_base64' => 'data:image/jpeg;base64,/9j/4AAQSkZJRg==', + ]); + $viewData['abhaPhotoAvailable'] = true; + + $html = view('billing/Person_Edit_V', $viewData); + + $this->assertStringContainsString('ABHA Photo:', $html); + $this->assertStringContainsString('AVAILABLE (BASE64 STORED)', $html); + } + + public function testViewRendersWithAbhaVerifiedLockedFields(): void + { + $viewData = $this->makePatientData([ + 'abha_id' => '12345678901234', + 'abha_verified_status' => 'VERIFIED', + 'abha_verification_type' => 'VERIFIED', + 'abha_kyc_verified' => 1, + 'abha_mobile_verified' => 1, + ]); + $viewData['abhaPhotoAvailable'] = false; + + $html = view('billing/Person_Edit_V', $viewData); + + $this->assertStringContainsString('ABHA verified: name, gender, ABHA ID, and date of birth are locked.', $html); + $this->assertStringContainsString('KYC Verified: YES', $html); + $this->assertStringContainsString('Mobile Verified: YES', $html); + } + + public function testProfileViewRendersWithAbhaPhoto(): void + { + $viewData = $this->makePatientData([ + 'insurance_id' => 0, + 'insurance_card_id' => 0, + 'log' => '', + ]); + $viewData['profile_file_path'] = '/assets/images/no_image.svg'; + $viewData['case_master_opd'] = []; + $viewData['case_master_ipd'] = []; + $viewData['data_insurance_card'] = []; + $viewData['opd_List'] = []; + $viewData['invoice_list'] = []; + $viewData['abhaPhotoAvailable'] = true; + + $html = view('billing/Person_profile_V', $viewData); + + $this->assertStringContainsString('Profile', $html); + $this->assertStringContainsString('AVAILABLE (BASE64 STORED)', $html); + } +}