fix(billing,abdm): resolve patient profile edit error and enhance opd prescription fhir mapping
- Fix Undefined variable \ 500 error in Person_Edit_V.php - Safeguard auth()->user() and variable access in Person_Edit_V.php and Person_profile_V.php - Enhance getPrescriptionMedicines with fallback dosing, remarks, and SNOMED lookup for OP Consultation FHIR - Add deep-link SMS notify and link verification in ABDM gateway and task board - Add PatientEditViewTest covering profile and edit view rendering
This commit is contained in:
parent
6270b0517a
commit
da61637c56
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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 = '<span class="badge bg-success status-pill">LINKED</span>' + refHtml;
|
||||
}
|
||||
var actionCell = row.children[6];
|
||||
if (actionCell) {
|
||||
actionCell.innerHTML = '<span class="badge bg-success-subtle text-success border border-success-subtle py-1 px-2"><i class="bi bi-check2-all me-1"></i>Done</span>';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
</nav>
|
||||
</div>
|
||||
<?php
|
||||
$user = auth()->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 @@
|
|||
<div class="row" style="margin-top: 6px;">
|
||||
<div class="col-md-3"><strong>KYC Verified:</strong> <?= $abhaKycVerified ? 'YES' : 'NO' ?></div>
|
||||
<div class="col-md-3"><strong>Mobile Verified:</strong> <?= $abhaMobileVerified ? 'YES' : 'NO' ?></div>
|
||||
<div class="col-md-6"><strong>ABHA Photo:</strong> <?= $abhaPhotoAvailable ? 'AVAILABLE (BASE64 STORED)' : 'NOT AVAILABLE' ?></div>
|
||||
<div class="col-md-6"><strong>ABHA Photo:</strong> <?= !empty($abhaPhotoAvailable) ? 'AVAILABLE (BASE64 STORED)' : 'NOT AVAILABLE' ?></div>
|
||||
</div>
|
||||
<?php if ($abhaLockNotice !== '') : ?>
|
||||
<div class="row" style="margin-top: 6px;">
|
||||
|
|
|
|||
|
|
@ -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 !== '');
|
||||
?>
|
||||
<?php
|
||||
$user = auth()->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 @@
|
|||
<div class="d-flex justify-content-between align-items-center border rounded px-3 py-2">
|
||||
<div>
|
||||
<div class="small text-muted">OPD Visits</div>
|
||||
<div class="fw-bold"><?= count($opd_List) ?></div>
|
||||
<div class="fw-bold"><?= count($opd_List ?? []) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-muted">Invoices</div>
|
||||
<div class="fw-bold"><?= count($invoice_list) ?></div>
|
||||
<div class="fw-bold"><?= count($invoice_list ?? []) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-muted">Insurance</div>
|
||||
|
|
@ -178,7 +182,7 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 label">Gender / Age</div>
|
||||
<div class="col-lg-9 col-md-8"><?=$data[0]->xgender?> / <?= esc(get_age_1($data[0]->dob ?? null, $data[0]->age ?? '', $data[0]->age_in_month ?? '', $data[0]->estimate_dob ?? '')) ?></div>
|
||||
<div class="col-lg-9 col-md-8"><?= esc($data[0]->xgender ?? ((int) ($data[0]->gender ?? 0) === 1 ? 'Male' : 'Female')) ?> / <?= esc(get_age_1($data[0]->dob ?? null, $data[0]->age ?? '', $data[0]->age_in_month ?? '', $data[0]->estimate_dob ?? '')) ?></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 label">Relation</div>
|
||||
|
|
|
|||
|
|
@ -678,6 +678,75 @@
|
|||
alertBox.innerHTML += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Invoice bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).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('<?= base_url('AbdmGateway/share_immunization_bundle') ?>', {
|
||||
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 += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Immunization bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).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('<?= base_url('AbdmGateway/share_ipd_discharge_bundle') ?>', {
|
||||
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 += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Discharge summary bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).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('<?= base_url('AbdmGateway/share_wellness_bundle') ?>', {
|
||||
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 += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Wellness bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) → tap <em>Linked Facilities</em> → tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
|
||||
}
|
||||
}).catch(function() {});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
123
tests/unit/PatientEditViewTest.php
Normal file
123
tests/unit/PatientEditViewTest.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
final class PatientEditViewTest extends CIUnitTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
helper(['common', 'form', 'url', 'age']);
|
||||
}
|
||||
|
||||
private function makePatientData(array $overrides = []): array
|
||||
{
|
||||
$patient = (object) array_merge([
|
||||
'id' => 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:</strong> YES', $html);
|
||||
$this->assertStringContainsString('Mobile Verified:</strong> 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user