fix(abdm): partition care context discovery and linking by ABHA address

This commit is contained in:
DevSoftTech Server 2026-10-23 00:42:39 +05:30
parent 5219b8bfc6
commit e4bb4e3464
2 changed files with 187 additions and 15 deletions

View File

@ -190,7 +190,7 @@ class AbdmGateway extends BaseController
} }
} }
[$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientDisplay, '', '', 0, '', true); [$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientDisplay, '', '', 0, '', true, $discoveredAbha);
$this->getAuditService()->log([ $this->getAuditService()->log([
'action' => 'discovery_records', 'action' => 'discovery_records',
@ -5494,7 +5494,7 @@ class AbdmGateway extends BaseController
} }
} }
[$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientDisplay, '', '', 0, '', true); [$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientDisplay, '', '', 0, '', true, $discoveredAbha);
$this->getAuditService()->log([ $this->getAuditService()->log([
'action' => 'discovery_records', 'action' => 'discovery_records',
@ -7589,7 +7589,8 @@ class AbdmGateway extends BaseController
string $entityId = '', string $entityId = '',
int $taskId = 0, int $taskId = 0,
string $source = '', string $source = '',
bool $readyOnly = false bool $readyOnly = false,
string $filterAbhaAddress = ''
): array { ): array {
$this->db = $this->db ?? \Config\Database::connect(); $this->db = $this->db ?? \Config\Database::connect();
$careContextsV3 = []; $careContextsV3 = [];
@ -7597,7 +7598,85 @@ class AbdmGateway extends BaseController
$seenRefs = []; $seenRefs = [];
$seenOpdSessions = []; $seenOpdSessions = [];
$addContext = function (array $item) use (&$careContextsV3, &$careContextsFull, &$seenRefs, &$seenOpdSessions, $readyOnly) { // ABHA Address Partitioning:
// When discovering or linking for a specific ABHA address, care contexts linked to OTHER
// (older/different) ABHA addresses must be excluded, so old data remains with the old ABHA address
// and only records relevant to the active ABHA address (or unlinked records) are returned.
$targetAbha = strtolower(trim($filterAbhaAddress));
if ($targetAbha === '' && $patientId > 0 && $this->db->tableExists('patient_master')) {
$pmRow = $this->db->table('patient_master')->select('abha_address')->where('id', $patientId)->get(1)->getRowArray();
if (! empty($pmRow['abha_address'])) {
$targetAbha = strtolower(trim((string) $pmRow['abha_address']));
}
}
$otherLinkedRefs = [];
$thisAbhaLinkedRefs = [];
if ($targetAbha !== '') {
$allowedAbhaSet = [$targetAbha => true];
// 1. Check record_links
if ($this->db->tableExists('record_links')) {
$rlRows = $this->db->table('record_links')
->select('care_context_reference, abha_id, link_status')
->where('link_status', 'linked')
->where('care_context_reference IS NOT NULL')
->where('care_context_reference !=', '')
->get()
->getResultArray();
foreach ($rlRows as $rl) {
$ref = trim((string) ($rl['care_context_reference'] ?? ''));
$rAbha = strtolower(trim((string) ($rl['abha_id'] ?? '')));
if ($ref === '' || $rAbha === '') {
continue;
}
if (isset($allowedAbhaSet[$rAbha])) {
$thisAbhaLinkedRefs[$ref] = true;
unset($otherLinkedRefs[$ref]);
} elseif (! isset($thisAbhaLinkedRefs[$ref])) {
$otherLinkedRefs[$ref] = $rAbha;
}
}
}
// 2. Check health_records
if ($this->db->tableExists('health_records')) {
$hrRows = $this->db->table('health_records')
->select('care_context_reference, abha_id, push_status')
->where('patient_id', $patientId)
->whereIn('push_status', ['linked', 'pushed'])
->where('care_context_reference IS NOT NULL')
->where('care_context_reference !=', '')
->get()
->getResultArray();
foreach ($hrRows as $hr) {
$ref = trim((string) ($hr['care_context_reference'] ?? ''));
$hAbha = strtolower(trim((string) ($hr['abha_id'] ?? '')));
if ($ref === '' || $hAbha === '') {
continue;
}
if (isset($allowedAbhaSet[$hAbha])) {
$thisAbhaLinkedRefs[$ref] = true;
unset($otherLinkedRefs[$ref]);
} elseif (! isset($thisAbhaLinkedRefs[$ref])) {
$otherLinkedRefs[$ref] = $hAbha;
}
}
}
}
$addContext = function (array $item) use (
&$careContextsV3,
&$careContextsFull,
&$seenRefs,
&$seenOpdSessions,
$readyOnly,
$targetAbha,
&$otherLinkedRefs,
&$thisAbhaLinkedRefs
) {
$ref = trim((string) ($item['referenceNumber'] ?? $item['careContextId'] ?? '')); $ref = trim((string) ($item['referenceNumber'] ?? $item['careContextId'] ?? ''));
if ($ref === '' || isset($seenRefs[$ref])) { if ($ref === '' || isset($seenRefs[$ref])) {
return; return;
@ -7605,6 +7684,20 @@ class AbdmGateway extends BaseController
if ($readyOnly && empty($item['is_fhir_ready'])) { if ($readyOnly && empty($item['is_fhir_ready'])) {
return; return;
} }
// ABHA Address Partitioning filter:
// If this care context is linked to ANOTHER ABHA address and NOT linked to this target ABHA address, skip it.
if ($targetAbha !== '') {
$alias = trim((string) ($item['alias_reference'] ?? ''));
$isLinkedToThis = isset($thisAbhaLinkedRefs[$ref]) || ($alias !== '' && isset($thisAbhaLinkedRefs[$alias]));
$isLinkedToOther = (isset($otherLinkedRefs[$ref]) && ! isset($thisAbhaLinkedRefs[$ref]))
|| ($alias !== '' && isset($otherLinkedRefs[$alias]) && ! isset($thisAbhaLinkedRefs[$alias]));
if ($isLinkedToOther && ! $isLinkedToThis) {
return;
}
}
$seenRefs[$ref] = true; $seenRefs[$ref] = true;
if (! empty($item['alias_reference'])) { if (! empty($item['alias_reference'])) {
$seenRefs[trim((string) $item['alias_reference'])] = true; $seenRefs[trim((string) $item['alias_reference'])] = true;
@ -14120,7 +14213,7 @@ class AbdmGateway extends BaseController
$rawPhone = trim((string) ($patient['mphone1'] ?? $patient['mphone2'] ?? '')); $rawPhone = trim((string) ($patient['mphone1'] ?? $patient['mphone2'] ?? ''));
$cleanPhone = substr(preg_replace('/\D/', '', $rawPhone), -10); $cleanPhone = substr(preg_replace('/\D/', '', $rawPhone), -10);
[$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientName, $taskType, $entityId, $taskId, $source); [$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientName, $taskType, $entityId, $taskId, $source, false, $effAbhaAddress);
// Fetch already-linked care contexts from Bridge and Local DB (record_links & health_records) // Fetch already-linked care contexts from Bridge and Local DB (record_links & health_records)
$linkedRefs = []; $linkedRefs = [];
@ -14148,10 +14241,9 @@ class AbdmGateway extends BaseController
->select('care_context_reference') ->select('care_context_reference')
->where('link_status', 'linked'); ->where('link_status', 'linked');
if ($effAbhaAddress !== '') { if ($effAbhaAddress !== '') {
$rlQuery->groupStart() $rlQuery->where('abha_id', $effAbhaAddress);
->where('abha_id', $effAbhaAddress) } else {
->orWhere('care_context_reference LIKE', '%' . $patientId . '%') $rlQuery->where('care_context_reference LIKE', '%' . $patientId . '%');
->groupEnd();
} }
$rlRows = $rlQuery->get()->getResultArray(); $rlRows = $rlQuery->get()->getResultArray();
foreach ($rlRows as $rl) { foreach ($rlRows as $rl) {
@ -14166,14 +14258,11 @@ class AbdmGateway extends BaseController
if ($this->db->tableExists('health_records') && $patientId > 0) { if ($this->db->tableExists('health_records') && $patientId > 0) {
$hrQuery = $this->db->table('health_records') $hrQuery = $this->db->table('health_records')
->select('care_context_reference') ->select('care_context_reference')
->groupStart() ->where('patient_id', $patientId);
->where('patient_id', $patientId)
->orWhere('care_context_reference LIKE', '%-' . $patientId . '-%');
if ($effAbhaAddress !== '') { if ($effAbhaAddress !== '') {
$hrQuery->orWhere('abha_id', $effAbhaAddress); $hrQuery->where('abha_id', $effAbhaAddress);
} }
$hrRows = $hrQuery->groupEnd() $hrRows = $hrQuery->whereIn('push_status', ['linked', 'pushed'])
->whereIn('push_status', ['linked', 'pushed'])
->get() ->get()
->getResultArray(); ->getResultArray();
foreach ($hrRows as $hr) { foreach ($hrRows as $hr) {

View File

@ -129,6 +129,19 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
net_amount REAL net_amount REAL
)"); )");
$db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('record_links') . " (
id INTEGER PRIMARY KEY AUTOINCREMENT,
health_record_id INTEGER,
abdm_txn_id TEXT,
care_context_reference TEXT,
abha_id TEXT,
link_status TEXT,
response_json TEXT,
linked_at TEXT,
created_at TEXT,
updated_at TEXT
)");
$db->resetDataCache(); $db->resetDataCache();
$db->table('charge_master')->emptyTable(); $db->table('charge_master')->emptyTable();
@ -138,6 +151,7 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
$db->table('health_records')->emptyTable(); $db->table('health_records')->emptyTable();
$db->table('patient_master')->emptyTable(); $db->table('patient_master')->emptyTable();
$db->table('ipd_master')->emptyTable(); $db->table('ipd_master')->emptyTable();
$db->table('record_links')->emptyTable();
$db->table('immunization_records')->emptyTable(); $db->table('immunization_records')->emptyTable();
$db->table('invoice_master')->emptyTable(); $db->table('invoice_master')->emptyTable();
@ -611,6 +625,75 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
$this->assertCount(1, $opdConsults); $this->assertCount(1, $opdConsults);
$this->assertTrue($opdConsults[0]['is_fhir_ready'], 'Completed consultation with findings must be marked FHIR ready'); $this->assertTrue($opdConsults[0]['is_fhir_ready'], 'Completed consultation with findings must be marked FHIR ready');
} }
public function testCareContextsPartitioningByAbhaAddress(): void
{
$db = \Config\Database::connect();
// Setup patient 999
$db->table('patient_master')->insert([
'id' => 999,
'p_code' => 'P-999',
'p_fname' => 'PARTITION TEST',
'dob' => '1985-01-01',
'gender' => 1,
'abha_id' => '91999999999999',
'abha_address' => 'newuser@sbx',
'mphone1' => '9999999999',
]);
// Insert 2 prescriptions for patient 999
$db->table('opd_prescription')->insert([
'id' => 701,
'opd_id' => 701,
'p_id' => 999,
'session_id' => 701,
'date_opd_visit' => '2026-08-01',
'complaints' => 'Old visit complaints',
]);
$db->table('opd_prescription')->insert([
'id' => 702,
'opd_id' => 702,
'p_id' => 999,
'session_id' => 702,
'date_opd_visit' => '2026-10-22',
'complaints' => 'New visit complaints',
]);
// Link old prescription #701 to 'olduser@sbx'
$oldRef = 'OPD-999-S701-20260801';
$db->table('record_links')->insert([
'care_context_reference' => $oldRef,
'abha_id' => 'olduser@sbx',
'link_status' => 'linked',
'created_at' => '2026-08-01 10:00:00',
]);
// Link new prescription #702 to 'newuser@sbx'
$newRef = 'OPD-999-S702-20261022';
$db->table('record_links')->insert([
'care_context_reference' => $newRef,
'abha_id' => 'newuser@sbx',
'link_status' => 'linked',
'created_at' => '2026-10-22 10:00:00',
]);
$refGateway = new ReflectionClass($this->gateway);
$method = $refGateway->getMethod('findCareContextsForPatient');
$method->setAccessible(true);
// Discovery for 'newuser@sbx': MUST contain $newRef and MUST NOT contain $oldRef
[$v3New, $fullNew] = $method->invoke($this->gateway, 999, 'P-999', 'PARTITION TEST', '', '', 0, '', false, 'newuser@sbx');
$newRefs = array_column($v3New, 'referenceNumber');
$this->assertContains($newRef, $newRefs, 'New ABHA address should discover its own linked record');
$this->assertNotContains($oldRef, $newRefs, 'New ABHA address MUST NOT discover records linked to olduser@sbx');
// Discovery for 'olduser@sbx': MUST contain $oldRef and MUST NOT contain $newRef
[$v3Old, $fullOld] = $method->invoke($this->gateway, 999, 'P-999', 'PARTITION TEST', '', '', 0, '', false, 'olduser@sbx');
$oldRefs = array_column($v3Old, 'referenceNumber');
$this->assertContains($oldRef, $oldRefs, 'Old ABHA address should discover its own linked record');
$this->assertNotContains($newRef, $oldRefs, 'Old ABHA address MUST NOT discover records linked to newuser@sbx');
}
} }