From fb2bfb8a8cbc1522573f8814391051c0bcc5a190 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Tue, 18 Aug 2026 03:11:09 +0530 Subject: [PATCH] fix(abdm): stamp consent status on its own request row Reconciled status lived only on a separate reconcile row, whose correlation back to a session was rebuilt on every page load, so denied/expired requests reverted to REQUESTED after reopening. Write GRANTED/DENIED/EXPIRED/REVOKED onto the session's own consent_request row and treat a stamped terminal state as authoritative. --- app/Controllers/Patient.php | 94 ++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 2acd04f..df9c4ac 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -2697,7 +2697,7 @@ class Patient extends BaseController // older requests the patient later denied/expired in the PHR app keep // showing REQUESTED otherwise, because nothing ever re-checks them. $sessions = $this->getAbdmConsentRequestsList($abhaAddress); - $reconcilePayloads = []; + $reconcileJobs = []; foreach ($sessions['requests'] ?? [] as $consent) { if (in_array(strtoupper(trim((string) ($consent['status'] ?? ''))), ['EXPIRED', 'DENIED', 'REVOKED'], true)) { continue; @@ -2708,16 +2708,19 @@ class Patient extends BaseController if ($sessionRequestId === '' && $sessionConsentRequestId === '' && $sessionConsentId === '') { continue; } - $reconcilePayloads[] = [ - 'abha_address' => $abhaAddress, - 'request_id' => $sessionRequestId, - 'abdm_consent_request_id' => $sessionConsentRequestId, - 'abdm_consent_artifact_id' => $sessionConsentId, - 'consent_id' => $sessionConsentId, + $reconcileJobs[] = [ + 'anchor_row_id' => (int) ($consent['id'] ?? 0), + 'payload' => [ + 'abha_address' => $abhaAddress, + 'request_id' => $sessionRequestId, + 'abdm_consent_request_id' => $sessionConsentRequestId, + 'abdm_consent_artifact_id' => $sessionConsentId, + 'consent_id' => $sessionConsentId, + ], ]; } - if ($reconcilePayloads === []) { + if ($reconcileJobs === []) { $snapshot = $this->getLatestAbdmSyncSnapshot($abhaAddress); $requestId = trim((string) ($snapshot['request_id'] ?? '')); $consentRequestId = trim((string) ($snapshot['consent_request_id'] ?? '')); @@ -2728,12 +2731,15 @@ class Patient extends BaseController 'error' => 'No consent request found for this patient to check.', ]); } - $reconcilePayloads[] = [ - 'abha_address' => $abhaAddress, - 'request_id' => $requestId, - 'abdm_consent_request_id' => $consentRequestId, - 'abdm_consent_artifact_id' => $consentId, - 'consent_id' => $consentId, + $reconcileJobs[] = [ + 'anchor_row_id' => 0, + 'payload' => [ + 'abha_address' => $abhaAddress, + 'request_id' => $requestId, + 'abdm_consent_request_id' => $consentRequestId, + 'abdm_consent_artifact_id' => $consentId, + 'consent_id' => $consentId, + ], ]; } @@ -2745,7 +2751,8 @@ class Patient extends BaseController $dataUpdates = 0; $dataFailed = 0; - foreach ($reconcilePayloads as $reconcilePayload) { + foreach ($reconcileJobs as $job) { + $reconcilePayload = $job['payload']; try { $reconcile = $service->runOperation('consent_reconcile', $reconcilePayload); } catch (\Throwable $e) { @@ -2758,6 +2765,7 @@ class Patient extends BaseController continue; } $reconcileOk = true; + $this->persistConsentPhaseOnRequestRow((int) $job['anchor_row_id'], $reconcile); $fetchSummary = ['granted' => false, 'data_updates' => 0, 'failed' => 0, 'artifact_ids' => []]; try { @@ -2782,7 +2790,7 @@ class Patient extends BaseController return $this->response->setJSON([ 'ok' => 1, - 'sessions_checked' => count($reconcilePayloads), + 'sessions_checked' => count($reconcileJobs), 'reconcile_ok' => $reconcileOk ? 1 : 0, 'reconcile_error' => $reconcileOk ? '' : $reconcileError, 'granted' => $granted ? 1 : 0, @@ -2792,6 +2800,52 @@ class Patient extends BaseController ] + $this->getAbdmConsentRequestsList($abhaAddress)); } + /** + * Stamps a reconciled consent status onto the session's own CONSENT_REQUEST + * row. Without this the status lives only on a separate reconcile row, whose + * attribution back to a session depends on id correlation — so a reload could + * show REQUESTED again for a request ABDM already reported as denied/expired. + * + * @param array $reconcile consent_reconcile operation result + */ + private function persistConsentPhaseOnRequestRow(int $rowId, array $reconcile): void + { + if ($rowId <= 0) { + return; + } + + $status = strtoupper(trim((string) ( + $reconcile['consent']['status'] + ?? $reconcile['consent_status'] + ?? $reconcile['status'] + ?? '' + ))); + $state = match ($status) { + 'GRANTED', 'APPROVED', 'ACTIVE' => 'GRANTED', + 'DENIED' => 'DENIED', + 'EXPIRED' => 'EXPIRED', + 'REVOKED' => 'REVOKED', + default => '', + }; + if ($state === '') { + return; + } + + $now = date('Y-m-d H:i:s'); + $update = ['workflow_state' => $state, 'updated_at' => $now]; + if ($state === 'EXPIRED') { + $update['expired_at'] = $now; + } elseif ($state === 'REVOKED' || $state === 'DENIED') { + $update['revoked_at'] = $now; + } + + try { + $this->db->table('abdm_hiu_workflows')->where('id', $rowId)->update($update); + } catch (\Throwable $e) { + log_message('error', 'Unable to persist ABDM consent status on row ' . $rowId . ': ' . $e->getMessage()); + } + } + public function abdm_timeline(int $pno) { if (! $this->db->tableExists('patient_master')) { @@ -4789,6 +4843,14 @@ class Patient extends BaseController } } + // A status stamped directly onto this request row by a live status check + // is authoritative: it names this exact consent request, unlike separate + // reconcile rows that have to be correlated back to a session. + $anchorState = strtoupper(trim((string) (is_array($consentRequestRow) ? ($consentRequestRow['workflow_state'] ?? '') : ''))); + if (in_array($anchorState, ['EXPIRED', 'DENIED', 'REVOKED'], true)) { + $phase = $anchorState; + } + // A short, human-friendly identifier for this session (the "Request ID" // guid is too long to display in a table column) -- use the underlying // abdm_hiu_workflows row id of this session's own CONSENT_REQUEST row