Replace ABDM HIU M3 sidebar console with global ABHA Patient Request List (Phase 3 across all patients)
This commit is contained in:
parent
dec025d6bf
commit
9536cd2990
|
|
@ -641,6 +641,7 @@ $routes->post('AbdmGateway/consent_revoked_callback', 'AbdmGateway::consentRevok
|
|||
$routes->post('AbdmGateway/record_linked_callback', 'AbdmGateway::recordLinkedCallback');
|
||||
// ABDM M3 HIU (HMS -> Gateway)
|
||||
$routes->get('AbdmHiu', 'AbdmHiu::index', ['filter' => $abdmPermFilter]);
|
||||
$routes->get('AbdmHiu/patient_request_list_data', 'AbdmHiu::patientRequestListData', ['filter' => $abdmPermFilter]);
|
||||
$routes->get('AbdmHiu/documents', 'AbdmHiu::documents', ['filter' => $abdmPermFilter]);
|
||||
$routes->get('AbdmHiu/patient_lookup', 'AbdmHiu::patientLookup', ['filter' => $abdmPermFilter]);
|
||||
$routes->get('AbdmHiu/documents_list', 'AbdmHiu::documentsList', ['filter' => $abdmPermFilter]);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,22 @@ class AbdmHiu extends BaseController
|
|||
|
||||
public function index()
|
||||
{
|
||||
return view('abdm/hiu_m3');
|
||||
return view('abdm/patient_request_list');
|
||||
}
|
||||
|
||||
public function patientRequestListData()
|
||||
{
|
||||
$q = trim((string) ($this->request->getGet('q') ?? ''));
|
||||
$status = trim((string) ($this->request->getGet('status') ?? ''));
|
||||
$limit = (int) ($this->request->getGet('limit') ?? 300);
|
||||
if ($limit <= 0 || $limit > 1000) {
|
||||
$limit = 300;
|
||||
}
|
||||
|
||||
$service = new \App\Libraries\Abdm\ConsentSessionListService();
|
||||
$result = $service->getGlobalConsentRequestsList(['q' => $q, 'status' => $status], $limit);
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function documents()
|
||||
|
|
|
|||
443
app/Libraries/Abdm/ConsentSessionListService.php
Normal file
443
app/Libraries/Abdm/ConsentSessionListService.php
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
<?php
|
||||
|
||||
namespace App\Libraries\Abdm;
|
||||
|
||||
/**
|
||||
* Builds the global "ABHA Patient Request List" (all patients' ABDM consent
|
||||
* request history, most recent first) shown from the left-nav ABDM panel.
|
||||
*
|
||||
* The session-grouping / detail-computation logic here mirrors
|
||||
* App\Controllers\Patient::getAbdmConsentRequestsList() (Phase 3, per-patient
|
||||
* Consent Request History), just applied across every patient's ABHA address
|
||||
* instead of a single one.
|
||||
*/
|
||||
class ConsentSessionListService
|
||||
{
|
||||
private \CodeIgniter\Database\BaseConnection $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $filters optional keys: 'q' (search text), 'status'
|
||||
*/
|
||||
public function getGlobalConsentRequestsList(array $filters = [], int $limit = 300): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows') || ! $this->db->tableExists('patient_master')) {
|
||||
return ['ok' => 1, 'requests' => []];
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||
$select = ['id', 'operation', 'workflow_state', 'status', 'request_id', 'consent_id', 'hfr_id', 'abha_address', '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')
|
||||
->select(implode(', ', $select))
|
||||
->whereIn('operation', [
|
||||
'consent_request',
|
||||
'consent_status',
|
||||
'consent_reconcile',
|
||||
'data_fetch',
|
||||
'consent_callback',
|
||||
'hi_on_request_callback',
|
||||
'hi_data_push_callback',
|
||||
])
|
||||
->orderBy('id', 'DESC')
|
||||
->get(2000)
|
||||
->getResultArray();
|
||||
|
||||
if ($rows === []) {
|
||||
return ['ok' => 1, 'requests' => []];
|
||||
}
|
||||
|
||||
// Group rows by abha_address, preserving the DESC-by-id order within each group.
|
||||
$byAddress = [];
|
||||
foreach ($rows as $row) {
|
||||
$addr = trim((string) ($row['abha_address'] ?? ''));
|
||||
if ($addr === '') {
|
||||
continue;
|
||||
}
|
||||
$byAddress[$addr][] = $row;
|
||||
}
|
||||
|
||||
if ($byAddress === []) {
|
||||
return ['ok' => 1, 'requests' => []];
|
||||
}
|
||||
|
||||
$patientsByAddress = $this->lookupPatientsByAbhaAddress(array_keys($byAddress));
|
||||
|
||||
$statusFilter = strtoupper(trim((string) ($filters['status'] ?? '')));
|
||||
$q = strtolower(trim((string) ($filters['q'] ?? '')));
|
||||
|
||||
$out = [];
|
||||
foreach ($byAddress as $addr => $addrRows) {
|
||||
$patientInfo = $patientsByAddress[$addr] ?? [
|
||||
'patient_id' => 0,
|
||||
'patient_code' => '',
|
||||
'patient_name' => '',
|
||||
'mobile' => '',
|
||||
];
|
||||
|
||||
$sessions = $this->groupWorkflowRowsIntoSessions($addrRows);
|
||||
foreach ($sessions as $sessionRows) {
|
||||
$detail = $this->computeConsentSessionDetail($sessionRows, $addr);
|
||||
if ((int) ($detail['ok'] ?? 0) !== 1) {
|
||||
continue;
|
||||
}
|
||||
$consent = $detail['consent'];
|
||||
|
||||
if ($statusFilter !== '' && strtoupper((string) $consent['status']) !== $statusFilter) {
|
||||
continue;
|
||||
}
|
||||
if ($q !== '') {
|
||||
$haystack = strtolower(
|
||||
$patientInfo['patient_name'] . ' ' . $patientInfo['patient_code'] . ' '
|
||||
. $patientInfo['mobile'] . ' ' . $addr
|
||||
);
|
||||
if (strpos($haystack, $q) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = array_merge($patientInfo, $consent);
|
||||
}
|
||||
}
|
||||
|
||||
usort($out, function ($a, $b) {
|
||||
return strcmp((string) ($b['requested_on'] ?? ''), (string) ($a['requested_on'] ?? ''));
|
||||
});
|
||||
|
||||
if ($limit > 0 && count($out) > $limit) {
|
||||
$out = array_slice($out, 0, $limit);
|
||||
}
|
||||
|
||||
return ['ok' => 1, 'requests' => $out];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $abhaAddresses
|
||||
* @return array<string, array<string, mixed>> keyed by abha_address
|
||||
*/
|
||||
private function lookupPatientsByAbhaAddress(array $abhaAddresses): array
|
||||
{
|
||||
$fields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$idCol = $this->resolveExistingColumn($fields, ['id']);
|
||||
$uhidCol = $this->resolveExistingColumn($fields, ['p_code', 'uhid', 'uhid_no', 'patient_code', 'patient_id']);
|
||||
$nameCol = $this->resolveExistingColumn($fields, ['p_fname', 'patient_name', 'name']);
|
||||
$mobileCol = $this->resolveExistingColumn($fields, ['p_mobile', 'mobile', 'phone', 'contact_no']);
|
||||
$abhaAddressCol = $this->resolveExistingColumn($fields, ['abha_address', 'abha_addr']);
|
||||
|
||||
if ($idCol === null || $abhaAddressCol === null || $abhaAddresses === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('patient_master')
|
||||
->select('' . $idCol . ' AS id, ' . $abhaAddressCol . ' AS abha_address', false);
|
||||
if ($uhidCol !== null) {
|
||||
$builder->select($uhidCol . ' AS uhid', false);
|
||||
}
|
||||
if ($nameCol !== null) {
|
||||
$builder->select($nameCol . ' AS name', false);
|
||||
}
|
||||
if ($mobileCol !== null) {
|
||||
$builder->select($mobileCol . ' AS mobile', false);
|
||||
}
|
||||
$builder->whereIn($abhaAddressCol, $abhaAddresses);
|
||||
|
||||
$out = [];
|
||||
foreach ($builder->get()->getResultArray() as $p) {
|
||||
$addr = trim((string) ($p['abha_address'] ?? ''));
|
||||
if ($addr === '') {
|
||||
continue;
|
||||
}
|
||||
$out[$addr] = [
|
||||
'patient_id' => (int) ($p['id'] ?? 0),
|
||||
'patient_code' => trim((string) ($p['uhid'] ?? '')),
|
||||
'patient_name' => trim((string) ($p['name'] ?? '')),
|
||||
'mobile' => trim((string) ($p['mobile'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function resolveExistingColumn(array $fields, array $candidates): ?string
|
||||
{
|
||||
foreach ($candidates as $candidate) {
|
||||
if (in_array($candidate, $fields, true)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a DESC-by-id set of workflow rows (all belonging to one ABHA
|
||||
* address) into distinct consent request "sessions" — each
|
||||
* CONSENT_REQUEST row starts a new session, and every subsequent row
|
||||
* belongs to that session until the next CONSENT_REQUEST row appears.
|
||||
* Returns sessions in chronological order (oldest session first).
|
||||
*
|
||||
* @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.'];
|
||||
}
|
||||
|
||||
$best = null;
|
||||
$bestPriority = -1;
|
||||
$bestDecoded = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
$decoded = [];
|
||||
}
|
||||
|
||||
$rawConsentStatus = strtoupper(trim((string) (
|
||||
$decoded['consent']['status']
|
||||
?? $decoded['consent_status']
|
||||
?? $decoded['status']
|
||||
?? $decoded['data']['consent']['status']
|
||||
?? ''
|
||||
)));
|
||||
$operation = strtoupper(trim((string) ($row['operation'] ?? '')));
|
||||
$status = strtoupper(trim((string) ($row['status'] ?? '')));
|
||||
$state = strtoupper(trim((string) ($row['workflow_state'] ?? '')));
|
||||
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 120;
|
||||
|
||||
if (($operation === 'DATA_FETCH' || $operation === 'HI_DATA_PUSH_CALLBACK') && $status === 'SUCCESS') {
|
||||
$phase = 'COMPLETED';
|
||||
$priority = 500;
|
||||
} elseif (in_array($rawConsentStatus, ['GRANTED', 'APPROVED', 'ACTIVE'], true)) {
|
||||
$phase = 'GRANTED';
|
||||
$priority = 430;
|
||||
} elseif ($rawConsentStatus === 'REVOKED') {
|
||||
$phase = 'REVOKED';
|
||||
$priority = 320;
|
||||
} elseif ($rawConsentStatus === 'EXPIRED') {
|
||||
$phase = 'EXPIRED';
|
||||
$priority = 310;
|
||||
} elseif ($rawConsentStatus === 'DENIED') {
|
||||
$phase = 'DENIED';
|
||||
$priority = 300;
|
||||
} elseif ($state === 'DATA_RECEIVED') {
|
||||
$phase = 'COMPLETED';
|
||||
$priority = 480;
|
||||
} elseif ($state === 'GRANTED') {
|
||||
$phase = 'GRANTED';
|
||||
$priority = 420;
|
||||
} elseif ($state === 'REVOKED') {
|
||||
$phase = 'REVOKED';
|
||||
$priority = 300;
|
||||
} elseif ($state === 'EXPIRED') {
|
||||
$phase = 'EXPIRED';
|
||||
$priority = 290;
|
||||
} elseif ($status === 'FAILED' && $operation === 'CONSENT_REQUEST') {
|
||||
$phase = 'FAILED';
|
||||
$priority = 260;
|
||||
} elseif ($status === 'FAILED') {
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 190;
|
||||
} elseif (in_array($state, ['REQUESTED', 'PENDING', 'STATUS_CHECKED'], true)) {
|
||||
$phase = 'REQUESTED';
|
||||
$priority = 180;
|
||||
}
|
||||
|
||||
if ($best === null || $priority > $bestPriority) {
|
||||
$best = $row;
|
||||
$best['_phase'] = $phase;
|
||||
$bestPriority = $priority;
|
||||
$bestDecoded = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if ($best === null) {
|
||||
return ['ok' => 0, 'error' => 'No ABDM consent activity found.'];
|
||||
}
|
||||
|
||||
$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'] ?? ''));
|
||||
|
||||
// The session should start with its own CONSENT_REQUEST row.
|
||||
$consentRequestRow = null;
|
||||
foreach ($rows as $row) {
|
||||
if (strtoupper((string) ($row['operation'] ?? '')) === 'CONSENT_REQUEST') {
|
||||
$consentRequestRow = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$requestedHiTypes = [];
|
||||
$requestedOn = '';
|
||||
$purpose = '';
|
||||
$validFrom = '';
|
||||
$validTo = '';
|
||||
$eraseAt = '';
|
||||
$requestedBy = '';
|
||||
$hfrId = trim((string) ($best['hfr_id'] ?? ''));
|
||||
|
||||
if (is_array($consentRequestRow)) {
|
||||
$reqPayload = json_decode((string) ($consentRequestRow['request_json'] ?? ''), true);
|
||||
if (! is_array($reqPayload)) {
|
||||
$reqPayload = [];
|
||||
}
|
||||
$consentBlock = (array) ($reqPayload['consent'] ?? []);
|
||||
$requestedHiTypes = $this->normalizeHiTypesList($consentBlock['hiTypes'] ?? $consentBlock['hi_types'] ?? []);
|
||||
$requestedOn = trim((string) ($consentRequestRow['created_at'] ?? ''));
|
||||
$purpose = trim((string) ($consentBlock['purpose']['text'] ?? $consentBlock['purpose']['code'] ?? ''));
|
||||
$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'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$grantedHiTypes = [];
|
||||
$grantedOn = '';
|
||||
foreach ($rows as $row) {
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$rowHiTypes = $this->normalizeHiTypesList($decoded['hi_types'] ?? $decoded['consent']['hi_types'] ?? []);
|
||||
if ($rowHiTypes !== []) {
|
||||
$grantedHiTypes = $rowHiTypes;
|
||||
$grantedOn = trim((string) ($decoded['granted_at'] ?? $row['updated_at'] ?? ''));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$revokedOn = trim((string) ($best['revoked_at'] ?? ''));
|
||||
$expiredOn = trim((string) ($best['expired_at'] ?? ''));
|
||||
if (in_array($phase, ['GRANTED', 'COMPLETED'], true) && $grantedOn === '') {
|
||||
$grantedOn = trim((string) ($bestDecoded['granted_at'] ?? $best['updated_at'] ?? ''));
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$typesForItems = $requestedHiTypes !== [] ? $requestedHiTypes : $grantedHiTypes;
|
||||
foreach ($typesForItems as $hiType) {
|
||||
$itemStatus = 'REQUESTED';
|
||||
$itemTimestamp = $requestedOn;
|
||||
|
||||
if ($phase === 'REVOKED') {
|
||||
$itemStatus = 'REVOKED';
|
||||
$itemTimestamp = $revokedOn;
|
||||
} elseif ($phase === 'EXPIRED') {
|
||||
$itemStatus = 'EXPIRED';
|
||||
$itemTimestamp = $expiredOn;
|
||||
} elseif (in_array($phase, ['GRANTED', 'COMPLETED'], true)) {
|
||||
if (in_array($hiType, $grantedHiTypes, true)) {
|
||||
$itemStatus = 'GRANTED';
|
||||
$itemTimestamp = $grantedOn;
|
||||
} else {
|
||||
$itemStatus = 'DENIED';
|
||||
$itemTimestamp = $grantedOn;
|
||||
}
|
||||
} elseif ($phase === 'FAILED') {
|
||||
$itemStatus = 'FAILED';
|
||||
$itemTimestamp = trim((string) ($best['updated_at'] ?? ''));
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'document_name' => $hiType,
|
||||
'permission' => 'VIEW',
|
||||
'status' => $itemStatus,
|
||||
'timestamp' => $itemTimestamp,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'consent' => [
|
||||
'consent_id' => $consentId,
|
||||
'consent_request_id' => $consentRequestId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'status' => $phase,
|
||||
'purpose' => $purpose !== '' ? $purpose : 'Care Management',
|
||||
'requested_hi_types' => $requestedHiTypes,
|
||||
'granted_hi_types' => $grantedHiTypes,
|
||||
'valid_from' => $validFrom,
|
||||
'valid_to' => $validTo,
|
||||
'erase_at' => $eraseAt,
|
||||
'requested_on' => $requestedOn,
|
||||
'granted_on' => $grantedOn,
|
||||
'revoked_on' => $revokedOn,
|
||||
'expired_on' => $expiredOn,
|
||||
'hfr_id' => $hfrId,
|
||||
'requested_by' => $requestedBy !== '' ? $requestedBy : 'HMS',
|
||||
'items' => $items,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a hiTypes value (JSON string, array, or single string) into a
|
||||
* clean, de-duplicated string array.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function normalizeHiTypesList($value): array
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
$value = is_array($decoded) ? $decoded : [$value];
|
||||
}
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($value as $v) {
|
||||
$v = trim((string) $v);
|
||||
if ($v !== '' && ! in_array($v, $out, true)) {
|
||||
$out[] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
286
app/Views/abdm/patient_request_list.php
Normal file
286
app/Views/abdm/patient_request_list.php
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<div class="container-fluid py-3">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<h5 class="mb-0">ABHA Patient Request List</h5>
|
||||
<button class="btn btn-sm btn-outline-primary" id="btnRefreshPatientRequests" type="button">Refresh</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info small mb-3">
|
||||
Consent request history for every patient, most recent first. Open a patient's OPD profile to raise a new request.
|
||||
</div>
|
||||
|
||||
<div class="row g-2 align-items-end mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label mb-1">Search Patient</label>
|
||||
<input id="prlSearch" class="form-control" placeholder="Name / UHID / Mobile / ABHA Address">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1">Status</label>
|
||||
<select id="prlStatus" class="form-select">
|
||||
<option value="">All</option>
|
||||
<option value="REQUESTED">Requested</option>
|
||||
<option value="GRANTED">Granted</option>
|
||||
<option value="COMPLETED">Completed</option>
|
||||
<option value="REVOKED">Revoked</option>
|
||||
<option value="EXPIRED">Expired</option>
|
||||
<option value="DENIED">Denied</option>
|
||||
<option value="FAILED">Failed</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button id="btnPrlSearch" class="btn btn-outline-secondary w-100" type="button">Apply</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div id="prlCountBox" class="small text-muted text-md-end">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive border rounded">
|
||||
<table class="table table-sm table-hover mb-0" id="prlTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
<th>UHID</th>
|
||||
<th>ABHA Address</th>
|
||||
<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="9" class="text-muted text-center">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="prlDetailModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Consent Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="prlDetailBody">
|
||||
<div class="text-muted small">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var listUrl = '<?= base_url('AbdmHiu/patient_request_list_data') ?>';
|
||||
var fetchOnlyBaseUrl = '<?= base_url('billing/patient/abdm_content_fetch_only') ?>';
|
||||
var openPatientBaseUrl = '<?= base_url('billing/patient/show_profile_opd') ?>';
|
||||
var currentItems = [];
|
||||
var fetchRunningIdx = -1;
|
||||
|
||||
function escHtml(v) {
|
||||
return (v || '').toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function fmtTs(value) {
|
||||
value = (value || '').toString().trim();
|
||||
if (value === '') {
|
||||
return '-';
|
||||
}
|
||||
var d = new Date(value.indexOf('T') === -1 ? value.replace(' ', 'T') : value);
|
||||
if (isNaN(d.getTime())) {
|
||||
return escHtml(value);
|
||||
}
|
||||
return escHtml(d.toLocaleString());
|
||||
}
|
||||
|
||||
function statusBadgeClass(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';
|
||||
}
|
||||
}
|
||||
|
||||
function itemBadgeClass(status) {
|
||||
switch ((status || '').toString().toUpperCase()) {
|
||||
case 'GRANTED': 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-info-subtle text-info border border-info-subtle';
|
||||
}
|
||||
}
|
||||
|
||||
function renderDetail(consent) {
|
||||
if (!consent) {
|
||||
return '<div class="text-danger small">No consent details available.</div>';
|
||||
}
|
||||
var metaHtml = '<div class="row small text-muted mb-3">'
|
||||
+ '<div class="col-md-6">Patient: <strong>' + escHtml(consent.patient_name || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">UHID: <strong>' + escHtml(consent.patient_code || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Consent ID: <strong>' + escHtml(consent.consent_id || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">ABHA: <strong>' + escHtml(consent.abha_address || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Purpose: <strong>' + escHtml(consent.purpose || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Status: <strong>' + escHtml(consent.status || '-') + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid From: <strong>' + fmtTs(consent.valid_from) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Valid To: <strong>' + fmtTs(consent.valid_to) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Requested On: <strong>' + fmtTs(consent.requested_on) + '</strong></div>'
|
||||
+ '<div class="col-md-6">Erase Date: <strong>' + fmtTs(consent.erase_at) + '</strong></div>'
|
||||
+ '</div>';
|
||||
|
||||
var rowsHtml = '';
|
||||
(consent.items || []).forEach(function(item) {
|
||||
rowsHtml += '<tr>'
|
||||
+ '<td>' + escHtml(item.document_name || '') + '</td>'
|
||||
+ '<td>' + escHtml(item.permission || 'VIEW') + '</td>'
|
||||
+ '<td><span class="badge ' + itemBadgeClass(item.status) + '">' + escHtml(item.status || '') + '</span></td>'
|
||||
+ '<td>' + fmtTs(item.timestamp) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
if (rowsHtml === '') {
|
||||
rowsHtml = '<tr><td colspan="4" class="text-muted text-center">No health information types recorded for this request.</td></tr>';
|
||||
}
|
||||
|
||||
return metaHtml + '<div class="table-responsive"><table class="table table-sm table-bordered mb-0">'
|
||||
+ '<thead><tr><th>Document Type</th><th>Permission</th><th>Status</th><th>Timestamp</th></tr></thead>'
|
||||
+ '<tbody>' + rowsHtml + '</tbody></table></div>';
|
||||
}
|
||||
|
||||
function renderTable(items) {
|
||||
if (!items || !items.length) {
|
||||
$('#prlTable tbody').html('<tr><td colspan="9" class="text-muted text-center">No consent requests found.</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
items.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 patientId = Number(consent.patient_id || 0);
|
||||
var canFetch = (status === 'GRANTED' || status === 'COMPLETED') && patientId > 0;
|
||||
var canOpen = patientId > 0;
|
||||
|
||||
html += '<tr>'
|
||||
+ '<td class="small">' + escHtml(consent.patient_name || '-') + '</td>'
|
||||
+ '<td class="small">' + escHtml(consent.patient_code || '-') + '</td>'
|
||||
+ '<td class="small">' + escHtml(consent.abha_address || '-') + '</td>'
|
||||
+ '<td><span class="badge ' + statusBadgeClass(status) + '">' + escHtml(status || 'UNKNOWN') + '</span></td>'
|
||||
+ '<td class="small">' + escHtml(hiTypesText) + '</td>'
|
||||
+ '<td class="small">' + escHtml(consent.requested_by || 'HMS') + '</td>'
|
||||
+ '<td class="small">' + fmtTs(consent.requested_on) + '</td>'
|
||||
+ '<td class="small">' + fmtTs(consent.valid_to) + '</td>'
|
||||
+ '<td class="text-end">'
|
||||
+ '<button type="button" class="btn btn-link btn-sm p-0 me-2 prl-view-btn" data-idx="' + idx + '">View</button>'
|
||||
+ (canFetch ? ('<button type="button" class="btn btn-link btn-sm p-0 me-2 prl-fetch-btn" data-idx="' + idx + '">Fetch Records</button>') : '')
|
||||
+ (canOpen ? ('<button type="button" class="btn btn-link btn-sm p-0 prl-open-btn" data-idx="' + idx + '">Open Patient</button>') : '')
|
||||
+ '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
$('#prlTable tbody').html(html);
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
$('#prlCountBox').text('Loading...');
|
||||
var url = listUrl + '?q=' + encodeURIComponent($('#prlSearch').val() || '') + '&status=' + encodeURIComponent($('#prlStatus').val() || '');
|
||||
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) || 'Unable to load consent requests.');
|
||||
}
|
||||
currentItems = Array.isArray(data.requests) ? data.requests : [];
|
||||
renderTable(currentItems);
|
||||
$('#prlCountBox').text(currentItems.length + ' request(s) found.');
|
||||
})
|
||||
.catch(function(err) {
|
||||
$('#prlTable tbody').html('<tr><td colspan="9" class="text-danger text-center">' + escHtml('Failed to load: ' + (err.message || err)) + '</td></tr>');
|
||||
$('#prlCountBox').text('Failed to load.');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).off('click.prl', '#btnRefreshPatientRequests').on('click.prl', '#btnRefreshPatientRequests', function() {
|
||||
loadList();
|
||||
});
|
||||
$(document).off('click.prl', '#btnPrlSearch').on('click.prl', '#btnPrlSearch', function() {
|
||||
loadList();
|
||||
});
|
||||
$(document).off('keypress.prl', '#prlSearch').on('keypress.prl', '#prlSearch', function(e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
loadList();
|
||||
}
|
||||
});
|
||||
$(document).off('change.prl', '#prlStatus').on('change.prl', '#prlStatus', function() {
|
||||
loadList();
|
||||
});
|
||||
|
||||
$(document).off('click.prl', '.prl-view-btn').on('click.prl', '.prl-view-btn', function() {
|
||||
var idx = Number($(this).data('idx'));
|
||||
var consent = currentItems[idx];
|
||||
$('#prlDetailBody').html(renderDetail(consent));
|
||||
bootstrap.Modal.getOrCreateInstance(document.getElementById('prlDetailModal')).show();
|
||||
});
|
||||
|
||||
$(document).off('click.prl', '.prl-open-btn').on('click.prl', '.prl-open-btn', function() {
|
||||
var idx = Number($(this).data('idx'));
|
||||
var consent = currentItems[idx];
|
||||
if (consent && consent.patient_id && typeof load_form === 'function') {
|
||||
load_form(openPatientBaseUrl + '/' + consent.patient_id, consent.patient_name || 'Patient');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).off('click.prl', '.prl-fetch-btn').on('click.prl', '.prl-fetch-btn', function() {
|
||||
var idx = Number($(this).data('idx'));
|
||||
if (fetchRunningIdx !== -1) {
|
||||
return;
|
||||
}
|
||||
var consent = currentItems[idx];
|
||||
if (!consent || !consent.patient_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchRunningIdx = idx;
|
||||
var $btn = $(this);
|
||||
var originalText = $btn.text();
|
||||
$btn.text('Fetching...').prop('disabled', true);
|
||||
|
||||
var url = fetchOnlyBaseUrl + '/' + consent.patient_id + '?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.');
|
||||
}
|
||||
fetchRunningIdx = -1;
|
||||
$btn.text(originalText).prop('disabled', false);
|
||||
loadList();
|
||||
})
|
||||
.catch(function(err) {
|
||||
fetchRunningIdx = -1;
|
||||
$btn.text(originalText).prop('disabled', false);
|
||||
alert('Fetch failed: ' + (err.message || err));
|
||||
});
|
||||
});
|
||||
|
||||
loadList();
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -415,9 +415,9 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
|
|||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('AbdmHiu') ?>','ABDM HIU M3')">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
<span>ABDM HIU M3</span>
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('AbdmHiu') ?>','ABHA Patient Request List')">
|
||||
<i class="bi bi-clipboard2-pulse"></i>
|
||||
<span>ABHA Patient Request List</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user