ABDM OPD tab: replace flow buttons with per-patient Consent Request History list (Phase 3)
This commit is contained in:
parent
48d19f0ff7
commit
25db2e5ddc
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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<int, array<string, mixed>> $rows rows ordered DESC by id
|
||||
* @return array<int, array<int, array<string, mixed>>>
|
||||
*/
|
||||
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<int, array<string, mixed>> $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,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -312,16 +312,8 @@ if ($patientPhotoPath === '') {
|
|||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button type="button" class="btn btn-primary" id="btnAutoAbdmFlow" <?= ($abhaIsVerified && $patientAbhaAddress !== '') ? '' : 'disabled' ?>>
|
||||
<span class="abdm-btn-spinner spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
|
||||
<span id="btnAutoAbdmFlowLabel">Fetch ABDM Records</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="btnCustomAbdmRequest" data-bs-toggle="modal" data-bs-target="#abdmCustomConsentModal" <?= ($abhaIsVerified && $patientAbhaAddress !== '') ? '' : 'disabled' ?>>
|
||||
Custom Request
|
||||
</button>
|
||||
<button type="button" class="btn btn-success d-none" id="btnFetchRecordsOnly">
|
||||
<span class="abdm-btn-spinner spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
|
||||
<span id="btnFetchRecordsOnlyLabel">Fetch Records</span>
|
||||
<button type="button" class="btn btn-primary" id="btnNewAbdmRequest" data-bs-toggle="modal" data-bs-target="#abdmCustomConsentModal" <?= ($abhaIsVerified && $patientAbhaAddress !== '') ? '' : 'disabled' ?>>
|
||||
+ New Request
|
||||
</button>
|
||||
<div>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 mt-1" id="btnLoadAbdmDocs">Refresh list</button>
|
||||
|
|
@ -335,24 +327,38 @@ if ($patientPhotoPath === '') {
|
|||
</div>
|
||||
<?php } else { ?>
|
||||
<div class="small text-muted mt-2 mb-0">
|
||||
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.
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="border rounded p-3 mt-3 bg-light">
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-2" id="abdmFlowSteps">
|
||||
<span class="abdm-flow-step" data-step="1">1. Consent Requested</span>
|
||||
<span class="abdm-flow-step" data-step="2">2. Consent Granted</span>
|
||||
<span class="abdm-flow-step" data-step="3">3. Records Fetched</span>
|
||||
</div>
|
||||
<div class="progress mb-2" style="height: 8px;">
|
||||
<div id="abdmFlowProgressBar" class="progress-bar" role="progressbar" style="width:0%"></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-secondary" id="abdmLastConsentBadge">Unknown</span>
|
||||
<span id="abdmStatusBox" class="small text-muted flex-grow-1">Click "Fetch ABDM Records" to request and load the patient's ABDM health records.</span>
|
||||
<button type="button" class="btn btn-link btn-sm p-0 d-none" id="btnViewAbdmConsent" data-bs-toggle="modal" data-bs-target="#abdmConsentDetailModal">View Consent</button>
|
||||
</div>
|
||||
<div id="abdmStatusBoxWrap" class="border rounded p-3 mt-3 bg-light d-none">
|
||||
<span id="abdmStatusBox" class="small text-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border mt-2 mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="fw-semibold">Consent Request History</div>
|
||||
<button type="button" class="btn btn-link btn-sm p-0" id="btnRefreshAbdmRequests">Refresh</button>
|
||||
</div>
|
||||
<div class="table-responsive border rounded">
|
||||
<table class="table table-sm table-hover mb-0" id="abdmRequestsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>HI Types</th>
|
||||
<th>Requested By</th>
|
||||
<th>Requested On</th>
|
||||
<th>Expiry</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td colspan="6" class="text-muted text-center">No consent requests yet.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -533,65 +539,28 @@ $(function() {
|
|||
var abdmAutoFlowUrl = '<?= base_url('billing/patient/abdm_content_auto_flow/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmFetchOnlyUrl = '<?= base_url('billing/patient/abdm_content_fetch_only/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmConsentDetailUrl = '<?= base_url('billing/patient/abdm_consent_detail/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmConsentRequestsUrl = '<?= base_url('billing/patient/abdm_consent_requests/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmCustomRequestUrl = '<?= base_url('billing/patient/abdm_content_request_custom/' . (int) ($patient->id ?? 0)) ?>';
|
||||
var abdmDocDetailBaseUrl = '<?= base_url('billing/patient/abdm_document_detail/' . (int) ($patient->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('<div class="text-muted small">Loading...</div>');
|
||||
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('<tr><td colspan="6" class="text-muted text-center">No consent requests yet.</td></tr>');
|
||||
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 += '<tr>'
|
||||
+ '<td><span class="badge ' + consentStatusBadgeClass(status) + '">' + escHtml(status || 'UNKNOWN') + '</span></td>'
|
||||
+ '<td class="small">' + escHtml(hiTypesText) + '</td>'
|
||||
+ '<td class="small">' + escHtml(consent.requested_by || 'HMS') + '</td>'
|
||||
+ '<td class="small">' + fmtConsentTs(consent.requested_on) + '</td>'
|
||||
+ '<td class="small">' + fmtConsentTs(consent.valid_to) + '</td>'
|
||||
+ '<td class="text-end">'
|
||||
+ '<button type="button" class="btn btn-link btn-sm p-0 me-2 abdm-view-request-btn" data-idx="' + idx + '">View</button>'
|
||||
+ (canFetch ? ('<button type="button" class="btn btn-link btn-sm p-0 abdm-fetch-request-btn" data-idx="' + idx + '">Fetch Records</button>') : '')
|
||||
+ '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
$('#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('<div class="text-danger small">' + escHtml('Failed to load consent details: ' + (err.message || err)) + '</div>');
|
||||
$('#abdmRequestsTable tbody').html('<tr><td colspan="6" class="text-danger text-center">' + escHtml('Failed to load consent request history: ' + (err.message || err)) + '</td></tr>');
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user