feat(abdm & schema): drop patient_master.p_lname and add abha address selector to M1 verify flow
This commit is contained in:
parent
e946000fb9
commit
5f9784d7ec
|
|
@ -37,3 +37,13 @@ In `app/Controllers/AbdmGateway.php` and the bridge gateway:
|
|||
- Use valid LOINC codes for composition type and section codes.
|
||||
- Use SNOMED CT for clinical diagnoses, findings, and conditions.
|
||||
- Refer to `docs/abdm/CARE_CONTEXT_AND_FHIR_STANDARDS.md` for full implementation specifications.
|
||||
|
||||
---
|
||||
|
||||
## Rule 4: Patient Name Handling (`patient_master.p_fname`)
|
||||
- Patient full name is stored strictly in `patient_master.p_fname`.
|
||||
- The column `patient_master.p_lname` has been permanently dropped and deleted from `patient_master` in all databases.
|
||||
- NEVER query, select, join, insert, or reference `patient_master.p_lname` in any controllers, models, queries, or FHIR generators.
|
||||
- When generating FHIR Patient or Composition resources, map the patient's full name directly from `patient_master.p_fname`.
|
||||
- Note: Doctors in `doctor_master` continue to use `p_fname`, `p_mname`, and `p_lname`.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,3 +10,9 @@ For all ABDM (Ayushman Bharat Digital Mission) development, linking, Care Contex
|
|||
The ABDM PHR app displays this field under its "Patient reference" table column. It MUST always be set to the specific care context reference (`PRESC-{patient_id}-S{session_id}-{YYYYMMDD}`, `OPD-{patient_id}-S{session_id}-{YYYYMMDD}`, etc.).
|
||||
2. **Never overwrite structured references with `p_code` in `AbdmGateway.php`.**
|
||||
3. **All FHIR bundles must conform to NRCeS FHIR R4 document bundle specifications.**
|
||||
4. **Patient Full Name Standard (`patient_master.p_fname`):**
|
||||
- The patient's full name is stored strictly in `patient_master.p_fname`.
|
||||
- The column `patient_master.p_lname` has been permanently dropped from the database table.
|
||||
- **DO NOT** use, select, update, or reference `patient_master.p_lname` anywhere in the application.
|
||||
- Note: For doctors, `doctor_master` continues to use `p_fname`, `p_mname`, and `p_lname`.
|
||||
|
||||
|
|
|
|||
|
|
@ -345,9 +345,6 @@ class AbdmFhirInspector extends BaseController
|
|||
}
|
||||
|
||||
$select = ['id', 'p_code', 'p_fname', 'gender', 'dob'];
|
||||
if (in_array('p_lname', $fields, true)) {
|
||||
$select[] = 'p_lname';
|
||||
}
|
||||
if (in_array('mphone1', $fields, true)) {
|
||||
$select[] = 'mphone1';
|
||||
}
|
||||
|
|
@ -406,7 +403,7 @@ class AbdmFhirInspector extends BaseController
|
|||
continue;
|
||||
}
|
||||
|
||||
$name = trim(($row['p_fname'] ?? '') . ' ' . ($row['p_lname'] ?? ''));
|
||||
$name = trim((string) ($row['p_fname'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = 'Patient #' . $row['id'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14127,6 +14127,7 @@ class AbdmGateway extends BaseController
|
|||
|
||||
$result['care_contexts'] = $contexts;
|
||||
$result['total_linked'] = count($contexts);
|
||||
$result['links'] = $contexts;
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
|
@ -14304,6 +14305,7 @@ class AbdmGateway extends BaseController
|
|||
|
||||
// Fetch already-linked care contexts from Bridge and Local DB (record_links & health_records)
|
||||
$linkedRefs = [];
|
||||
$gatewayVerifiedRefs = [];
|
||||
if ($effAbhaAddress !== '') {
|
||||
try {
|
||||
$bridgeLinks = $this->connector->hipGetPatientLinks(['abha_address' => $effAbhaAddress]);
|
||||
|
|
@ -14312,6 +14314,7 @@ class AbdmGateway extends BaseController
|
|||
foreach ($linkPat['careContexts'] ?? [] as $cc) {
|
||||
$ref = trim((string) ($cc['referenceNumber'] ?? $cc['ref'] ?? ''));
|
||||
if ($ref !== '') {
|
||||
$gatewayVerifiedRefs[] = $ref;
|
||||
$linkedRefs[] = $ref;
|
||||
}
|
||||
}
|
||||
|
|
@ -14382,22 +14385,28 @@ class AbdmGateway extends BaseController
|
|||
}
|
||||
|
||||
$linkedRefs = array_values(array_unique($linkedRefs));
|
||||
$gatewayVerifiedRefs = array_values(array_unique($gatewayVerifiedRefs));
|
||||
|
||||
$contextsList = [];
|
||||
foreach ($careContextsFull as $cc) {
|
||||
$ref = $cc['referenceNumber'] ?? $cc['careContextId'] ?? '';
|
||||
$isGatewayLinked = in_array($ref, $gatewayVerifiedRefs, true);
|
||||
$isLinked = in_array($ref, $linkedRefs, true);
|
||||
if (! $isLinked && ! empty($cc['alias_reference'])) {
|
||||
$isLinked = in_array($cc['alias_reference'], $linkedRefs, true);
|
||||
if (in_array($cc['alias_reference'], $gatewayVerifiedRefs, true)) {
|
||||
$isGatewayLinked = 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'] ?? false),
|
||||
'is_primary' => (bool) ($cc['is_primary'] ?? false),
|
||||
'entity_id' => (string) ($cc['entity_id'] ?? $cc['opd_id'] ?? ''),
|
||||
'ref' => $ref,
|
||||
'display' => $cc['display'] ?? $ref,
|
||||
'hi_type' => $cc['record_type'] ?? 'OPConsultRecord',
|
||||
'is_linked' => $isLinked,
|
||||
'is_gateway_linked' => $isGatewayLinked,
|
||||
'is_fhir_ready' => (bool) ($cc['is_fhir_ready'] ?? false),
|
||||
'is_primary' => (bool) ($cc['is_primary'] ?? false),
|
||||
'entity_id' => (string) ($cc['entity_id'] ?? $cc['opd_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -14422,7 +14431,7 @@ class AbdmGateway extends BaseController
|
|||
$multiplePatients[] = [
|
||||
'id' => (int) $or['id'],
|
||||
'p_code' => (string) ($or['p_code'] ?? ''),
|
||||
'name' => trim(($or['p_fname'] ?? '') . ' ' . ($or['p_lname'] ?? '')),
|
||||
'name' => trim((string) ($or['p_fname'] ?? '')),
|
||||
'gender' => ((int) ($or['gender'] ?? 0) === 2 || strtoupper((string) ($or['gender'] ?? '')) === 'F') ? 'F' : 'M',
|
||||
'dob' => (string) ($or['dob'] ?? ''),
|
||||
'is_current' => (int) $or['id'] === $patientId,
|
||||
|
|
@ -14445,10 +14454,11 @@ class AbdmGateway extends BaseController
|
|||
'abha_number' => $effAbhaNumber,
|
||||
'phone' => $cleanPhone,
|
||||
],
|
||||
'care_contexts' => $contextsList,
|
||||
'linked_refs' => $linkedRefs,
|
||||
'hospital_name' => $hospitalName,
|
||||
'multiple_patients' => $multiplePatients,
|
||||
'care_contexts' => $contextsList,
|
||||
'linked_refs' => $linkedRefs,
|
||||
'gateway_verified_refs' => $gatewayVerifiedRefs,
|
||||
'hospital_name' => $hospitalName,
|
||||
'multiple_patients' => $multiplePatients,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,9 +131,6 @@ class AbdmTaskBoard extends BaseController
|
|||
if (! empty($patientIds) && $db->tableExists('patient_master')) {
|
||||
$pFields = $db->getFieldNames('patient_master') ?? [];
|
||||
$pSel = ['id', 'p_code', 'p_fname', 'gender', 'dob'];
|
||||
if (in_array('p_lname', $pFields, true)) {
|
||||
$pSel[] = 'p_lname';
|
||||
}
|
||||
if (in_array('mphone1', $pFields, true)) {
|
||||
$pSel[] = 'mphone1';
|
||||
}
|
||||
|
|
@ -1240,9 +1237,6 @@ class AbdmTaskBoard extends BaseController
|
|||
}
|
||||
|
||||
$select = ['id', 'p_code', 'p_fname', 'gender', 'dob'];
|
||||
if (in_array('p_lname', $fields, true)) {
|
||||
$select[] = 'p_lname';
|
||||
}
|
||||
if (in_array('mphone1', $fields, true)) {
|
||||
$select[] = 'mphone1';
|
||||
}
|
||||
|
|
@ -1301,9 +1295,7 @@ class AbdmTaskBoard extends BaseController
|
|||
continue;
|
||||
}
|
||||
|
||||
$fname = trim((string) ($row['p_fname'] ?? ''));
|
||||
$lname = trim((string) ($row['p_lname'] ?? ''));
|
||||
$fullName = trim($fname . ' ' . $lname);
|
||||
$fullName = trim((string) ($row['p_fname'] ?? ''));
|
||||
if ($fullName === '') {
|
||||
$fullName = 'Patient #' . ($row['id'] ?? '');
|
||||
}
|
||||
|
|
@ -1782,7 +1774,7 @@ class AbdmTaskBoard extends BaseController
|
|||
$abhaCol = $this->resolveExistingColumn($patientFields, ['abha_address', 'abha_id', 'abha_no', 'abha']);
|
||||
if ($abhaCol !== null) {
|
||||
$wellnessRows = $this->db->table('patient_wellness_records w')
|
||||
->select('w.id, w.patient_id, w.recorded_at, w.care_context_reference, w.bridge_record_id, w.abdm_status, p.p_fname, p.p_lname, p.' . $abhaCol . ' as abha_id', false)
|
||||
->select('w.id, w.patient_id, w.recorded_at, w.care_context_reference, w.bridge_record_id, w.abdm_status, p.p_fname, p.' . $abhaCol . ' as abha_id', false)
|
||||
->join('patient_master p', 'p.id = w.patient_id', 'inner')
|
||||
->where('p.' . $abhaCol . ' !=', '')
|
||||
->orderBy('w.id', 'DESC')
|
||||
|
|
@ -1809,7 +1801,7 @@ class AbdmTaskBoard extends BaseController
|
|||
continue;
|
||||
}
|
||||
|
||||
$patientName = trim(($wRow['p_fname'] ?? '') . ' ' . ($wRow['p_lname'] ?? ''));
|
||||
$patientName = trim((string) ($wRow['p_fname'] ?? ''));
|
||||
$isPushed = (! empty($wRow['bridge_record_id']) || in_array($wRow['abdm_status'] ?? '', ['queued', 'pushed', 'linked'], true));
|
||||
$taskId = $this->taskService->createOrRefreshTask(
|
||||
'wellness_record_publish',
|
||||
|
|
|
|||
|
|
@ -718,47 +718,179 @@ class Abha extends BaseController
|
|||
}
|
||||
|
||||
try {
|
||||
$helper = new \App\Libraries\Abdm\AbdmV3DirectHelper();
|
||||
$result = $helper->searchByMobile($mobile);
|
||||
|
||||
if (! empty($result['ok']) && ! empty($result['profiles'])) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'txn_id' => $result['txn_id'] ?? '',
|
||||
'profiles' => $result['profiles'],
|
||||
'mobile' => $mobile,
|
||||
]);
|
||||
}
|
||||
|
||||
// Fallback: If ABDM Sandbox returns no external profiles, check if HMS patient_master has records with this mobile
|
||||
$db = \Config\Database::connect();
|
||||
$rows = $db->table('patient_master')
|
||||
|
||||
// 1. Fetch matching local patients from patient_master
|
||||
$localPatients = $db->table('patient_master')
|
||||
->groupStart()
|
||||
->where('mphone1', $mobile)
|
||||
->orWhere('mphone2', $mobile)
|
||||
->groupEnd()
|
||||
->get()->getResultArray();
|
||||
|
||||
if (! empty($rows)) {
|
||||
// 2. Fetch all known ABHA addresses from abdm_link_transactions for these patients
|
||||
$patientIds = array_filter(array_column($localPatients, 'id'));
|
||||
$patientCodes = array_filter(array_column($localPatients, 'p_code'));
|
||||
$linkedAddressesByPatient = [];
|
||||
if (! empty($patientIds) || ! empty($patientCodes)) {
|
||||
$builder = $db->table('abdm_link_transactions');
|
||||
$builder->groupStart();
|
||||
if (! empty($patientIds)) {
|
||||
$builder->whereIn('patient_id', $patientIds);
|
||||
}
|
||||
if (! empty($patientCodes)) {
|
||||
$builder->orWhereIn('patient_ref', $patientCodes);
|
||||
}
|
||||
$builder->groupEnd();
|
||||
$txns = $builder->select('patient_id, patient_ref, abha_address')->get()->getResultArray();
|
||||
foreach ($txns as $t) {
|
||||
$addr = trim((string)($t['abha_address'] ?? ''));
|
||||
if ($addr !== '') {
|
||||
if (! empty($t['patient_id'])) {
|
||||
$linkedAddressesByPatient['id_' . $t['patient_id']][$addr] = true;
|
||||
}
|
||||
if (! empty($t['patient_ref'])) {
|
||||
$linkedAddressesByPatient['ref_' . $t['patient_ref']][$addr] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Query ABDM V3 Direct Helper
|
||||
$helper = new \App\Libraries\Abdm\AbdmV3DirectHelper();
|
||||
$result = $helper->searchByMobile($mobile);
|
||||
|
||||
if (! empty($result['ok']) && ! empty($result['profiles'])) {
|
||||
// Enrich ABDM profiles with known local data (UHID, DOB, and associated ABHA addresses)
|
||||
$enrichedProfiles = [];
|
||||
foreach ($result['profiles'] as $prof) {
|
||||
$pIndex = (string)($prof['index'] ?? '');
|
||||
$pName = trim((string)($prof['name'] ?? ''));
|
||||
$pAbhaNum = trim((string)($prof['abha_number'] ?? ''));
|
||||
$last4 = preg_replace('/\D/', '', substr($pAbhaNum, -4));
|
||||
$pGender = strtoupper(substr(trim((string)($prof['gender'] ?? 'M')), 0, 1));
|
||||
|
||||
// Attempt match with local patient_master:
|
||||
// Priority 1: Match by last 4 digits of abha_id
|
||||
// Priority 2: Match by exact or partial name
|
||||
$matchedPatient = null;
|
||||
if ($last4 !== '') {
|
||||
foreach ($localPatients as $lp) {
|
||||
$lpAbha = preg_replace('/\D/', '', (string)($lp['abha_id'] ?? ''));
|
||||
if ($lpAbha !== '' && str_ends_with($lpAbha, $last4)) {
|
||||
$matchedPatient = $lp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! $matchedPatient) {
|
||||
foreach ($localPatients as $lp) {
|
||||
$lpFName = trim((string)($lp['p_fname'] ?? ''));
|
||||
if ($lpFName !== '' && (stripos($lpFName, $pName) !== false || stripos($pName, $lpFName) !== false)) {
|
||||
$matchedPatient = $lp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allAddresses = [];
|
||||
$primaryAddress = '';
|
||||
$pCode = '';
|
||||
$pId = 0;
|
||||
$pDob = (string)($prof['dob'] ?? '');
|
||||
|
||||
if ($matchedPatient) {
|
||||
$pCode = (string)($matchedPatient['p_code'] ?? '');
|
||||
$pId = (int)($matchedPatient['id'] ?? 0);
|
||||
if ($pDob === '' && ! empty($matchedPatient['dob'])) {
|
||||
$pDob = (string)$matchedPatient['dob'];
|
||||
}
|
||||
if (! empty($matchedPatient['abha_address'])) {
|
||||
$addr = trim((string)$matchedPatient['abha_address']);
|
||||
$allAddresses[$addr] = true;
|
||||
$primaryAddress = $addr;
|
||||
}
|
||||
if (isset($linkedAddressesByPatient['id_' . $pId])) {
|
||||
foreach (array_keys($linkedAddressesByPatient['id_' . $pId]) as $a) {
|
||||
$allAddresses[$a] = true;
|
||||
}
|
||||
}
|
||||
if (isset($linkedAddressesByPatient['ref_' . $pCode])) {
|
||||
foreach (array_keys($linkedAddressesByPatient['ref_' . $pCode]) as $a) {
|
||||
$allAddresses[$a] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prof['abha_address'] = $primaryAddress !== '' ? $primaryAddress : (array_keys($allAddresses)[0] ?? '');
|
||||
$prof['all_abha_addresses'] = array_keys($allAddresses);
|
||||
$prof['p_code'] = $pCode;
|
||||
$prof['patient_id'] = $pId;
|
||||
$prof['dob'] = $pDob;
|
||||
|
||||
$enrichedProfiles[] = $prof;
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'txn_id' => $result['txn_id'] ?? '',
|
||||
'profiles' => $enrichedProfiles,
|
||||
'mobile' => $mobile,
|
||||
]);
|
||||
}
|
||||
|
||||
// 4. Fallback: If ABDM Sandbox returns no external profiles, check if HMS patient_master has records with this mobile
|
||||
if (! empty($localPatients)) {
|
||||
$profiles = [];
|
||||
$idx = 1;
|
||||
foreach ($rows as $p) {
|
||||
$pName = trim((string)(($p['fname'] ?? '') . ' ' . ($p['mname'] ?? '') . ' ' . ($p['lname'] ?? '')));
|
||||
$pAbha = trim((string)($p['abdm_abha_number'] ?? $p['abha_number'] ?? $p['abdm_abha_id'] ?? ''));
|
||||
foreach ($localPatients as $p) {
|
||||
$pName = trim((string)($p['p_fname'] ?? ''));
|
||||
if ($pName === '') {
|
||||
$pName = 'Patient ' . ($p['p_code'] ?? '');
|
||||
}
|
||||
|
||||
$pAbha = trim((string)($p['abha_id'] ?? ''));
|
||||
$maskedAbha = $pAbha !== ''
|
||||
? preg_replace('/^(\d{2})(\d{4})(\d{4})(\d{4})$/', 'xx-xxxx-xxxx-$4', preg_replace('/\D/', '', $pAbha))
|
||||
: 'xx-xxxx-xxxx-XXXX';
|
||||
|
||||
$pGender = 'M';
|
||||
$rawGen = (string)($p['gender'] ?? '1');
|
||||
if ($rawGen === '2' || strtoupper($rawGen) === 'F' || strtoupper($rawGen) === 'FEMALE') {
|
||||
$pGender = 'F';
|
||||
}
|
||||
|
||||
$allAddresses = [];
|
||||
if (! empty($p['abha_address'])) {
|
||||
$allAddresses[trim((string)$p['abha_address'])] = true;
|
||||
}
|
||||
$pId = (int)($p['id'] ?? 0);
|
||||
$pCode = (string)($p['p_code'] ?? '');
|
||||
if (isset($linkedAddressesByPatient['id_' . $pId])) {
|
||||
foreach (array_keys($linkedAddressesByPatient['id_' . $pId]) as $a) {
|
||||
$allAddresses[$a] = true;
|
||||
}
|
||||
}
|
||||
if (isset($linkedAddressesByPatient['ref_' . $pCode])) {
|
||||
foreach (array_keys($linkedAddressesByPatient['ref_' . $pCode]) as $a) {
|
||||
$allAddresses[$a] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$profiles[] = [
|
||||
'index' => (string) $idx++,
|
||||
'abha_number' => $maskedAbha,
|
||||
'name' => $pName !== '' ? $pName : 'Patient ' . ($p['p_code'] ?? ''),
|
||||
'gender' => strtoupper(substr((string)($p['gender'] ?? 'M'), 0, 1)),
|
||||
'dob' => (string)($p['dob'] ?? ''),
|
||||
'kyc_verified' => true,
|
||||
'auth_methods' => ['MOBILE_OTP', 'AADHAAR_OTP'],
|
||||
'profile_photo' => '',
|
||||
'local_patient' => true,
|
||||
'p_code' => (string)($p['p_code'] ?? ''),
|
||||
'index' => (string) $idx++,
|
||||
'abha_number' => $maskedAbha,
|
||||
'name' => $pName,
|
||||
'gender' => $pGender,
|
||||
'dob' => (string)($p['dob'] ?? ''),
|
||||
'kyc_verified' => true,
|
||||
'auth_methods' => ['MOBILE_OTP', 'AADHAAR_OTP'],
|
||||
'profile_photo' => '',
|
||||
'local_patient' => true,
|
||||
'p_code' => $pCode,
|
||||
'patient_id' => $pId,
|
||||
'abha_address' => (string)($p['abha_address'] ?? (array_keys($allAddresses)[0] ?? '')),
|
||||
'all_abha_addresses' => array_keys($allAddresses),
|
||||
];
|
||||
}
|
||||
return $this->response->setJSON([
|
||||
|
|
@ -1024,6 +1156,61 @@ class Abha extends BaseController
|
|||
|
||||
$patientInfo = $this->tryAutoLinkByDirectMatch($abhaNum, $name, $profileMobile, $gender, $dob, $abhaMeta);
|
||||
|
||||
$allAbhaAddresses = [];
|
||||
if ($abhaAddress !== '' && str_contains($abhaAddress, '@')) {
|
||||
$allAbhaAddresses[$abhaAddress] = true;
|
||||
}
|
||||
if (! empty($result['accounts']) && is_array($result['accounts'])) {
|
||||
foreach ($result['accounts'] as $acc) {
|
||||
if (is_array($acc)) {
|
||||
foreach (['preferredAbhaAddress', 'preferredAddress', 'abhaAddress', 'abha_address'] as $ak) {
|
||||
if (! empty($acc[$ak]) && is_string($acc[$ak]) && str_contains($acc[$ak], '@')) {
|
||||
$allAbhaAddresses[trim($acc[$ak])] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$patientIdForAddrs = $patientInfo !== null ? (int)($patientInfo['patient_id'] ?? 0) : 0;
|
||||
$cleanAbhaDigits = preg_replace('/\D/', '', $abhaNum);
|
||||
|
||||
$pBuilder = $db->table('patient_master');
|
||||
$pBuilder->groupStart();
|
||||
if ($patientIdForAddrs > 0) {
|
||||
$pBuilder->where('id', $patientIdForAddrs);
|
||||
}
|
||||
if ($cleanAbhaDigits !== '') {
|
||||
$pBuilder->orWhere('abha_id', $cleanAbhaDigits);
|
||||
}
|
||||
if ($profileMobile !== '') {
|
||||
$pBuilder->orWhere('mphone1', $profileMobile)->orWhere('mphone2', $profileMobile);
|
||||
}
|
||||
$pBuilder->groupEnd();
|
||||
$pRows = $pBuilder->select('id, abha_address')->get()->getResultArray();
|
||||
$matchedPids = [];
|
||||
foreach ($pRows as $pr) {
|
||||
$matchedPids[] = (int)$pr['id'];
|
||||
$pa = trim((string)($pr['abha_address'] ?? ''));
|
||||
if ($pa !== '' && str_contains($pa, '@')) {
|
||||
$allAbhaAddresses[$pa] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($matchedPids)) {
|
||||
$txns = $db->table('abdm_link_transactions')
|
||||
->select('abha_address')
|
||||
->whereIn('patient_id', array_unique($matchedPids))
|
||||
->get()->getResultArray();
|
||||
foreach ($txns as $t) {
|
||||
$ta = trim((string)($t['abha_address'] ?? ''));
|
||||
if ($ta !== '' && str_contains($ta, '@')) {
|
||||
$allAbhaAddresses[$ta] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$responseBase = [
|
||||
'ok' => 1,
|
||||
'txn_id' => $result['txn_id'] ?? $txnId,
|
||||
|
|
@ -1039,7 +1226,8 @@ class Abha extends BaseController
|
|||
'mobile' => $profileMobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'abha_address' => $abhaAddress,
|
||||
'abha_address' => $abhaAddress !== '' ? $abhaAddress : (array_keys($allAbhaAddresses)[0] ?? ''),
|
||||
'all_abha_addresses'=> array_keys($allAbhaAddresses),
|
||||
'verified_status' => 'ACTIVE',
|
||||
'verification_type' => 'VERIFIED',
|
||||
'kyc_verified' => true,
|
||||
|
|
|
|||
|
|
@ -1263,7 +1263,7 @@ class NursingApi extends BaseController
|
|||
'hfr_id' => 'IN0510000828',
|
||||
'patient' => [
|
||||
'id' => $patientId,
|
||||
'name' => trim(($patient['p_fname'] ?? '') . (($patient['p_lname'] ?? '') !== '' && ($patient['p_lname'] ?? '') !== '0' && strcasecmp((string)($patient['p_lname'] ?? ''), 'NA') !== 0 ? ' ' . $patient['p_lname'] : '')),
|
||||
'name' => trim((string) ($patient['p_fname'] ?? '')),
|
||||
'gender' => match (strtolower(trim((string) ($patient['gender'] ?? '')))) {
|
||||
'2', 'f', 'female' => 'female',
|
||||
'1', 'm', 'male' => 'male',
|
||||
|
|
@ -1335,9 +1335,7 @@ class NursingApi extends BaseController
|
|||
'3', 'o', 'other' => 'O',
|
||||
default => 'F',
|
||||
};
|
||||
$pFname = trim((string) ($patient['p_fname'] ?? ''));
|
||||
$pLname = trim((string) ($patient['p_lname'] ?? ''));
|
||||
$cleanPatientName = trim($pFname . ($pLname !== '' && $pLname !== '0' && strcasecmp($pLname, 'NA') !== 0 ? ' ' . $pLname : ''));
|
||||
$cleanPatientName = trim((string) ($patient['p_fname'] ?? ''));
|
||||
|
||||
$connector = \App\Libraries\Abdm\AbdmConnectorFactory::make();
|
||||
$pushPayload = [
|
||||
|
|
@ -1407,7 +1405,7 @@ class NursingApi extends BaseController
|
|||
'wellness',
|
||||
(string) $wellnessId,
|
||||
$patientId,
|
||||
trim(($patient['p_fname'] ?? '') . ' ' . ($patient['p_lname'] ?? '')),
|
||||
trim((string) ($patient['p_fname'] ?? '')),
|
||||
$effectiveAbha,
|
||||
'submit',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -4892,12 +4892,12 @@ class Opd_prescription extends BaseController
|
|||
$abhaField = $this->resolvePatientAbhaField();
|
||||
$abhaAddress = $abhaField !== null ? trim((string) ($patientRow[$abhaField] ?? '')) : '';
|
||||
|
||||
$patFirstName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? ''));
|
||||
$patLastName = $this->sanitizePersonNamePart((string) ($patientRow['p_lname'] ?? ''));
|
||||
$patFullName = trim($patFirstName . ($patLastName !== '' ? ' ' . $patLastName : ''));
|
||||
$patFullName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? ''));
|
||||
if ($patFullName === '') {
|
||||
$patFullName = trim((string) ($opdRow['P_name'] ?? ''));
|
||||
}
|
||||
$patFirstName = $patFullName;
|
||||
$patLastName = '';
|
||||
|
||||
$patientFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$phoneField = $this->resolveFirstField($patientFields, ['p_contact', 'phone', 'mobile', 'contact_no', 'mobile_no', 'p_phone']);
|
||||
|
|
@ -11837,12 +11837,12 @@ class Opd_prescription extends BaseController
|
|||
$abhaField = $this->resolvePatientAbhaField();
|
||||
$abhaAddress = $abhaField !== null ? trim((string) ($patientRow[$abhaField] ?? '')) : '';
|
||||
|
||||
$patFirstName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? ''));
|
||||
$patLastName = $this->sanitizePersonNamePart((string) ($patientRow['p_lname'] ?? ''));
|
||||
$patFullName = trim($patFirstName . ($patLastName !== '' ? ' ' . $patLastName : ''));
|
||||
$patFullName = $this->sanitizePersonNamePart((string) ($patientRow['p_fname'] ?? ''));
|
||||
if ($patFullName === '') {
|
||||
$patFullName = trim((string) ($opdRow['P_name'] ?? ''));
|
||||
}
|
||||
$patFirstName = $patFullName;
|
||||
$patLastName = '';
|
||||
|
||||
// Resolve phone field (patient_master may use p_contact, phone, mobile, etc.)
|
||||
$patientFields = $this->db->tableExists('patient_master') ? ($this->db->getFieldNames('patient_master') ?? []) : [];
|
||||
|
|
|
|||
|
|
@ -2349,11 +2349,7 @@ class Patient extends BaseController
|
|||
|
||||
$title = trim((string) ($patientRow['title'] ?? ''));
|
||||
$fname = trim((string) ($patientRow['p_fname'] ?? ''));
|
||||
$lname = trim((string) ($patientRow['p_lname'] ?? ''));
|
||||
if ($lname === '0' || strtolower($lname) === 'null') {
|
||||
$lname = '';
|
||||
}
|
||||
$patientName = trim($title . ' ' . $fname . ($lname !== '' ? ' ' . $lname : ''));
|
||||
$patientName = trim($title . ' ' . $fname);
|
||||
if ($patientName === '') {
|
||||
$patientName = 'Patient #' . $pno;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33647,7 +33647,7 @@
|
|||
}
|
||||
},
|
||||
"patient_master": {
|
||||
"create_sql": "CREATE TABLE IF NOT EXISTS `patient_master` ( `id` int NOT NULL AUTO_INCREMENT, `p_code` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Code', `old_uhid` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'If Any OLD HMS Patient Code', `udai` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Aadhar Number', `p_fname` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Full Name', `p_relative` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Patient Relative Type like Son, Husband , Father', `p_rname` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Patient Relative Name', `title` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT 'Mr.' COMMENT 'Title like Mr. , Dr., Master etc', `p_lname` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0', `mphone1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Phone Number', `mphone2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Another Number', `gender` int DEFAULT '1' COMMENT 'Gender', `insurance_id` int DEFAULT '0' COMMENT 'Insurance Id like Direct Patient 0 ', `insurance_no` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Insurance Card No.', `card_holder_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '' COMMENT 'Name on Card', `relation_patient_cardholder` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '', `issue_date` date DEFAULT NULL, `expiry_date` date DEFAULT NULL, `insurance_card_id` int DEFAULT '0', `dob` date DEFAULT NULL, `estimate_dob` int DEFAULT '1', `age` int DEFAULT NULL, `age_in` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `age_in_month` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `zip` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `email1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `email2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `register_date` datetime DEFAULT NULL, `add1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `add2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `city` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `district` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `state` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `country` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `profile_file_id` int DEFAULT '0', `profile_picture_update_by` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0', `referby` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `insert_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `last_update` datetime DEFAULT NULL, `last_visit` datetime DEFAULT NULL, `blood_group` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `no_of_visit` int DEFAULT '0', `log` text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci, `is_smoking` int DEFAULT '0', `is_alcohol` int DEFAULT '0', `is_drug_abuse` int DEFAULT '0', `is_tobacoo` int DEFAULT '0', `is_hypertesion` int DEFAULT '0', `is_niddm` int DEFAULT '0', `is_hbsag` int DEFAULT '0', `is_hcv` int DEFAULT '0', `is_hiv_I_II` int DEFAULT '0', `Others` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `abha_id` varchar(30) COLLATE utf8mb3_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `p_code` (`p_code`), KEY `p_fname_123` (`p_fname`,`p_lname`,`mphone1`,`mphone2`,`email1`,`email2`), KEY `udai` (`udai`), KEY `insert_date` (`insert_date`), KEY `old_uhid` (`old_uhid`), KEY `p_relative` (`p_relative`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;",
|
||||
"create_sql": "CREATE TABLE IF NOT EXISTS `patient_master` ( `id` int NOT NULL AUTO_INCREMENT, `p_code` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Code', `old_uhid` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'If Any OLD HMS Patient Code', `udai` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Aadhar Number', `p_fname` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Full Name', `p_relative` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Patient Relative Type like Son, Husband , Father', `p_rname` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Patient Relative Name', `title` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT 'Mr.' COMMENT 'Title like Mr. , Dr., Master etc', `mphone1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Phone Number', `mphone2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Patient Another Number', `gender` int DEFAULT '1' COMMENT 'Gender', `insurance_id` int DEFAULT '0' COMMENT 'Insurance Id like Direct Patient 0 ', `insurance_no` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0' COMMENT 'Insurance Card No.', `card_holder_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '' COMMENT 'Name on Card', `relation_patient_cardholder` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '', `issue_date` date DEFAULT NULL, `expiry_date` date DEFAULT NULL, `insurance_card_id` int DEFAULT '0', `dob` date DEFAULT NULL, `estimate_dob` int DEFAULT '1', `age` int DEFAULT NULL, `age_in` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `age_in_month` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `zip` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `email1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `email2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `register_date` datetime DEFAULT NULL, `add1` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `add2` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `city` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `district` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `state` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `country` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `profile_file_id` int DEFAULT '0', `profile_picture_update_by` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '0', `referby` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `insert_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `last_update` datetime DEFAULT NULL, `last_visit` datetime DEFAULT NULL, `blood_group` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `no_of_visit` int DEFAULT '0', `log` text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci, `is_smoking` int DEFAULT '0', `is_alcohol` int DEFAULT '0', `is_drug_abuse` int DEFAULT '0', `is_tobacoo` int DEFAULT '0', `is_hypertesion` int DEFAULT '0', `is_niddm` int DEFAULT '0', `is_hbsag` int DEFAULT '0', `is_hcv` int DEFAULT '0', `is_hiv_I_II` int DEFAULT '0', `Others` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL, `abha_id` varchar(30) COLLATE utf8mb3_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `p_code` (`p_code`), KEY `p_fname_123` (`p_fname`,`mphone1`,`mphone2`,`email1`,`email2`), KEY `udai` (`udai`), KEY `insert_date` (`insert_date`), KEY `old_uhid` (`old_uhid`), KEY `p_relative` (`p_relative`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;",
|
||||
"columns": {
|
||||
"id": {
|
||||
"Field": "id",
|
||||
|
|
@ -33737,17 +33737,6 @@
|
|||
"Privileges": "select,insert,update,references",
|
||||
"Comment": "Title like Mr. , Dr., Master etc"
|
||||
},
|
||||
"p_lname": {
|
||||
"Field": "p_lname",
|
||||
"Type": "varchar(100)",
|
||||
"Collation": "utf8mb3_unicode_ci",
|
||||
"Null": "YES",
|
||||
"Key": "",
|
||||
"Default": "0",
|
||||
"Extra": "",
|
||||
"Privileges": "select,insert,update,references",
|
||||
"Comment": ""
|
||||
},
|
||||
"mphone1": {
|
||||
"Field": "mphone1",
|
||||
"Type": "varchar(100)",
|
||||
|
|
@ -34267,7 +34256,6 @@
|
|||
"type": "BTREE",
|
||||
"columns": [
|
||||
"p_fname",
|
||||
"p_lname",
|
||||
"mphone1",
|
||||
"mphone2",
|
||||
"email1",
|
||||
|
|
|
|||
|
|
@ -28,28 +28,48 @@ class AbdmV3DirectHelper
|
|||
$this->loadSettings();
|
||||
}
|
||||
|
||||
protected function log(string $level, string $msg): void
|
||||
{
|
||||
if (function_exists('log_message')) {
|
||||
\log_message($level, $msg);
|
||||
} else {
|
||||
error_log("[AbdmV3DirectHelper] [{$level}] {$msg}");
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadSettings(): void
|
||||
{
|
||||
try {
|
||||
$db = \Config\Database::connect();
|
||||
if ($db->tableExists('hospital_setting')) {
|
||||
$rows = $db->table('hospital_setting')
|
||||
->whereIn('s_name', ['ABDM_CLIENT_ID', 'ABDM_CLIENT_SECRET'])
|
||||
->get()->getResultArray();
|
||||
foreach ($rows as $r) {
|
||||
$name = trim((string) ($r['s_name'] ?? ''));
|
||||
$val = trim((string) ($r['s_value'] ?? ''));
|
||||
if ($name === 'ABDM_CLIENT_ID' && $val !== '') {
|
||||
$this->clientId = $val;
|
||||
}
|
||||
if ($name === 'ABDM_CLIENT_SECRET' && $val !== '') {
|
||||
$this->clientSecret = $val;
|
||||
if (class_exists('\Config\Database') && function_exists('config')) {
|
||||
$db = \Config\Database::connect();
|
||||
if ($db->tableExists('hospital_setting')) {
|
||||
$rows = $db->table('hospital_setting')
|
||||
->whereIn('s_name', ['ABDM_CLIENT_ID', 'ABDM_CLIENT_SECRET'])
|
||||
->get()->getResultArray();
|
||||
foreach ($rows as $r) {
|
||||
$name = trim((string) ($r['s_name'] ?? ''));
|
||||
$val = trim((string) ($r['s_value'] ?? ''));
|
||||
if ($name === 'ABDM_CLIENT_ID' && $val !== '') {
|
||||
$this->clientId = $val;
|
||||
}
|
||||
if ($name === 'ABDM_CLIENT_SECRET' && $val !== '') {
|
||||
$this->clientSecret = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if (function_exists('log_message')) {
|
||||
log_message('warning', '[AbdmV3DirectHelper] loadSettings failed: ' . $e->getMessage());
|
||||
$this->log('warning', '[AbdmV3DirectHelper] loadSettings failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (function_exists('env')) {
|
||||
$envClientId = env('abdm.directClientId', env('BRIDGE_SOURCE_CODE', ''));
|
||||
if (! empty($envClientId) && $this->clientId === 'SBXID_033661') {
|
||||
$this->clientId = (string) $envClientId;
|
||||
}
|
||||
$envSecret = env('abdm.directClientSecret', '');
|
||||
if (! empty($envSecret) && $this->clientSecret === '656f79f1-ef99-495f-9f37-713219ecbbcf') {
|
||||
$this->clientSecret = (string) $envSecret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,10 +124,10 @@ class AbdmV3DirectHelper
|
|||
/**
|
||||
* Get or refresh gateway session Bearer token.
|
||||
*/
|
||||
public function getSessionToken(): string
|
||||
public function getSessionToken(bool $forceRefresh = false): string
|
||||
{
|
||||
$cacheKey = 'abdm_v3_direct_session_token';
|
||||
if (function_exists('cache')) {
|
||||
if (! $forceRefresh && function_exists('cache')) {
|
||||
try {
|
||||
$cached = cache()->get($cacheKey);
|
||||
if (is_string($cached) && trim($cached) !== '') {
|
||||
|
|
@ -141,10 +161,10 @@ class AbdmV3DirectHelper
|
|||
/**
|
||||
* Get or refresh ABDM RSA Public Certificate PEM.
|
||||
*/
|
||||
public function getPublicCertificatePem(): string
|
||||
public function getPublicCertificatePem(bool $forceRefresh = false): string
|
||||
{
|
||||
$cacheKey = 'abdm_v3_direct_public_cert_pem';
|
||||
if (function_exists('cache')) {
|
||||
if (! $forceRefresh && function_exists('cache')) {
|
||||
try {
|
||||
$cached = cache()->get($cacheKey);
|
||||
if (is_string($cached) && trim($cached) !== '') {
|
||||
|
|
@ -153,7 +173,7 @@ class AbdmV3DirectHelper
|
|||
} catch (\Throwable $e) {}
|
||||
}
|
||||
|
||||
$token = $this->getSessionToken();
|
||||
$token = $this->getSessionToken($forceRefresh);
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('Failed to obtain ABDM session token for certificate fetch.');
|
||||
}
|
||||
|
|
@ -162,6 +182,7 @@ class AbdmV3DirectHelper
|
|||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
$rawKey = (string) ($resp['json']['publicKey'] ?? $resp['json']['Certificate'] ?? $resp['raw'] ?? '');
|
||||
|
|
@ -192,13 +213,22 @@ class AbdmV3DirectHelper
|
|||
$pem = $this->getPublicCertificatePem();
|
||||
$pubKey = openssl_pkey_get_public($pem);
|
||||
if ($pubKey === false) {
|
||||
throw new \RuntimeException('Invalid ABDM public key resource: ' . openssl_error_string());
|
||||
$pem = $this->getPublicCertificatePem(true);
|
||||
$pubKey = openssl_pkey_get_public($pem);
|
||||
if ($pubKey === false) {
|
||||
throw new \RuntimeException('Invalid ABDM public key resource: ' . openssl_error_string());
|
||||
}
|
||||
}
|
||||
|
||||
$encrypted = '';
|
||||
$ok = openssl_public_encrypt($plainText, $encrypted, $pubKey, OPENSSL_PKCS1_OAEP_PADDING);
|
||||
if (! $ok) {
|
||||
throw new \RuntimeException('RSA-OAEP encryption failed: ' . openssl_error_string());
|
||||
$pem = $this->getPublicCertificatePem(true);
|
||||
$pubKey = openssl_pkey_get_public($pem);
|
||||
$ok = openssl_public_encrypt($plainText, $encrypted, $pubKey, OPENSSL_PKCS1_OAEP_PADDING);
|
||||
if (! $ok) {
|
||||
throw new \RuntimeException('RSA-OAEP encryption failed: ' . openssl_error_string());
|
||||
}
|
||||
}
|
||||
|
||||
return base64_encode($encrypted);
|
||||
|
|
@ -234,6 +264,20 @@ class AbdmV3DirectHelper
|
|||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
// If 401 Unauthorized, refresh token and retry once
|
||||
if ($resp['code'] === 401) {
|
||||
$token = $this->getSessionToken(true);
|
||||
$resp = $this->curlCall($this->searchUrl, 'POST', [
|
||||
'scope' => ['search-abha'],
|
||||
'mobile' => $encMobile,
|
||||
], [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300 && ! empty($resp['json'])) {
|
||||
$firstBlock = is_array($resp['json']) && isset($resp['json'][0]) ? $resp['json'][0] : $resp['json'];
|
||||
$txnId = (string) ($firstBlock['txnId'] ?? '');
|
||||
|
|
@ -274,7 +318,7 @@ class AbdmV3DirectHelper
|
|||
$errMsg = (string) ($resp['json']['message'] ?? $resp['json']['error']['message'] ?? 'ABDM search returned no accounts for this mobile.');
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] searchByMobile exception: ' . $e->getMessage());
|
||||
$this->log('error', '[AbdmV3DirectHelper] searchByMobile exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
|
@ -317,6 +361,16 @@ class AbdmV3DirectHelper
|
|||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
if ($resp['code'] === 401) {
|
||||
$token = $this->getSessionToken(true);
|
||||
$resp = $this->curlCall($this->otpUrl, 'POST', $payload, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300 && ! empty($resp['json'])) {
|
||||
$newTxnId = (string) ($resp['json']['txnId'] ?? $searchTxnId);
|
||||
$msg = (string) ($resp['json']['message'] ?? 'OTP sent successfully.');
|
||||
|
|
@ -330,7 +384,7 @@ class AbdmV3DirectHelper
|
|||
$errMsg = (string) ($resp['json']['message'] ?? $resp['json']['error']['message'] ?? 'Failed to request OTP from ABDM.');
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] requestProfileOtp exception: ' . $e->getMessage());
|
||||
$this->log('error', '[AbdmV3DirectHelper] requestProfileOtp exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
|
@ -380,6 +434,16 @@ class AbdmV3DirectHelper
|
|||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
if ($resp['code'] === 401) {
|
||||
$token = $this->getSessionToken(true);
|
||||
$resp = $this->curlCall($this->verifyUrl, 'POST', $payload, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300 && ! empty($resp['json'])) {
|
||||
$authResult = strtolower((string) ($resp['json']['authResult'] ?? ''));
|
||||
if ($authResult === 'failed') {
|
||||
|
|
@ -413,7 +477,7 @@ class AbdmV3DirectHelper
|
|||
$errMsg = (string) ($resp['json']['message'] ?? $resp['json']['error']['message'] ?? 'OTP verification failed.');
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] verifyProfileOtp exception: ' . $e->getMessage());
|
||||
$this->log('error', '[AbdmV3DirectHelper] verifyProfileOtp exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
|
@ -580,7 +644,7 @@ class AbdmV3DirectHelper
|
|||
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] verifyLoginOtpByAadhaar exception: ' . $e->getMessage());
|
||||
$this->log('error', '[AbdmV3DirectHelper] verifyLoginOtpByAadhaar exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class AbhaDemographicValidator
|
|||
}
|
||||
|
||||
$builder = $db->table('patient_master')
|
||||
->select('id, p_code, p_fname, p_lname, gender, dob, age, mphone1, abha_id, abha_address');
|
||||
->select('id, p_code, p_fname, gender, dob, age, mphone1, abha_id, abha_address');
|
||||
|
||||
$matchedAny = false;
|
||||
$builder->groupStart();
|
||||
|
|
@ -101,7 +101,7 @@ class AbhaDemographicValidator
|
|||
|
||||
$conflictId = (int) ($conflict['id'] ?? 0);
|
||||
$conflictCode = (string) ($conflict['p_code'] ?? ('P-' . $conflictId));
|
||||
$conflictName = trim(($conflict['p_fname'] ?? '') . ' ' . ($conflict['p_lname'] ?? ''));
|
||||
$conflictName = trim((string) ($conflict['p_fname'] ?? ''));
|
||||
$matchedValue = $hasAddressCheck ? $cleanAbhaAddress : $cleanAbhaId;
|
||||
|
||||
return [
|
||||
|
|
@ -152,9 +152,6 @@ class AbhaDemographicValidator
|
|||
|
||||
// 3. Name Similarity Validation
|
||||
$patientName = trim((string) ($patientRow['p_fname'] ?? ($patientRow['input_name'] ?? '')));
|
||||
if (! empty($patientRow['p_lname']) && $patientRow['p_lname'] !== '0') {
|
||||
$patientName .= ' ' . trim((string) $patientRow['p_lname']);
|
||||
}
|
||||
$abhaName = trim((string) ($abhaProfile['full_name'] ?? ($abhaProfile['name'] ?? '')));
|
||||
|
||||
if ($patientName !== '' && $abhaName !== '') {
|
||||
|
|
|
|||
|
|
@ -205,6 +205,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notice Banner for Pending Linking / Auto-Sync -->
|
||||
<div id="inspNoticeBanner" class="mb-3 d-none"></div>
|
||||
|
||||
<!-- Care Contexts & FHIR Bundles Details Table -->
|
||||
<div class="table-responsive border rounded">
|
||||
<table class="table table-sm table-hover mb-0" id="inspCareContextTable">
|
||||
|
|
@ -1287,24 +1290,54 @@
|
|||
document.getElementById('inspAbhaNumber').textContent = pat.abha_number || '-';
|
||||
document.getElementById('inspPhone').textContent = pat.phone || '-';
|
||||
|
||||
var linkedCount = 0;
|
||||
var totalCount = contexts.length;
|
||||
var gatewayLinkedCount = 0;
|
||||
var localLinkedCount = 0;
|
||||
var fhirCount = 0;
|
||||
var pendingPhrList = [];
|
||||
|
||||
contexts.forEach(function (c) {
|
||||
if (c.is_linked) linkedCount++;
|
||||
if (c.is_gateway_linked) {
|
||||
gatewayLinkedCount++;
|
||||
} else if (c.is_linked) {
|
||||
localLinkedCount++;
|
||||
pendingPhrList.push(c.ref || c.display || '');
|
||||
}
|
||||
if (c.is_fhir_ready) fhirCount++;
|
||||
});
|
||||
|
||||
document.getElementById('inspTotalContexts').textContent = contexts.length;
|
||||
document.getElementById('inspLinkedContexts').textContent = linkedCount;
|
||||
document.getElementById('inspUnlinkedContexts').textContent = contexts.length - linkedCount;
|
||||
document.getElementById('inspTotalContexts').textContent = totalCount;
|
||||
document.getElementById('inspLinkedContexts').textContent = gatewayLinkedCount;
|
||||
document.getElementById('inspUnlinkedContexts').textContent = totalCount - gatewayLinkedCount;
|
||||
document.getElementById('inspFhirCount').textContent = fhirCount;
|
||||
|
||||
if (linkedRefs.length > 0) {
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-success"><i class="bi bi-shield-check me-1"></i>Gateway Verified (' + linkedRefs.length + ' links)</span>';
|
||||
} else if (linkedCount > 0) {
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-primary"><i class="bi bi-check2-circle me-1"></i>Local Linked</span>';
|
||||
if (gatewayLinkedCount > 0) {
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-success"><i class="bi bi-shield-check me-1"></i>Gateway Verified (' + gatewayLinkedCount + ' in PHR)</span>';
|
||||
} else if (localLinkedCount > 0) {
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-warning text-dark"><i class="bi bi-clock-history me-1"></i>Local Ready (' + localLinkedCount + ' pending PHR)</span>';
|
||||
} else {
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle me-1"></i>Unlinked</span>';
|
||||
document.getElementById('inspGatewayStatus').innerHTML = '<span class="badge bg-secondary"><i class="bi bi-exclamation-triangle me-1"></i>Unlinked</span>';
|
||||
}
|
||||
|
||||
var noticeBanner = document.getElementById('inspNoticeBanner');
|
||||
if (noticeBanner) {
|
||||
if (localLinkedCount > 0) {
|
||||
noticeBanner.className = 'alert alert-warning d-flex align-items-center mb-3 py-2 px-3 shadow-sm';
|
||||
noticeBanner.innerHTML = '<i class="bi bi-info-circle-fill fs-4 text-warning me-3 flex-shrink-0"></i>' +
|
||||
'<div>' +
|
||||
'<strong>Pending PHR App Link:</strong> Patient has <strong>' + localLinkedCount + '</strong> record(s) ready in HMS (' + hesc(pendingPhrList.slice(0, 3).join(', ')) + (pendingPhrList.length > 3 ? ' and more' : '') + ') that are not yet confirmed in the PHR app.' +
|
||||
'<br><span class="small text-muted">Records sync automatically via HIP Auto-Link. Alternatively, ask the patient to tap <strong>"Discover Records" / "Fetch New Records"</strong> in their ABHA app (e.g. ABHA / Aarogya Setu / Eka Care) to pull them instantly.</span>' +
|
||||
'</div>';
|
||||
} else if (gatewayLinkedCount > 0 && totalCount === gatewayLinkedCount) {
|
||||
noticeBanner.className = 'alert alert-success d-flex align-items-center mb-3 py-2 px-3 shadow-sm';
|
||||
noticeBanner.innerHTML = '<i class="bi bi-check-circle-fill fs-4 text-success me-3 flex-shrink-0"></i>' +
|
||||
'<div>' +
|
||||
'<strong>All Records Synced:</strong> All ' + gatewayLinkedCount + ' care context(s) are confirmed linked on ABDM Gateway and accessible in the patient\'s PHR app.' +
|
||||
'</div>';
|
||||
} else {
|
||||
noticeBanner.className = 'mb-3 d-none';
|
||||
noticeBanner.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (contexts.length === 0) {
|
||||
|
|
@ -1315,13 +1348,26 @@
|
|||
var html = '';
|
||||
contexts.forEach(function (c) {
|
||||
var ref = c.ref || '';
|
||||
var isGatewayLinked = !!c.is_gateway_linked;
|
||||
var isLinked = !!c.is_linked;
|
||||
var isFhir = !!c.is_fhir_ready;
|
||||
var hiType = c.hi_type || 'OPConsultRecord';
|
||||
|
||||
var linkBadge = isLinked
|
||||
? '<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>LINKED</span>'
|
||||
: '<span class="badge bg-warning text-dark"><i class="bi bi-clock me-1"></i>UNLINKED</span>';
|
||||
var linkBadge = '';
|
||||
var actionBtn = '';
|
||||
if (isGatewayLinked) {
|
||||
linkBadge = '<span class="badge bg-success" title="Confirmed on ABDM Gateway and linked in patient PHR app"><i class="bi bi-shield-check me-1"></i>Linked in PHR</span>';
|
||||
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>In PHR</span>';
|
||||
} else if (isLinked) {
|
||||
linkBadge = '<span class="badge bg-warning text-dark" title="Record is published in HMS; awaiting ABDM auto-link or PHR app discovery"><i class="bi bi-clock-history me-1"></i>Ready - Pending PHR Link</span>';
|
||||
actionBtn = '<button type="button" class="btn btn-sm btn-outline-primary 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) + '" title="Initiate HIP auto-linking to PHR app"><i class="bi bi-arrow-repeat me-1"></i>Link to PHR</button>';
|
||||
} else if (!isFhir) {
|
||||
linkBadge = '<span class="badge bg-secondary"><i class="bi bi-clock me-1"></i>UNLINKED</span>';
|
||||
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 {
|
||||
linkBadge = '<span class="badge bg-secondary"><i class="bi bi-clock me-1"></i>UNLINKED</span>';
|
||||
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>';
|
||||
}
|
||||
|
||||
var fhirBadge = isFhir
|
||||
? '<span class="badge bg-info"><i class="bi bi-file-earmark-code me-1"></i>Ready</span>'
|
||||
|
|
@ -1342,15 +1388,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
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>';
|
||||
}
|
||||
|
||||
html += '<tr>';
|
||||
html += '<td><code class="fw-semibold text-primary">' + hesc(ref) + '</code></td>';
|
||||
html += '<td>' + hesc(c.display || ref) + '</td>';
|
||||
|
|
@ -2113,11 +2150,14 @@
|
|||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res && res.ok === 1 && res.links && res.links.length > 0) {
|
||||
var links = (res && (res.care_contexts || res.links || (res.data && res.data.patient && res.data.patient.careContexts) || (res.data && res.data[0] && res.data[0].careContexts))) || [];
|
||||
if (res && res.ok === 1 && links.length > 0) {
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = '<span class="badge bg-success"><i class="bi bi-shield-check me-1"></i>Gateway Verified (' + res.links.length + ' links)</span>';
|
||||
statusEl.innerHTML = '<span class="badge bg-success"><i class="bi bi-shield-check me-1"></i>Gateway Verified (' + links.length + ' links)</span>';
|
||||
}
|
||||
setStatus('ABDM Gateway verified ' + res.links.length + ' linked care context(s) for patient.');
|
||||
setStatus('ABDM Gateway verified ' + links.length + ' linked care context(s) for patient.');
|
||||
// Reload inspector so table highlights verified vs pending
|
||||
loadPatientAbhaInspector(currentAbhaFilter);
|
||||
} else if (res && res.ok === 1) {
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = '<span class="badge bg-warning text-dark"><i class="bi bi-info-circle me-1"></i>No Gateway Links Found</span>';
|
||||
|
|
|
|||
|
|
@ -68,6 +68,14 @@
|
|||
.abha-mobile-modal .abha-card-preview { min-height:230px; display:grid; place-items:center; border:1px solid #dce3ec; background:#f8fafc; border-radius:8px; padding:12px; }
|
||||
.abha-mobile-modal .abha-card-preview img { max-width:100%; max-height:360px; object-fit:contain; border-radius:6px; box-shadow:0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
.abha-mobile-modal .bg-primary-subtle { background-color: #e7f1ff !important; color: #0d6efd !important; }
|
||||
.abha-mobile-modal .bg-success-subtle { background-color: #e6f8f0 !important; color: #198754 !important; }
|
||||
.abha-mobile-modal .bg-secondary-subtle { background-color: #f1f3f5 !important; color: #495057 !important; }
|
||||
.abha-mobile-modal .cursor-pointer { cursor: pointer; }
|
||||
.abha-mobile-modal .abha-addr-card { transition: all 0.15s ease-in-out; }
|
||||
.abha-mobile-modal .abha-addr-card:hover { border-color: #356cf4 !important; background-color: #f8faff !important; }
|
||||
.abha-mobile-modal .abha-addr-card.selected { border-color: #356cf4 !important; background-color: #f0f5ff !important; box-shadow: 0 0 0 1px #356cf4; }
|
||||
|
||||
@media (max-width:575.98px) {
|
||||
.abha-mobile-modal .modal-body { padding:16px; }
|
||||
.abha-mobile-step:not(:last-child)::after { width:18px; }
|
||||
|
|
@ -194,6 +202,17 @@
|
|||
<div class="text-muted fw-semibold" id="abhaMobileProfileAddress"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ABHA Address Selector when user has one or multiple addresses -->
|
||||
<div class="card border border-primary-subtle bg-light-subtle p-3 rounded-3 mb-3 d-none" id="abhaAddressSelectorCard">
|
||||
<label class="form-label fw-bold text-dark mb-2 d-flex align-items-center justify-content-between">
|
||||
<span><i class="bi bi-at text-primary me-1"></i>Select ABHA Address to Link / Update:</span>
|
||||
<span class="badge bg-primary rounded-pill" id="abhaAddressCountBadge">1</span>
|
||||
</label>
|
||||
<div id="abhaAddressRadioGroup" class="d-flex flex-column gap-2">
|
||||
<!-- Populated dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="abha-profile-list">
|
||||
<div><dt>ABHA Number</dt><dd id="abhaMobileProfileNumber">-</dd></div>
|
||||
<div><dt>ABHA Address</dt><dd id="abhaMobileProfileId">-</dd></div>
|
||||
|
|
@ -416,21 +435,39 @@ window.AbhaMobileModal = (function () {
|
|||
? '<img src="' + (String(prof.profile_photo).indexOf('data:') === 0 ? prof.profile_photo : 'data:image/jpeg;base64,' + prof.profile_photo) + '" class="rounded-circle" style="width:48px;height:48px;object-fit:cover">'
|
||||
: '<div class="abha-profile-avatar"><i class="bi ' + iconClass + '"></i></div>';
|
||||
|
||||
var addrs = Array.isArray(prof.all_abha_addresses) && prof.all_abha_addresses.length > 0
|
||||
? prof.all_abha_addresses
|
||||
: (prof.abha_address ? [prof.abha_address] : []);
|
||||
var addrBadges = '';
|
||||
if (addrs.length > 0) {
|
||||
addrBadges = addrs.map(function(a) {
|
||||
return '<span class="badge bg-primary-subtle text-primary border border-primary-subtle font-monospace"><i class="bi bi-at"></i>' + escapeHtml(a) + '</span>';
|
||||
}).join(' ');
|
||||
} else {
|
||||
addrBadges = '<span class="badge bg-light text-muted border"><i class="bi bi-person-badge me-1"></i>ABHA Profile</span>';
|
||||
}
|
||||
|
||||
var uhidBadge = prof.p_code
|
||||
? '<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle"><i class="bi bi-hospital me-1"></i>UHID: ' + escapeHtml(prof.p_code) + '</span>'
|
||||
: '';
|
||||
|
||||
var card = document.createElement('div');
|
||||
card.className = 'abha-profile-card d-flex align-items-center justify-content-between gap-3 ' + (isSelected ? 'selected' : '');
|
||||
card.setAttribute('data-index', index);
|
||||
card.innerHTML =
|
||||
'<div class="d-flex align-items-center gap-3">' +
|
||||
'<div class="d-flex align-items-center gap-3 flex-grow-1">' +
|
||||
'<div class="form-check m-0">' +
|
||||
'<input class="form-check-input profile-radio" type="radio" name="abhaProfileOption" value="' + index + '" ' + (isSelected ? 'checked' : '') + '>' +
|
||||
'</div>' +
|
||||
photoHtml +
|
||||
'<div>' +
|
||||
'<div class="d-flex align-items-center gap-2 flex-wrap">' +
|
||||
'<h6 class="mb-0 fw-bold text-dark">' + escapeHtml(name) + '</h6>' +
|
||||
'<div class="flex-grow-1">' +
|
||||
'<div class="d-flex align-items-center gap-2 flex-wrap mb-1">' +
|
||||
'<h6 class="mb-0 fw-bold text-dark fs-6">' + escapeHtml(name) + '</h6>' +
|
||||
(isVerified ? '<span class="badge bg-success-subtle text-success border border-success-subtle"><i class="bi bi-patch-check-fill me-1"></i>Active</span>' : '') +
|
||||
uhidBadge +
|
||||
'</div>' +
|
||||
'<div class="text-muted small mt-1">' +
|
||||
'<div class="mb-1 d-flex flex-wrap gap-1">' + addrBadges + '</div>' +
|
||||
'<div class="text-muted small">' +
|
||||
'<span class="me-3"><i class="bi bi-credit-card-2-front me-1"></i>ABHA: <strong>' + escapeHtml(formatAbha(abhaNum)) + '</strong></span>' +
|
||||
'<span class="me-3"><i class="bi bi-gender-ambiguous me-1"></i>' + escapeHtml(gender) + '</span>' +
|
||||
(dob && dob !== '-' ? '<span><i class="bi bi-calendar-event me-1"></i>DOB: ' + escapeHtml(dob) + '</span>' : '') +
|
||||
|
|
@ -610,6 +647,82 @@ window.AbhaMobileModal = (function () {
|
|||
if (elMobile) elMobile.textContent = maskMobile(profile.mobile || searchMobile);
|
||||
if (elFullAddr) elFullAddr.textContent = [profile.address, profile.district, profile.state, profile.zip].filter(Boolean).join(', ') || '-';
|
||||
|
||||
// Gather all known ABHA addresses for this profile
|
||||
var allAddrs = [];
|
||||
if (Array.isArray(profile.all_abha_addresses)) {
|
||||
allAddrs = allAddrs.concat(profile.all_abha_addresses);
|
||||
}
|
||||
if (selectedProfile && Array.isArray(selectedProfile.all_abha_addresses)) {
|
||||
allAddrs = allAddrs.concat(selectedProfile.all_abha_addresses);
|
||||
}
|
||||
if (profile.abha_address) {
|
||||
allAddrs.push(profile.abha_address);
|
||||
}
|
||||
if (selectedProfile && selectedProfile.abha_address) {
|
||||
allAddrs.push(selectedProfile.abha_address);
|
||||
}
|
||||
|
||||
var uniqueAddrs = [];
|
||||
allAddrs.forEach(function(a) {
|
||||
a = String(a || '').trim();
|
||||
if (a && a.indexOf('@') !== -1 && uniqueAddrs.indexOf(a) === -1) {
|
||||
uniqueAddrs.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
var currentSelectedAddr = profile.abha_address || uniqueAddrs[0] || '';
|
||||
profile.abha_address = currentSelectedAddr;
|
||||
verifiedProfile.abha_address = currentSelectedAddr;
|
||||
|
||||
var addrSelectorCard = document.getElementById('abhaAddressSelectorCard');
|
||||
var addrGroup = document.getElementById('abhaAddressRadioGroup');
|
||||
var addrCountBadge = document.getElementById('abhaAddressCountBadge');
|
||||
|
||||
if (addrSelectorCard && addrGroup) {
|
||||
if (uniqueAddrs.length > 0) {
|
||||
addrSelectorCard.classList.remove('d-none');
|
||||
if (addrCountBadge) addrCountBadge.textContent = uniqueAddrs.length;
|
||||
addrGroup.innerHTML = '';
|
||||
uniqueAddrs.forEach(function(addr, idx) {
|
||||
var isChecked = (addr === currentSelectedAddr);
|
||||
var itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'form-check p-2 px-3 border rounded-2 bg-white d-flex align-items-center gap-2 cursor-pointer abha-addr-card ' + (isChecked ? 'selected border-primary' : 'border-secondary-subtle');
|
||||
itemDiv.innerHTML =
|
||||
'<input class="form-check-input m-0 abha-addr-radio cursor-pointer" type="radio" name="selectedAbhaAddressOption" id="addrOpt_' + idx + '" value="' + escapeHtml(addr) + '" ' + (isChecked ? 'checked' : '') + '>' +
|
||||
'<label class="form-check-label flex-grow-1 cursor-pointer fw-semibold font-monospace text-dark mb-0" for="addrOpt_' + idx + '">' +
|
||||
'<i class="bi bi-at text-primary me-1"></i>' + escapeHtml(addr) +
|
||||
'</label>' +
|
||||
(isChecked ? '<span class="badge bg-success-subtle text-success border border-success-subtle active-badge"><i class="bi bi-check2 me-1"></i>Selected</span>' : '');
|
||||
|
||||
itemDiv.addEventListener('click', function(e) {
|
||||
if (e.target.tagName !== 'INPUT') {
|
||||
var r = itemDiv.querySelector('input[type="radio"]');
|
||||
if (r) r.checked = true;
|
||||
}
|
||||
profile.abha_address = addr;
|
||||
verifiedProfile.abha_address = addr;
|
||||
if (elAddr) elAddr.textContent = addr;
|
||||
if (elId) elId.textContent = addr;
|
||||
addrGroup.querySelectorAll('.abha-addr-card').forEach(function(div) {
|
||||
div.classList.remove('selected', 'border-primary');
|
||||
div.classList.add('border-secondary-subtle');
|
||||
var b = div.querySelector('.active-badge');
|
||||
if (b) b.remove();
|
||||
});
|
||||
itemDiv.classList.remove('border-secondary-subtle');
|
||||
itemDiv.classList.add('selected', 'border-primary');
|
||||
var span = document.createElement('span');
|
||||
span.className = 'badge bg-success-subtle text-success border border-success-subtle active-badge';
|
||||
span.innerHTML = '<i class="bi bi-check2 me-1"></i>Selected';
|
||||
itemDiv.appendChild(span);
|
||||
});
|
||||
addrGroup.appendChild(itemDiv);
|
||||
});
|
||||
} else {
|
||||
addrSelectorCard.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
var photo = profile.photo ? (String(profile.photo).indexOf('data:') === 0 ? profile.photo : 'data:image/jpeg;base64,' + profile.photo) : '';
|
||||
var photoEl = document.getElementById('abhaMobilePhoto');
|
||||
if (photoEl) {
|
||||
|
|
@ -704,11 +817,62 @@ window.AbhaMobileModal = (function () {
|
|||
if (compareBtn) compareBtn.onclick = function () {
|
||||
if (!verifiedProfile) return;
|
||||
var profile = verifiedProfile;
|
||||
|
||||
// If auto-matched to an existing HMS patient:
|
||||
if (profile.need_confirmation === false && Number(profile.patient_id || 0) > 0) {
|
||||
alertBox('success', 'This ABHA is already linked to HMS patient <strong>' + escapeHtml(profile.p_code || '') + '</strong>. Close this window when done.');
|
||||
if (typeof onVerified === 'function') onVerified(profile);
|
||||
var c = csrf();
|
||||
var postData = new URLSearchParams();
|
||||
postData.append('action', 'existing');
|
||||
postData.append('update_mode', 'update');
|
||||
postData.append('patient_id', profile.patient_id);
|
||||
postData.append('abha_number', profile.abha_number || '');
|
||||
postData.append('name', profile.name || '');
|
||||
postData.append('mobile', profile.mobile || searchMobile);
|
||||
postData.append('gender', profile.gender || '');
|
||||
postData.append('dob', profile.dob || '');
|
||||
postData.append('abha_address', profile.abha_address || '');
|
||||
postData.append('photo', profile.photo || '');
|
||||
postData.append('card_base64', profile.card_base64 || '');
|
||||
postData.append('verified_status', 'ACTIVE');
|
||||
postData.append('verification_type', 'VERIFIED');
|
||||
postData.append('kyc_verified', profile.kyc_verified ? '1' : '0');
|
||||
postData.append('mobile_verified', '1');
|
||||
postData.append('<?= csrf_token() ?>', c);
|
||||
|
||||
compareBtn.disabled = true;
|
||||
compareBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Linking to HMS...';
|
||||
|
||||
fetch('<?= base_url('abha/create/confirm_patient') ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: postData.toString()
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(resp) {
|
||||
compareBtn.disabled = false;
|
||||
compareBtn.innerHTML = '<i class="bi bi-people me-1"></i>Compare with HMS Patients';
|
||||
if (resp && resp.ok == 1) {
|
||||
alertBox('success', 'ABHA <strong>' + escapeHtml(profile.abha_address) + '</strong> successfully linked with HMS Patient <strong>' + escapeHtml(profile.p_code) + '</strong>.');
|
||||
modalEl.addEventListener('hidden.bs.modal', function handler() {
|
||||
modalEl.removeEventListener('hidden.bs.modal', handler);
|
||||
if (typeof onVerified === 'function') onVerified(Object.assign({}, profile, resp));
|
||||
});
|
||||
setTimeout(function() { modal.hide(); }, 800);
|
||||
} else {
|
||||
alertBox('danger', (resp && resp.error_text) || 'Failed to update patient record.');
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
compareBtn.disabled = false;
|
||||
compareBtn.innerHTML = '<i class="bi bi-people me-1"></i>Compare with HMS Patients';
|
||||
alertBox('danger', 'Error updating patient record in HMS.');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
modalEl.addEventListener('hidden.bs.modal', function handler() {
|
||||
modalEl.removeEventListener('hidden.bs.modal', handler);
|
||||
if (typeof onVerified === 'function') onVerified(profile);
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
|||
id INTEGER PRIMARY KEY,
|
||||
p_code TEXT,
|
||||
p_fname TEXT,
|
||||
p_lname TEXT,
|
||||
dob TEXT,
|
||||
age TEXT,
|
||||
gender INTEGER,
|
||||
|
|
@ -153,8 +152,7 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
|||
$db->table('patient_master')->insert([
|
||||
'id' => $testPatientId,
|
||||
'p_code' => 'P-TEST-998876',
|
||||
'p_fname' => 'Janvi',
|
||||
'p_lname' => 'Bisht',
|
||||
'p_fname' => 'Janvi Bisht',
|
||||
'mphone1' => '9876543212',
|
||||
'gender' => 2,
|
||||
'abha_address' => 'janvibisht2506@sbx',
|
||||
|
|
@ -199,7 +197,6 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
|||
id INTEGER PRIMARY KEY,
|
||||
p_code TEXT,
|
||||
p_fname TEXT,
|
||||
p_lname TEXT,
|
||||
dob TEXT,
|
||||
age TEXT,
|
||||
gender INTEGER,
|
||||
|
|
@ -214,8 +211,7 @@ final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
|||
$db->table('patient_master')->insert([
|
||||
'id' => $testPatientId,
|
||||
'p_code' => 'P-TEST-998877',
|
||||
'p_fname' => 'Test',
|
||||
'p_lname' => 'AbhaPatient',
|
||||
'p_fname' => 'Test AbhaPatient',
|
||||
'mphone1' => '9876543210',
|
||||
'gender' => 1,
|
||||
'abha_address' => 'testpatient998877@sbx',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user