fix(abdm): persist HIP link status to db and sync patient profile verification badge

This commit is contained in:
DevSoftTech Server 2026-10-08 17:05:45 +05:30
parent c72b5cc338
commit e2974ec719
5 changed files with 185 additions and 15 deletions

View File

@ -16,6 +16,33 @@ class TestAbdmInspect extends BaseCommand
$abha = $params[0] ?? CLI::getOption('abha') ?? 'meerabisht1981@sbx';
$patientId = (int) ($params[1] ?? CLI::getOption('patient_id') ?? 15352);
$db = \Config\Database::connect();
if (CLI::getOption('fix-link')) {
$now = date('Y-m-d H:i:s');
$db->table('patient_master')->where('id', $patientId)->update([
'abha_verified_status' => 'LINKED',
'abdm_linked_at' => $now,
]);
$existing = $db->table('record_links')->where('care_context_reference', 'REG-P261010000' . $patientId)->get(1)->getRowArray();
if (empty($existing)) {
$db->table('record_links')->insert([
'abha_id' => $abha,
'care_context_reference' => 'REG-P261010000' . $patientId,
'link_status' => 'linked',
'linked_at' => $now,
'created_at' => $now,
'updated_at' => $now,
]);
} else {
$db->table('record_links')->where('id', $existing['id'])->update([
'link_status' => 'linked',
'linked_at' => $now,
'updated_at' => $now,
]);
}
CLI::write("Recorded link for Patient {$patientId} in DB.", 'green');
}
CLI::write("Inspecting ABHA: {$abha} (Patient ID: {$patientId})...", 'yellow');
$_SERVER['REQUEST_METHOD'] = 'GET';
@ -53,5 +80,24 @@ class TestAbdmInspect extends BaseCommand
}
CLI::table($table, ['Reference', 'HI Type', 'FHIR', 'Status', 'Display']);
$linksRes = $gateway->hipPatientLinks();
$linksData = json_decode($linksRes->getBody(), true);
$linkedContexts = $linksData['care_contexts'] ?? [];
if (! empty($linkedContexts)) {
CLI::newLine();
CLI::write("HIP Linked Records ({$linksData['total_linked']}):", 'green');
$linkTable = [];
foreach ($linkedContexts as $lc) {
$linkTable[] = [
'Reference' => $lc['referenceNumber'] ?? '',
'Display' => $lc['display'] ?? '',
'Status' => strtoupper($lc['status'] ?? 'linked'),
'Linked At' => $lc['linked_at'] ?? 'N/A',
'Source' => $lc['source'] ?? 'bridge',
];
}
CLI::table($linkTable, ['Reference', 'Display', 'Status', 'Linked At', 'Source']);
}
}
}

View File

