diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 11cf13b..095262f 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -502,6 +502,7 @@ $routes->group('billing', function($routes) { $routes->get('patient/abdm_content_auto_flow/(:num)', 'Patient::abdm_content_auto_flow/$1'); $routes->get('patient/abdm_content_fetch_only/(:num)', 'Patient::abdm_content_fetch_only/$1'); $routes->get('patient/abdm_consent_detail/(:num)', 'Patient::abdm_consent_detail/$1'); +$routes->get('patient/abdm_consent_requests/(:num)', 'Patient::abdm_consent_requests/$1'); $routes->post('patient/abdm_content_request_custom/(:num)', 'Patient::abdm_content_request_custom/$1'); $routes->get('patient/abdm_timeline/(:num)', 'Patient::abdm_timeline/$1'); $routes->post('patient/save_profile_image/(:num)', 'Patient::save_profile_image/$1'); diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 48b1acf..38bb841 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -1956,23 +1956,44 @@ class Patient extends BaseController ]); } - $lastSync = $this->getLatestAbdmSyncSnapshot((string) ($abhaContext['abha_address'] ?? '')); - $phase = strtoupper(trim((string) ($lastSync['phase'] ?? ''))); - if (! in_array($phase, ['GRANTED', 'COMPLETED'], true)) { - return $this->response->setStatusCode(422)->setJSON([ - 'ok' => 0, - 'phase' => $phase !== '' ? $phase : 'IDLE', - 'error' => 'Consent is not granted yet. Use "Fetch ABDM Records" to send/track a consent request first.', - ]); + // Optional overrides let the "Fetch Records" action in the Consent Request + // History list target a SPECIFIC past session's consent reference, instead + // of always re-pulling using the globally-latest granted consent. + $overrideConsentId = trim((string) ( + $this->request->getGet('consent_id') + ?? $this->request->getPost('consent_id') + ?? '' + )); + $overrideConsentRequestId = trim((string) ( + $this->request->getGet('consent_request_id') + ?? $this->request->getPost('consent_request_id') + ?? '' + )); + + if ($overrideConsentId !== '' || $overrideConsentRequestId !== '') { + $phase = 'GRANTED'; + $consentArtifactRef = $overrideConsentId; + $consentRequestRef = $overrideConsentRequestId; + } else { + $lastSync = $this->getLatestAbdmSyncSnapshot((string) ($abhaContext['abha_address'] ?? '')); + $phase = strtoupper(trim((string) ($lastSync['phase'] ?? ''))); + if (! in_array($phase, ['GRANTED', 'COMPLETED'], true)) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'phase' => $phase !== '' ? $phase : 'IDLE', + 'error' => 'Consent is not granted yet. Use "+ New Request" to send/track a consent request first.', + ]); + } + + $consentRequestRef = trim((string) ($lastSync['consent_request_id'] ?? '')); + $consentArtifactRef = trim((string) ($lastSync['consent_id'] ?? '')); } - $consentRequestRef = trim((string) ($lastSync['consent_request_id'] ?? '')); - $consentArtifactRef = trim((string) ($lastSync['consent_id'] ?? '')); if ($consentArtifactRef === '' && $consentRequestRef === '') { return $this->response->setStatusCode(422)->setJSON([ 'ok' => 0, 'phase' => $phase, - 'error' => 'No saved consent reference found. Use "Fetch ABDM Records" to re-establish consent.', + 'error' => 'No saved consent reference found. Use "+ New Request" to re-establish consent.', ]); } @@ -2059,6 +2080,40 @@ class Patient extends BaseController return $this->response->setJSON($detail); } + /** + * Full history of this patient's ABDM consent requests (one entry per distinct + * consent request "session"), most recent first — used by the "Consent Request + * History" table on the OPD ABDM tab. + */ + public function abdm_consent_requests(int $pno) + { + if (! $this->db->tableExists('patient_master')) { + return $this->response->setStatusCode(500)->setJSON([ + 'ok' => 0, + 'error' => 'patient_master table not found', + ]); + } + + $patientRow = $this->db->table('patient_master')->where('id', $pno)->get()->getRowArray(); + if (! is_array($patientRow)) { + return $this->response->setStatusCode(404)->setJSON([ + 'ok' => 0, + 'error' => 'Patient not found', + ]); + } + + $abhaContext = $this->buildAbhaProfileContext($patientRow); + $abhaAddress = trim((string) ($abhaContext['abha_address'] ?? '')); + if ($abhaAddress === '') { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'ABHA Address not available for this patient.', + ]); + } + + return $this->response->setJSON($this->getAbdmConsentRequestsList($abhaAddress)); + } + public function abdm_timeline(int $pno) { if (! $this->db->tableExists('patient_master')) { @@ -3594,15 +3649,65 @@ class Patient extends BaseController return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.']; } + $rows = $this->fetchAbdmWorkflowRows($abhaAddress); + if ($rows === []) { + return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.']; + } + + $sessions = $this->groupWorkflowRowsIntoSessions($rows); + if ($sessions === []) { + return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.']; + } + + // Sessions are chronological (oldest first); the last one is the most recent. + return $this->computeConsentSessionDetail(end($sessions), $abhaAddress); + } + + /** + * Builds one detail entry (same shape as getAbdmConsentArtifactDetail) per + * distinct consent request the patient has had over time, for the "Consent + * Request History" list — most recent first. + */ + private function getAbdmConsentRequestsList(string $abhaAddress): array + { + $abhaAddress = trim($abhaAddress); + if ($abhaAddress === '' || ! $this->db->tableExists('abdm_hiu_workflows')) { + return ['ok' => 1, 'requests' => []]; + } + + $rows = $this->fetchAbdmWorkflowRows($abhaAddress); + if ($rows === []) { + return ['ok' => 1, 'requests' => []]; + } + + $sessions = $this->groupWorkflowRowsIntoSessions($rows); + $requests = []; + foreach ($sessions as $sessionRows) { + $detail = $this->computeConsentSessionDetail($sessionRows, $abhaAddress); + if ((int) ($detail['ok'] ?? 0) === 1) { + $requests[] = $detail['consent']; + } + } + + return ['ok' => 1, 'requests' => array_reverse($requests)]; + } + + /** + * Fetches the raw consent-related workflow rows for a patient's ABHA address, + * most recent first. Shared by getAbdmConsentArtifactDetail() and + * getAbdmConsentRequestsList(). + */ + private function fetchAbdmWorkflowRows(string $abhaAddress): array + { $fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? []; - $select = ['id', 'operation', 'workflow_state', 'status', 'request_id', 'consent_id', 'created_at', 'updated_at', 'completed_at', 'expired_at', 'revoked_at', 'last_error', 'http_code', 'request_json', 'response_json']; + $select = ['id', 'operation', 'workflow_state', 'status', 'request_id', 'consent_id', 'hfr_id', 'created_at', 'updated_at', 'completed_at', 'expired_at', 'revoked_at', 'last_error', 'http_code', 'request_json', 'response_json']; foreach (['abdm_consent_request_id', 'abdm_consent_artifact_id', 'gateway_request_id'] as $optionalField) { if (in_array($optionalField, $fields, true)) { $select[] = $optionalField; } } - $rows = $this->db->table('abdm_hiu_workflows') + return $this->db->table('abdm_hiu_workflows') ->select(implode(', ', $select)) ->where('abha_address', $abhaAddress) ->whereIn('operation', [ @@ -3615,9 +3720,51 @@ class Patient extends BaseController 'hi_data_push_callback', ]) ->orderBy('id', 'DESC') - ->get(60) + ->get(200) ->getResultArray(); + } + /** + * Splits a DESC-by-id set of workflow rows into distinct consent request + * "sessions" — each CONSENT_REQUEST row starts a new session, and every + * subsequent row (status/reconcile/data_fetch/callback) belongs to that + * session until the next CONSENT_REQUEST row appears. Returns sessions in + * chronological order (oldest session first); rows within CONSENT_REQUEST + * are always the first row of their session. + * + * @param array> $rows rows ordered DESC by id + * @return array>> + */ + private function groupWorkflowRowsIntoSessions(array $rows): array + { + $chronological = array_reverse($rows); + + $sessions = []; + $current = []; + foreach ($chronological as $row) { + $operation = strtoupper(trim((string) ($row['operation'] ?? ''))); + if ($operation === 'CONSENT_REQUEST' && $current !== []) { + $sessions[] = $current; + $current = []; + } + $current[] = $row; + } + if ($current !== []) { + $sessions[] = $current; + } + + return $sessions; + } + + /** + * Computes the requested-vs-granted Health Information Type breakdown for a + * single consent request "session" (one CONSENT_REQUEST row plus its + * subsequent status/reconcile/data_fetch/callback rows). + * + * @param array> $rows the session's rows (any order) + */ + private function computeConsentSessionDetail(array $rows, string $abhaAddress): array + { if ($rows === []) { return ['ok' => 0, 'error' => 'No ABDM consent activity found for this patient.']; } @@ -3699,32 +3846,15 @@ class Patient extends BaseController $phase = (string) $best['_phase']; $consentId = trim((string) ($best['abdm_consent_artifact_id'] ?? $best['consent_id'] ?? $bestDecoded['consent_id'] ?? $bestDecoded['consentId'] ?? '')); $consentRequestId = trim((string) ($best['abdm_consent_request_id'] ?? $bestDecoded['abdm_consent_request_id'] ?? $bestDecoded['consent_request_id'] ?? $bestDecoded['consentRequestId'] ?? '')); - $bestRequestId = trim((string) ($best['request_id'] ?? '')); - // Find the original consent_request row for this same chain, to get the - // requested hi_types list + the original requested-on timestamp. + // The session should start with its own CONSENT_REQUEST row. $consentRequestRow = null; foreach ($rows as $row) { - if (strtoupper((string) ($row['operation'] ?? '')) !== 'CONSENT_REQUEST') { - continue; - } - $rowConsentReqId = trim((string) ($row['abdm_consent_request_id'] ?? '')); - $rowRequestId = trim((string) ($row['request_id'] ?? '')); - if (($consentRequestId !== '' && $rowConsentReqId === $consentRequestId) - || ($bestRequestId !== '' && $rowRequestId === $bestRequestId) - ) { + if (strtoupper((string) ($row['operation'] ?? '')) === 'CONSENT_REQUEST') { $consentRequestRow = $row; break; } } - if ($consentRequestRow === null) { - foreach ($rows as $row) { - if (strtoupper((string) ($row['operation'] ?? '')) === 'CONSENT_REQUEST') { - $consentRequestRow = $row; - break; - } - } - } $requestedHiTypes = []; $requestedOn = ''; @@ -3732,6 +3862,8 @@ class Patient extends BaseController $validFrom = ''; $validTo = ''; $eraseAt = ''; + $requestedBy = ''; + $hfrId = trim((string) ($best['hfr_id'] ?? '')); if (is_array($consentRequestRow)) { $reqPayload = json_decode((string) ($consentRequestRow['request_json'] ?? ''), true); @@ -3745,6 +3877,10 @@ class Patient extends BaseController $validFrom = trim((string) ($consentBlock['permission']['dateRange']['from'] ?? '')); $validTo = trim((string) ($consentBlock['permission']['dateRange']['to'] ?? '')); $eraseAt = trim((string) ($consentBlock['permission']['dataEraseAt'] ?? '')); + $requestedBy = trim((string) ($consentBlock['requester']['name'] ?? '')); + if ($hfrId === '') { + $hfrId = trim((string) ($consentRequestRow['hfr_id'] ?? '')); + } } // Bridge flattens consent.hi_types -> top-level hi_types on consent_status / @@ -3752,7 +3888,8 @@ class Patient extends BaseController // flatten logic). The "best" row for overall phase can be a data_fetch / // hi_data_push_callback row (once records are COMPLETED) whose response is // a FHIR data bundle with no hi_types field at all — so look separately for - // the most recent row that actually carries the granted hi_types payload. + // the most recent row (within this session) that actually carries the + // granted hi_types payload. $grantedHiTypes = []; $grantedOn = ''; foreach ($rows as $row) { @@ -3824,6 +3961,8 @@ class Patient extends BaseController 'granted_on' => $grantedOn, 'revoked_on' => $revokedOn, 'expired_on' => $expiredOn, + 'hfr_id' => $hfrId, + 'requested_by' => $requestedBy !== '' ? $requestedBy : 'HMS', 'items' => $items, ], ]; diff --git a/app/Views/billing/Patient_Profile_Opd_V.php b/app/Views/billing/Patient_Profile_Opd_V.php index 2cece43..9aeda33 100644 --- a/app/Views/billing/Patient_Profile_Opd_V.php +++ b/app/Views/billing/Patient_Profile_Opd_V.php @@ -312,16 +312,8 @@ if ($patientPhotoPath === '') {
- - -
@@ -335,24 +327,38 @@ if ($patientPhotoPath === '') {
- One click sends a consent request to the patient's ABHA (PHR) app. Once the patient approves it there, HMS automatically fetches their health records — no need to click again. + Send a consent request to the patient's ABHA (PHR) app. Once the patient approves it there, HMS automatically fetches their health records — no need to click again.
-
-
- 1. Consent Requested - 2. Consent Granted - 3. Records Fetched -
-
-
-
-
- Unknown - Click "Fetch ABDM Records" to request and load the patient's ABDM health records. - -
+
+ +
+
+
+ +
+
+
+
Consent Request History
+ +
+
+ + + + + + + + + + + + + + +
StatusHI TypesRequested ByRequested OnExpiryActions
No consent requests yet.
@@ -533,65 +539,28 @@ $(function() { var abdmAutoFlowUrl = 'id ?? 0)) ?>'; var abdmFetchOnlyUrl = 'id ?? 0)) ?>'; var abdmConsentDetailUrl = 'id ?? 0)) ?>'; + var abdmConsentRequestsUrl = 'id ?? 0)) ?>'; var abdmCustomRequestUrl = 'id ?? 0)) ?>'; var abdmDocDetailBaseUrl = 'id ?? 0)) ?>'; var currentAbdmRows = []; + var currentAbdmRequests = []; var autoFlowTimer = null; var autoFlowRequestId = ''; var autoFlowAttempts = 0; var autoFlowMaxAttempts = 15; - var autoFlowNeedsFreshRequest = false; var autoFlowRunning = false; - var fetchOnlyRunning = false; + var fetchOnlyRunningIdx = -1; - function updateConsentBadge(phase, needsFresh) { - var text = (phase || '').toString().toUpperCase(); - var $badge = $('#abdmLastConsentBadge'); - if (!$badge.length) { - return; + function consentStatusBadgeClass(status) { + switch ((status || '').toString().toUpperCase()) { + case 'GRANTED': return 'bg-info-subtle text-info border border-info-subtle'; + case 'COMPLETED': return 'bg-success-subtle text-success border border-success-subtle'; + case 'REVOKED': return 'bg-secondary-subtle text-secondary border border-secondary-subtle'; + case 'EXPIRED': return 'bg-warning-subtle text-warning border border-warning-subtle'; + case 'DENIED': return 'bg-danger-subtle text-danger border border-danger-subtle'; + case 'FAILED': return 'bg-danger-subtle text-danger border border-danger-subtle'; + default: return 'bg-warning-subtle text-warning border border-warning-subtle'; } - - $badge.removeClass('bg-secondary bg-success bg-info bg-warning bg-danger'); - if (needsFresh) { - $badge.addClass('bg-danger').text('Failed - New Required'); - return; - } - - if (text === 'COMPLETED') { - $badge.addClass('bg-success').text('Completed'); - } else if (text === 'GRANTED') { - $badge.addClass('bg-info').text('Granted'); - } else if (text === 'REQUESTED' || text === 'PENDING') { - $badge.addClass('bg-warning').text('Pending'); - } else if (text === 'FAILED' || text === 'DENIED') { - $badge.addClass('bg-danger').text('Failed'); - } else { - $badge.addClass('bg-secondary').text('Unknown'); - } - } - - function applyConsentButtonState() { - $('#btnAutoAbdmFlow').prop('disabled', autoFlowRunning || fetchOnlyRunning); - $('#btnAutoAbdmFlow .abdm-btn-spinner').toggleClass('d-none', !autoFlowRunning); - $('#btnAutoAbdmFlowLabel').text(autoFlowRunning ? 'Fetching...' : 'Fetch ABDM Records'); - } - - function applyFetchOnlyButtonState() { - $('#btnFetchRecordsOnly').prop('disabled', autoFlowRunning || fetchOnlyRunning); - $('#btnFetchRecordsOnly .abdm-btn-spinner').toggleClass('d-none', !fetchOnlyRunning); - $('#btnFetchRecordsOnlyLabel').text(fetchOnlyRunning ? 'Fetching...' : 'Fetch Records'); - } - - // Only show the standalone "Fetch Records" action once consent has actually - // been granted (or already completed once) — it re-pulls data using the - // saved consent reference without creating a brand-new consent request. - function showFetchRecordsButton(show) { - $('#btnFetchRecordsOnly').toggleClass('d-none', !show); - } - - // "View Consent" is shown once any consent activity has started (not just IDLE). - function showViewConsentLink(show) { - $('#btnViewAbdmConsent').toggleClass('d-none', !show); } function consentItemBadgeClass(status) { @@ -671,18 +640,104 @@ $(function() { return stepperHtml + metaHtml + tableHtml; } - function loadAbdmConsentDetail() { - $('#abdmConsentDetailBody').html('
Loading...
'); - fetch(abdmConsentDetailUrl, { credentials: 'same-origin' }) + function setAbdmStatus(text, isError) { + var t = (text || '').toString().trim(); + if (t === '') { + $('#abdmStatusBoxWrap').addClass('d-none'); + $('#abdmStatusBox').text(''); + return; + } + $('#abdmStatusBoxWrap').removeClass('d-none'); + $('#abdmStatusBox').toggleClass('text-danger', !!isError).toggleClass('text-muted', !isError).text(t); + } + + function applyNewRequestButtonState() { + $('#btnNewAbdmRequest').prop('disabled', autoFlowRunning || fetchOnlyRunningIdx !== -1); + } + + // Renders one request's detail (from already-loaded list data, no extra + // network call) into the shared Consent Details modal. + function showAbdmConsentDetailForIndex(idx) { + var consent = currentAbdmRequests[idx]; + $('#abdmConsentDetailBody').html(renderAbdmConsentDetail(consent)); + var bsModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('abdmConsentDetailModal')); + bsModal.show(); + } + + function renderAbdmRequestsTable(requests) { + if (!requests || !requests.length) { + $('#abdmRequestsTable tbody').html('No consent requests yet.'); + return; + } + + var html = ''; + requests.forEach(function(consent, idx) { + var status = (consent.status || '').toString().toUpperCase(); + var hiTypes = (consent.requested_hi_types && consent.requested_hi_types.length) ? consent.requested_hi_types : (consent.granted_hi_types || []); + var hiTypesText = hiTypes.length ? hiTypes.join(', ') : '-'; + var canFetch = (status === 'GRANTED' || status === 'COMPLETED'); + + html += '' + + '' + escHtml(status || 'UNKNOWN') + '' + + '' + escHtml(hiTypesText) + '' + + '' + escHtml(consent.requested_by || 'HMS') + '' + + '' + fmtConsentTs(consent.requested_on) + '' + + '' + fmtConsentTs(consent.valid_to) + '' + + '' + + '' + + (canFetch ? ('') : '') + + '' + + ''; + }); + $('#abdmRequestsTable tbody').html(html); + } + + function loadAbdmConsentRequests() { + fetch(abdmConsentRequestsUrl, { credentials: 'same-origin' }) .then(function(resp) { return resp.json(); }) .then(function(data) { if (!data || data.ok !== 1) { - throw new Error((data && data.error) || 'Unable to load consent details.'); + throw new Error((data && data.error) || 'Unable to load consent request history.'); } - $('#abdmConsentDetailBody').html(renderAbdmConsentDetail(data.consent)); + currentAbdmRequests = Array.isArray(data.requests) ? data.requests : []; + renderAbdmRequestsTable(currentAbdmRequests); }) .catch(function(err) { - $('#abdmConsentDetailBody').html('
' + escHtml('Failed to load consent details: ' + (err.message || err)) + '
'); + $('#abdmRequestsTable tbody').html('' + escHtml('Failed to load consent request history: ' + (err.message || err)) + ''); + }); + } + + // Re-pulls data for a specific (already GRANTED/COMPLETED) request row using + // its saved consent reference, without creating a brand-new consent request. + function runFetchRecordsForRow(idx) { + if (autoFlowRunning || fetchOnlyRunningIdx !== -1) { + return; + } + var consent = currentAbdmRequests[idx]; + if (!consent) { + return; + } + fetchOnlyRunningIdx = idx; + applyNewRequestButtonState(); + setAbdmStatus('Fetching latest records using existing granted consent...', false); + + var url = abdmFetchOnlyUrl + '?consent_id=' + encodeURIComponent(consent.consent_id || '') + '&consent_request_id=' + encodeURIComponent(consent.consent_request_id || ''); + fetch(url, { credentials: 'same-origin' }) + .then(function(resp) { return resp.json(); }) + .then(function(data) { + if (!data || data.ok !== 1) { + throw new Error((data && data.error) || 'Fetch failed.'); + } + setAbdmStatus((data.message || 'Records fetched successfully.').toString(), false); + fetchOnlyRunningIdx = -1; + applyNewRequestButtonState(); + loadAbdmDocs(); + loadAbdmConsentRequests(); + }) + .catch(function(err) { + fetchOnlyRunningIdx = -1; + applyNewRequestButtonState(); + setAbdmStatus('Fetch failed: ' + (err.message || err), true); }); } @@ -873,7 +928,6 @@ $(function() { if (q !== '') { url += '&q=' + encodeURIComponent(q); } - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text('Loading ABDM records...'); fetch(url, { credentials: 'same-origin' }) .then(function(resp) { return resp.json(); }) @@ -883,79 +937,12 @@ $(function() { } currentAbdmRows = Array.isArray(data.items) ? data.items : []; renderAbdmRows(currentAbdmRows); - - var lastSync = data.last_sync || null; - if (lastSync && (lastSync.request_id || '').toString().trim() !== '') { - autoFlowRequestId = lastSync.restart_required ? '' : (lastSync.request_id || '').toString().trim(); - autoFlowNeedsFreshRequest = !!lastSync.restart_required; - } - - var lastPhase = lastSync ? (lastSync.phase || '').toString().toUpperCase() : ''; - if (lastPhase === 'COMPLETED') { - setFlowProgress(3); - } else if (lastPhase === 'GRANTED') { - setFlowProgress(2); - } else if (lastPhase === 'REQUESTED' || lastPhase === 'PENDING') { - setFlowProgress(1); - } else if (lastPhase === 'FAILED' || lastPhase === 'DENIED') { - setFlowProgress(0); - } - - showFetchRecordsButton(lastPhase === 'GRANTED' || lastPhase === 'COMPLETED'); - showViewConsentLink(lastPhase !== '' && lastPhase !== 'IDLE'); - updateConsentBadge(lastPhase, autoFlowNeedsFreshRequest); - - var baseMsg = 'Loaded ' + currentAbdmRows.length + ' fetched ABDM record(s).'; - if (lastSync && (lastSync.message || '').toString().trim() !== '') { - baseMsg = (lastSync.message || '').toString(); - if ((lastSync.updated_at || '').toString().trim() !== '') { - baseMsg += ' | Last update: ' + (lastSync.updated_at || '').toString(); - } - if ((lastSync.request_id || '').toString().trim() !== '') { - baseMsg += ' | Request ID: ' + (lastSync.request_id || '').toString(); - } - } - - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text(baseMsg); - applyConsentButtonState(); }) .catch(function(err) { - $('#abdmStatusBox').removeClass('text-muted').addClass('text-danger').text('ABDM load failed: ' + (err.message || err)); + setAbdmStatus('ABDM load failed: ' + (err.message || err), true); }); } - function setFlowProgress(step) { - var current = Number(step || 0); - if (current < 0) { - current = 0; - } - if (current > 3) { - current = 3; - } - - var width = 0; - if (current === 1) { - width = 34; - } else if (current === 2) { - width = 67; - } else if (current >= 3) { - width = 100; - } - - $('#abdmFlowProgressBar').css('width', width + '%'); - - $('#abdmFlowSteps .abdm-flow-step').each(function() { - var $el = $(this); - var stepNo = Number($el.data('step') || 0); - $el.removeClass('is-active is-done'); - if (stepNo < current) { - $el.addClass('is-done'); - } else if (stepNo === current && current > 0) { - $el.addClass('is-active'); - } - }); - } - function stopAutoFlowLoop() { if (autoFlowTimer) { clearTimeout(autoFlowTimer); @@ -982,26 +969,15 @@ $(function() { var resetRequestId = Number(data.reset_request_id || 0) === 1; var info = msg !== '' ? msg : ('Phase: ' + (phase || 'UNKNOWN')); - if (phase === 'COMPLETED') { - setFlowProgress(3); - } else if (phase === 'GRANTED') { - setFlowProgress(2); - } else if (phase === 'REQUESTED' || phase === 'PENDING') { - setFlowProgress(1); - } - showFetchRecordsButton(phase === 'GRANTED' || phase === 'COMPLETED'); - showViewConsentLink(phase !== '' && phase !== 'IDLE'); - updateConsentBadge(phase, autoFlowNeedsFreshRequest); - - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text(info + (autoFlowRequestId ? (' | Request ID: ' + autoFlowRequestId) : '')); + setAbdmStatus(info + (autoFlowRequestId ? (' | Request ID: ' + autoFlowRequestId) : ''), false); if (resetRequestId) { autoFlowRequestId = ''; stopAutoFlowLoop(); - updateConsentBadge('FAILED', false); autoFlowRunning = false; - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text('Previous request is still being processed. Please wait for the first request to finish before starting a new one.'); + applyNewRequestButtonState(); + setAbdmStatus('Previous request is still being processed. Please wait for the first request to finish before starting a new one.', false); + loadAbdmConsentRequests(); return; } @@ -1013,7 +989,8 @@ $(function() { } autoFlowRunning = false; - applyConsentButtonState(); + applyNewRequestButtonState(); + loadAbdmConsentRequests(); if (phase === 'COMPLETED') { loadAbdmDocs(); } @@ -1021,8 +998,9 @@ $(function() { .catch(function(err) { stopAutoFlowLoop(); autoFlowRunning = false; - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-muted').addClass('text-danger').text('Auto flow failed: ' + (err.message || err)); + applyNewRequestButtonState(); + setAbdmStatus('Auto flow failed: ' + (err.message || err), true); + loadAbdmConsentRequests(); }); } @@ -1043,51 +1021,14 @@ $(function() { }); } - // Standalone fetch: consent is already GRANTED, so this skips consent_request - // and reconcile polling entirely and just pulls data using the saved consent - // reference. Used by the "Fetch Records" button once consent is granted. - function runFetchOnlyStep() { - if (fetchOnlyRunning || autoFlowRunning) { - return; - } - fetchOnlyRunning = true; - applyFetchOnlyButtonState(); - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text('Fetching latest records using existing granted consent...'); - - fetch(abdmFetchOnlyUrl, { credentials: 'same-origin' }) - .then(function(resp) { return resp.json().then(function(data) { return { status: resp.status, data: data }; }); }) - .then(function(wrapped) { - var data = wrapped.data; - if (!data || data.ok !== 1) { - throw new Error((data && data.error) || 'Fetch failed.'); - } - - setFlowProgress(3); - showFetchRecordsButton(true); - showViewConsentLink(true); - updateConsentBadge('COMPLETED', false); - - var info = (data.message || 'Records fetched successfully.').toString(); - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text(info); - - fetchOnlyRunning = false; - applyFetchOnlyButtonState(); - applyConsentButtonState(); - loadAbdmDocs(); - }) - .catch(function(err) { - fetchOnlyRunning = false; - applyFetchOnlyButtonState(); - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-muted').addClass('text-danger').text('Fetch failed: ' + (err.message || err)); - }); - } - $(document).off('click.abdmOpd', '#btnLoadAbdmDocs').on('click.abdmOpd', '#btnLoadAbdmDocs', function() { loadAbdmDocs(); }); + $(document).off('click.abdmOpd', '#btnRefreshAbdmRequests').on('click.abdmOpd', '#btnRefreshAbdmRequests', function() { + loadAbdmConsentRequests(); + }); + $(document).off('click.abdmOpd', '#btnSearchAbdmDocs').on('click.abdmOpd', '#btnSearchAbdmDocs', function() { loadAbdmDocs(); }); @@ -1106,35 +1047,21 @@ $(function() { loadAbdmDocDetail(docId); }); - $(document).off('click.abdmOpd', '#btnAutoAbdmFlow').on('click.abdmOpd', '#btnAutoAbdmFlow', function() { - if (autoFlowRunning) { - return; - } - stopAutoFlowLoop(); - autoFlowAttempts = 0; - if (autoFlowNeedsFreshRequest) { - autoFlowRequestId = ''; - autoFlowNeedsFreshRequest = false; - } - setFlowProgress(1); - autoFlowRunning = true; - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text('Sending consent request to patient\'s ABHA app...'); - runAutoFlowStep(); + $(document).off('click.abdmOpd', '.abdm-view-request-btn').on('click.abdmOpd', '.abdm-view-request-btn', function() { + var idx = Number($(this).data('idx')); + showAbdmConsentDetailForIndex(idx); }); - $(document).off('click.abdmOpd', '#btnFetchRecordsOnly').on('click.abdmOpd', '#btnFetchRecordsOnly', function() { - runFetchOnlyStep(); + $(document).off('click.abdmOpd', '.abdm-fetch-request-btn').on('click.abdmOpd', '.abdm-fetch-request-btn', function() { + var idx = Number($(this).data('idx')); + runFetchRecordsForRow(idx); }); $(document).off('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]').on('shown.bs.tab.abdmOpd', '[data-bs-target="#opd-abdm-tab"]', function() { if (!currentAbdmRows.length) { loadAbdmDocs(); } - }); - - $('#abdmConsentDetailModal').on('show.bs.modal', function() { - loadAbdmConsentDetail(); + loadAbdmConsentRequests(); }); $('#abdmCustomConsentModal').on('show.bs.modal', function() { @@ -1150,7 +1077,7 @@ $(function() { }); $(document).off('click.abdmOpd', '#btnSendCustomConsent').on('click.abdmOpd', '#btnSendCustomConsent', function() { - if (autoFlowRunning || fetchOnlyRunning) { + if (autoFlowRunning || fetchOnlyRunningIdx !== -1) { return; } @@ -1194,12 +1121,10 @@ $(function() { stopAutoFlowLoop(); autoFlowAttempts = 0; - autoFlowNeedsFreshRequest = false; autoFlowRequestId = (data.request_id || '').toString(); - setFlowProgress(1); autoFlowRunning = true; - applyConsentButtonState(); - $('#abdmStatusBox').removeClass('text-danger').addClass('text-muted').text('Sending custom consent request to patient\'s ABHA app...'); + applyNewRequestButtonState(); + setAbdmStatus('Sending consent request to patient\'s ABHA app...', false); runAutoFlowStep(); }).fail(function(xhr) { var data = xhr.responseJSON; @@ -1213,8 +1138,8 @@ $(function() { $('[data-bs-toggle="tooltip"]').tooltip(); - applyConsentButtonState(); - applyFetchOnlyButtonState(); - updateConsentBadge('IDLE', false); + applyNewRequestButtonState(); + loadAbdmConsentRequests(); }); +