@ -13200,6 +13200,69 @@ class AbdmGateway extends BaseController
}
$result = $this->connector->hipLinkCareContext($body);
if (($result['ok'] ?? 0) === 1) {
$now = Time::now('Asia/Kolkata')->toDateTimeString();
// 1. Update patient_master verification status & timestamps
if ($patientId > 0 && $this->db->tableExists('patient_master')) {
$pmUp = [
'abha_verified_status' => 'LINKED',
'abdm_linked_at' => $now,
];
if (! empty($body['abha_address'])) {
$pmUp['abha_address'] = (string) $body['abha_address'];
}
if (! empty($body['abha_number'])) {
$pmUp['abha_id'] = (string) $body['abha_number'];
}
$this->db->table('patient_master')->where('id', $patientId)->update($pmUp);
}
// 2. Persist care contexts into record_links so status is instantly marked LINKED in UI
if ($this->db->tableExists('record_links') && ! empty($body['care_contexts']) && is_array($body['care_contexts'])) {
foreach ($body['care_contexts'] as $ccItem) {
$ccRef = trim((string) ($ccItem['ref'] ?? $ccItem['referenceNumber'] ?? $ccItem['reference_number'] ?? ''));
if ($ccRef === '') {
continue;
}
$existingRl = $this->db->table('record_links')->where('care_context_reference', $ccRef)->get(1)->getRowArray();
$rlData = [
'abha_id' => (string) ($body['abha_address'] ?? ''),
'care_context_reference' => $ccRef,
'link_status' => 'linked',
'linked_at' => $now,
'updated_at' => $now,
'response_json' => json_encode($result, JSON_UNESCAPED_UNICODE),
];
if (! empty($existingRl)) {
$this->db->table('record_links')->where('id', (int) $existingRl['id'])->update($rlData);
} else {
$rlData['created_at'] = $now;
$this->db->table('record_links')->insert($rlData);
}
}
}
// 3. Mark health_records linked
if ($this->db->tableExists('health_records') && ! empty($body['care_contexts']) && is_array($body['care_contexts'])) {
foreach ($body['care_contexts'] as $ccItem) {
$ccRef = trim((string) ($ccItem['ref'] ?? $ccItem['referenceNumber'] ?? $ccItem['reference_number'] ?? ''));
if ($ccRef === '') {
continue;
}
$this->db->table('health_records')
->where('care_context_reference', $ccRef)
->update([
'push_status' => 'linked',
'linked_at' => $now,
'abha_id' => (string) ($body['abha_address'] ?? ''),
'updated_at' => $now,
]);
}
}
}
return $this->response->setJSON($result);
}
@ -13210,13 +13273,15 @@ class AbdmGateway extends BaseController
public function hipPatientLinks()
{
if (! $this->request->isAJAX()) {
if (ENVIRONMENT !== 'testing' && method_exists($this->request, 'isAJAX') && ! $this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
}
$abhaReq = $this->request->getGet('abha_address') ?? $this->request->getPost('abha_address') ?? ($_GET['abha_address'] ?? ($_REQUEST['abha_address'] ?? ''));
$limitReq = $this->request->getGet('limit') ?? $this->request->getPost('limit') ?? ($_GET['limit'] ?? 20);
$filters = array_filter([
'abha_address' => $this->request->getGet('abha_address') ?? $this->request->getPost('abha_address'),
'limit' => $this->request->getGet('limit') ?? $this->request->getPost('limit'),
'abha_address' => $abhaReq,
'limit' => $limitReq,
]);
if (empty($filters['abha_address'])) {
@ -13224,6 +13289,49 @@ class AbdmGateway extends BaseController
}
$result = $this->connector->hipGetPatientLinks($filters);
// Normalize careContexts array from bridge (which returns data.patient.careContexts or data[0].careContexts)
$contexts = [];
if (! empty($result['data']['patient']['careContexts']) && is_array($result['data']['patient']['careContexts'])) {
$contexts = $result['data']['patient']['careContexts'];
} elseif (! empty($result['data'][0]['careContexts']) && is_array($result['data'][0]['careContexts'])) {
$contexts = $result['data'][0]['careContexts'];
}
// Merge local record_links so HMS always reflects linked status even before bridge async cache updates
if ($this->db->tableExists('record_links')) {
$localRows = $this->db->table('record_links')
->where('abha_id', $filters['abha_address'])
->where('link_status', 'linked')
->get()
->getResultArray();
$seen = [];
foreach ($contexts as $c) {
$r = trim((string) ($c['referenceNumber'] ?? $c['ref'] ?? ''));
if ($r !== '') {
$seen[$r] = true;
}
}
foreach ($localRows as $lr) {
$ref = trim((string) ($lr['care_context_reference'] ?? ''));
if ($ref !== '' && empty($seen[$ref])) {
$contexts[] = [
'referenceNumber' => $ref,
'display' => $ref,
'status' => 'linked',
'linked_at' => $lr['linked_at'] ?? $lr['created_at'] ?? null,
'source' => 'hms_db',
];
$seen[$ref] = true;
}
}
}
$result['care_contexts'] = $contexts;
$result['total_linked'] = count($contexts);
return $this->response->setJSON($result);
}
@ -13235,7 +13343,7 @@ class AbdmGateway extends BaseController
public function hipLinkNotify()
{
if (! $this->request->isAJAX()) {
if (ENVIRONMENT !== 'testing' && method_exists($this->request, 'isAJAX') && ! $this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
}
@ -13260,7 +13368,7 @@ class AbdmGateway extends BaseController
public function hipSmsNotify()
{
if (! $this->request->isAJAX()) {
if (ENVIRONMENT !== 'testing' && method_exists($this->request, 'isAJAX') && ! $this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
}

View File

@ -82,7 +82,7 @@ if ($patientPhotoPath === '') {
$patientYob = (string)((int)date('Y') - $patientAgeVal);
}
?>
<button type="button" class="btn btn-outline-primary btn-sm fw-semibold" onclick="openAbdmHipLinkModal(<?= (int)$patient->id ?>, '<?= esc($patientAbhaAddress ?? '') ?>', { name: '<?= esc($patient->p_fname ?? '') ?>', gender: '<?= $patient->gender == 1 ? 'M' : ($patient->gender == 2 ? 'F' : 'O') ?>', yob: '<?= esc($patientYob) ?>', phone: '<?= esc($patient->mphone1 ?? '') ?>', abha_number: '<?= esc($validModalAbhaNum) ?>' })">
<button type="button" class="btn btn-outline-primary btn-sm fw-semibold" onclick="openAbdmHipLinkModal(<?= (int)$patient->id ?>, '<?= esc($patientAbhaAddress ?? '') ?>', { name: '<?= esc($patient->p_fname ?? '') ?>', gender: '<?= $patient->gender == 1 ? 'M' : ($patient->gender == 2 ? 'F' : 'O') ?>', yob: '<?= esc($patientYob) ?>', phone: '<?= esc($patient->mphone1 ?? '') ?>', abha_number: '<?= esc($validModalAbhaNum) ?>', onLinked: function() { setTimeout(function() { location.reload(); }, 1200); } })">
<i class="bi bi-link-45deg me-1"></i>Link to ABHA (HIP)
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"

View File

@ -119,7 +119,7 @@
$personYob = (string)((int)date('Y') - $personAge);
}
?>
<button type="button" class="btn btn-outline-primary btn-sm w-100 mt-2 fw-semibold" onclick="openAbdmHipLinkModal(<?= (int)$data[0]->id ?>, '<?= esc($abhaAddress) ?>', { name: '<?= esc($data[0]->p_fname ?? '') ?>', gender: '<?= $data[0]->gender == 1 ? 'M' : ($data[0]->gender == 2 ? 'F' : 'O') ?>', yob: '<?= esc($personYob) ?>', phone: '<?= esc($data[0]->mphone1 ?? '') ?>', abha_number: '<?= esc($validAbhaNumForModal) ?>' })">
<button type="button" class="btn btn-outline-primary btn-sm w-100 mt-2 fw-semibold" onclick="openAbdmHipLinkModal(<?= (int)$data[0]->id ?>, '<?= esc($abhaAddress) ?>', { name: '<?= esc($data[0]->p_fname ?? '') ?>', gender: '<?= $data[0]->gender == 1 ? 'M' : ($data[0]->gender == 2 ? 'F' : 'O') ?>', yob: '<?= esc($personYob) ?>', phone: '<?= esc($data[0]->mphone1 ?? '') ?>', abha_number: '<?= esc($validAbhaNumForModal) ?>', onLinked: function() { setTimeout(function() { location.reload(); }, 1200); } })">
<i class="bi bi-link-45deg me-1"></i>Link Records to ABHA (HIP)
</button>
<?php if (empty($abhaAddress) && !empty($data[0]->mphone1)) : ?>
@ -287,9 +287,9 @@
<div class="col-lg-3 col-md-4 label">ABHA Verification</div>
<div class="col-lg-9 col-md-8 d-flex align-items-center gap-2 flex-wrap">
<?php if ($abhaVerifiedStatus !== '') : ?>
<span class="badge bg-success-subtle text-success border border-success-subtle"><?= esc($abhaVerifiedStatus) ?></span>
<span class="badge bg-success-subtle text-success border border-success-subtle" id="abha_verified_status_badge"><?= esc($abhaVerifiedStatus) ?></span>
<?php else : ?>
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle">UNAVAILABLE</span>
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle" id="abha_verified_status_badge">UNAVAILABLE</span>
<?php endif; ?>
<?php if ($abhaVerificationType !== '') : ?>

View File

@ -1054,8 +1054,16 @@
if (linkData.ok) {
alertBox.className = 'alert alert-success py-2 px-3 small';
alertBox.innerHTML = '<strong><i class="bi bi-check-circle-fill me-1"></i>Success!</strong> '
+ 'Care contexts successfully submitted to ABDM for linking. Patient can now discover and access them in their ABHA / PHR app.';
alertBox.innerHTML = '<strong><i class="bi bi-check-circle-fill me-1"></i>Care Contexts Linked Successfully!</strong><br>'
+ 'Care contexts have been linked to patient\'s ABHA (<code>' + abhaAddress + '</code>) and recorded in HMS.<br>'
+ '<i class="bi bi-info-circle me-1 text-primary"></i>Patient can now discover and access their health records in the ABHA / PHR app under <em>Linked Facilities</em>.';
// Update any verification badge on the underlying patient profile page
var pBadge = document.getElementById('abha_verified_status_badge');
if (pBadge) {
pBadge.className = 'badge bg-success-subtle text-success border border-success-subtle';
pBadge.textContent = 'LINKED';
}
syncAndPushTaskFhirRecord(selectedContexts, alertBox, abhaAddress);
@ -1247,12 +1255,20 @@
.then(function(r) { return r.json(); })
.then(function(res) {
if (btn) { btn.disabled = false; btn.innerHTML = origHtml; }
var records = (res.data && res.data[0] && res.data[0].careContexts) ? res.data[0].careContexts : [];
if (records.length === 0) {
alert('No care contexts currently linked to ' + abha + ' on ABDM Bridge.');
var records = res.care_contexts
|| (res.data && res.data.patient && res.data.patient.careContexts)
|| (res.data && res.data[0] && res.data[0].careContexts)
|| (Array.isArray(res.data) ? res.data : []);
if (!records || records.length === 0) {
alert('No care contexts currently linked to ' + abha + ' in ABDM registry or HMS records.');
} else {
var list = records.map(function(c, i) {
return (i + 1) + '. ' + (c.referenceNumber || c.ref) + ' (' + (c.display || 'Record') + ') - Status: ' + (c.status || 'linked');
var ref = c.referenceNumber || c.ref || 'Ref';
var disp = c.display || ref;
var st = (c.status || 'linked').toUpperCase();
var src = c.source ? (' [' + c.source + ']') : '';
return (i + 1) + '. ' + ref + ' (' + disp + ') - ' + st + src;
}).join('\n');
alert('Found ' + records.length + ' care context(s) linked to ' + abha + ':\n\n' + list);
}