fix(abdm): scope M1 mobile OTP verified ABHA addresses strictly to selected profile
This commit is contained in:
parent
543f78e696
commit
41b6231548
|
|
@ -969,10 +969,14 @@ class Abha extends BaseController
|
|||
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||
$otp = trim((string) ($this->request->getPost('otp') ?? ''));
|
||||
$authMethod = strtoupper(trim((string) ($this->request->getPost('auth_method') ?? 'MOBILE_OTP')));
|
||||
$mobile = preg_replace('/\D/', '', trim((string) ($this->request->getPost('mobile') ?? '')));
|
||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||
$otp = trim((string) ($this->request->getPost('otp') ?? ''));
|
||||
$authMethod = strtoupper(trim((string) ($this->request->getPost('auth_method') ?? 'MOBILE_OTP')));
|
||||
$mobile = preg_replace('/\D/', '', trim((string) ($this->request->getPost('mobile') ?? '')));
|
||||
$selectedPatientId = (int) ($this->request->getPost('selected_patient_id') ?? 0);
|
||||
$selectedPCode = trim((string) ($this->request->getPost('selected_p_code') ?? ''));
|
||||
$selectedAbhaNumber = trim((string) ($this->request->getPost('selected_abha_number') ?? ''));
|
||||
$selectedName = trim((string) ($this->request->getPost('selected_name') ?? ''));
|
||||
|
||||
if ($txnId === '' || $otp === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'Transaction ID and 6-digit OTP are required']);
|
||||
|
|
@ -1002,33 +1006,69 @@ class Abha extends BaseController
|
|||
return '';
|
||||
};
|
||||
|
||||
// If multiple accounts were returned in $result['accounts'], pick the one matching fullProfile or selected profile
|
||||
$targetLast4 = preg_replace('/\D/', '', substr(
|
||||
$firstNonEmpty([
|
||||
$fullProfile['ABHANumber'] ?? null,
|
||||
$fullProfile['abhaNumber'] ?? null,
|
||||
$fullProfile['abha_id'] ?? null,
|
||||
$selectedAbhaNumber,
|
||||
]),
|
||||
-4
|
||||
));
|
||||
if (! empty($result['accounts']) && is_array($result['accounts'])) {
|
||||
foreach ($result['accounts'] as $accCandidate) {
|
||||
if (! is_array($accCandidate)) {
|
||||
continue;
|
||||
}
|
||||
$candAbha = $firstNonEmpty([
|
||||
$accCandidate['ABHANumber'] ?? null,
|
||||
$accCandidate['abhaNumber'] ?? null,
|
||||
$accCandidate['abha_number'] ?? null,
|
||||
$accCandidate['abha_id'] ?? null,
|
||||
]);
|
||||
$candLast4 = preg_replace('/\D/', '', substr($candAbha, -4));
|
||||
if ($targetLast4 !== '' && $candLast4 !== '' && $candLast4 === $targetLast4) {
|
||||
$account = $accCandidate;
|
||||
break;
|
||||
}
|
||||
$candName = trim((string) ($accCandidate['name'] ?? $accCandidate['fullName'] ?? ''));
|
||||
if ($targetLast4 === '' && $selectedName !== '' && $candName !== '' && strcasecmp($candName, $selectedName) === 0) {
|
||||
$account = $accCandidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$abhaNum = $firstNonEmpty([
|
||||
$fullProfile['ABHANumber'] ?? null,
|
||||
$fullProfile['abhaNumber'] ?? null,
|
||||
$fullProfile['abha_id'] ?? null,
|
||||
$account['ABHANumber'] ?? null,
|
||||
$account['abhaNumber'] ?? null,
|
||||
$account['abha_number'] ?? null,
|
||||
$account['abha_id'] ?? null,
|
||||
$fullProfile['ABHANumber'] ?? null,
|
||||
$fullProfile['abhaNumber'] ?? null,
|
||||
$fullProfile['abha_id'] ?? null,
|
||||
$selectedAbhaNumber,
|
||||
]);
|
||||
|
||||
$abhaAddress = $firstNonEmpty([
|
||||
$account['preferredAbhaAddress'] ?? null,
|
||||
$account['preferredAddress'] ?? null,
|
||||
$account['abhaAddress'] ?? null,
|
||||
$account['abha_address'] ?? null,
|
||||
$fullProfile['preferredAbhaAddress'] ?? null,
|
||||
$fullProfile['preferredAddress'] ?? null,
|
||||
$fullProfile['abhaAddress'] ?? null,
|
||||
$fullProfile['abha_address'] ?? null,
|
||||
$account['preferredAbhaAddress'] ?? null,
|
||||
$account['preferredAddress'] ?? null,
|
||||
$account['abhaAddress'] ?? null,
|
||||
$account['abha_address'] ?? null,
|
||||
]);
|
||||
|
||||
$name = $firstNonEmpty([
|
||||
$fullProfile['name'] ?? null,
|
||||
trim(($fullProfile['firstName'] ?? '') . ' ' . ($fullProfile['middleName'] ?? '') . ' ' . ($fullProfile['lastName'] ?? '')),
|
||||
$account['name'] ?? null,
|
||||
$account['fullName'] ?? null,
|
||||
$account['full_name'] ?? null,
|
||||
$fullProfile['name'] ?? null,
|
||||
trim(($fullProfile['firstName'] ?? '') . ' ' . ($fullProfile['middleName'] ?? '') . ' ' . ($fullProfile['lastName'] ?? '')),
|
||||
$selectedName,
|
||||
]);
|
||||
|
||||
$genderRaw = $firstNonEmpty([
|
||||
|
|
@ -1160,12 +1200,43 @@ class Abha extends BaseController
|
|||
if ($abhaAddress !== '' && str_contains($abhaAddress, '@')) {
|
||||
$allAbhaAddresses[$abhaAddress] = true;
|
||||
}
|
||||
if (! empty($fullProfile['phrAddress']) && is_array($fullProfile['phrAddress'])) {
|
||||
foreach ($fullProfile['phrAddress'] as $phrAddr) {
|
||||
if (is_string($phrAddr) && str_contains($phrAddr, '@')) {
|
||||
$allAbhaAddresses[trim($phrAddr)] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$cleanAbhaDigits = preg_replace('/\D/', '', $abhaNum);
|
||||
$last4Digits = strlen($cleanAbhaDigits) >= 4 ? substr($cleanAbhaDigits, -4) : '';
|
||||
|
||||
if (! empty($result['accounts']) && is_array($result['accounts'])) {
|
||||
foreach ($result['accounts'] as $acc) {
|
||||
if (is_array($acc)) {
|
||||
foreach (['preferredAbhaAddress', 'preferredAddress', 'abhaAddress', 'abha_address'] as $ak) {
|
||||
if (! empty($acc[$ak]) && is_string($acc[$ak]) && str_contains($acc[$ak], '@')) {
|
||||
$allAbhaAddresses[trim($acc[$ak])] = true;
|
||||
if (! is_array($acc)) {
|
||||
continue;
|
||||
}
|
||||
$accAbhaRaw = (string) ($acc['ABHANumber'] ?? $acc['abhaNumber'] ?? $acc['abha_number'] ?? $acc['abha_id'] ?? '');
|
||||
$accAbhaDigits = preg_replace('/\D/', '', $accAbhaRaw);
|
||||
$accName = trim((string) ($acc['name'] ?? $acc['fullName'] ?? ''));
|
||||
|
||||
// Skip accounts belonging to other family members on the same mobile
|
||||
if ($last4Digits !== '' && $accAbhaDigits !== '' && ! str_ends_with($accAbhaDigits, $last4Digits)) {
|
||||
continue;
|
||||
}
|
||||
if ($last4Digits === '' && $name !== '' && $accName !== '' && stripos($accName, $name) === false && stripos($name, $accName) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (['preferredAbhaAddress', 'preferredAddress', 'abhaAddress', 'abha_address'] as $ak) {
|
||||
if (! empty($acc[$ak]) && is_string($acc[$ak]) && str_contains($acc[$ak], '@')) {
|
||||
$allAbhaAddresses[trim($acc[$ak])] = true;
|
||||
}
|
||||
}
|
||||
if (! empty($acc['phrAddress']) && is_array($acc['phrAddress'])) {
|
||||
foreach ($acc['phrAddress'] as $phrAddr) {
|
||||
if (is_string($phrAddr) && str_contains($phrAddr, '@')) {
|
||||
$allAbhaAddresses[trim($phrAddr)] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1173,38 +1244,82 @@ class Abha extends BaseController
|
|||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$patientIdForAddrs = $patientInfo !== null ? (int)($patientInfo['patient_id'] ?? 0) : 0;
|
||||
$cleanAbhaDigits = preg_replace('/\D/', '', $abhaNum);
|
||||
$patientIdForAddrs = $patientInfo !== null ? (int) ($patientInfo['patient_id'] ?? 0) : $selectedPatientId;
|
||||
|
||||
$pBuilder = $db->table('patient_master');
|
||||
$pBuilder->groupStart();
|
||||
if ($patientIdForAddrs > 0) {
|
||||
$pBuilder->where('id', $patientIdForAddrs);
|
||||
// Match ONLY the local patient record(s) belonging to this specific ABHA profile
|
||||
// (never pull all family members who share the same mobile number).
|
||||
$matchedPids = [];
|
||||
$matchedPCodes = [];
|
||||
if ($selectedPCode !== '') {
|
||||
$matchedPCodes[] = $selectedPCode;
|
||||
}
|
||||
if ($cleanAbhaDigits !== '') {
|
||||
$pBuilder->orWhere('abha_id', $cleanAbhaDigits);
|
||||
|
||||
$pRows = [];
|
||||
if ($patientIdForAddrs > 0 || strlen($cleanAbhaDigits) === 14) {
|
||||
$pBuilder = $db->table('patient_master');
|
||||
$pBuilder->groupStart();
|
||||
if ($patientIdForAddrs > 0) {
|
||||
$pBuilder->where('id', $patientIdForAddrs);
|
||||
}
|
||||
if (strlen($cleanAbhaDigits) === 14) {
|
||||
$pBuilder->orWhere('abha_id', $cleanAbhaDigits);
|
||||
}
|
||||
$pBuilder->groupEnd();
|
||||
$pRows = $pBuilder->select('id, p_code, p_fname, abha_id, abha_address')->get()->getResultArray();
|
||||
}
|
||||
if ($profileMobile !== '') {
|
||||
$pBuilder->orWhere('mphone1', $profileMobile)->orWhere('mphone2', $profileMobile);
|
||||
|
||||
// Fallback: if not yet matched by patient_id or 14-digit abha_id, search by mobile AND filter strictly by last-4 ABHA digits or name
|
||||
if (empty($pRows) && $profileMobile !== '') {
|
||||
$mobCandidates = $db->table('patient_master')
|
||||
->select('id, p_code, p_fname, abha_id, abha_address')
|
||||
->groupStart()
|
||||
->where('mphone1', $profileMobile)
|
||||
->orWhere('mphone2', $profileMobile)
|
||||
->groupEnd()
|
||||
->get()->getResultArray();
|
||||
|
||||
foreach ($mobCandidates as $mc) {
|
||||
$mcAbha = preg_replace('/\D/', '', (string) ($mc['abha_id'] ?? ''));
|
||||
if ($last4Digits !== '' && $mcAbha !== '') {
|
||||
if (str_ends_with($mcAbha, $last4Digits)) {
|
||||
$pRows[] = $mc;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$mcName = trim((string) ($mc['p_fname'] ?? ''));
|
||||
if ($name !== '' && $mcName !== '' && (stripos($mcName, $name) !== false || stripos($name, $mcName) !== false)) {
|
||||
$pRows[] = $mc;
|
||||
}
|
||||
}
|
||||
}
|
||||
$pBuilder->groupEnd();
|
||||
$pRows = $pBuilder->select('id, abha_address')->get()->getResultArray();
|
||||
$matchedPids = [];
|
||||
|
||||
foreach ($pRows as $pr) {
|
||||
$matchedPids[] = (int)$pr['id'];
|
||||
$pa = trim((string)($pr['abha_address'] ?? ''));
|
||||
$matchedPids[] = (int) $pr['id'];
|
||||
if (! empty($pr['p_code'])) {
|
||||
$matchedPCodes[] = trim((string) $pr['p_code']);
|
||||
}
|
||||
$pa = trim((string) ($pr['abha_address'] ?? ''));
|
||||
if ($pa !== '' && str_contains($pa, '@')) {
|
||||
$allAbhaAddresses[$pa] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($matchedPids)) {
|
||||
$txns = $db->table('abdm_link_transactions')
|
||||
->select('abha_address')
|
||||
->whereIn('patient_id', array_unique($matchedPids))
|
||||
->get()->getResultArray();
|
||||
$matchedPids = array_values(array_filter(array_unique($matchedPids)));
|
||||
$matchedPCodes = array_values(array_filter(array_unique($matchedPCodes)));
|
||||
|
||||
if (! empty($matchedPids) || ! empty($matchedPCodes)) {
|
||||
$txnBuilder = $db->table('abdm_link_transactions')->select('abha_address');
|
||||
$txnBuilder->groupStart();
|
||||
if (! empty($matchedPids)) {
|
||||
$txnBuilder->whereIn('patient_id', $matchedPids);
|
||||
}
|
||||
if (! empty($matchedPCodes)) {
|
||||
$txnBuilder->orWhereIn('patient_ref', $matchedPCodes);
|
||||
}
|
||||
$txnBuilder->groupEnd();
|
||||
$txns = $txnBuilder->get()->getResultArray();
|
||||
foreach ($txns as $t) {
|
||||
$ta = trim((string)($t['abha_address'] ?? ''));
|
||||
$ta = trim((string) ($t['abha_address'] ?? ''));
|
||||
if ($ta !== '' && str_contains($ta, '@')) {
|
||||
$allAbhaAddresses[$ta] = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -594,6 +594,13 @@ window.AbhaMobileModal = (function () {
|
|||
postData.append('otp', otp);
|
||||
postData.append('auth_method', authMethod);
|
||||
postData.append('mobile', searchMobile);
|
||||
if (selectedProfile) {
|
||||
postData.append('selected_index', selectedProfile.index || '');
|
||||
postData.append('selected_patient_id', selectedProfile.patient_id || '');
|
||||
postData.append('selected_p_code', selectedProfile.p_code || '');
|
||||
postData.append('selected_abha_number', selectedProfile.abha_number || '');
|
||||
postData.append('selected_name', selectedProfile.name || '');
|
||||
}
|
||||
postData.append('<?= csrf_token() ?>', csrf());
|
||||
|
||||
fetch('<?= base_url('abha/find/mobile/verify-otp') ?>', {
|
||||
|
|
|
|||
95
docs/abdm/ABDM_HTC_AUDIT_DEMO_GUIDE.md
Normal file
95
docs/abdm/ABDM_HTC_AUDIT_DEMO_GUIDE.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# ABDM Sandbox Exit — HTC Interview & Audit Demo Guide
|
||||
|
||||
**Saved Session Date:** 2026-10-29
|
||||
**Purpose:** Quick-reference readiness checklist, UI URLs, database log tables, and live demo script for the NHA / Empaneled Implementation Support Agency (ISA) Functional & Security Audit (HTC).
|
||||
|
||||
---
|
||||
|
||||
## 1. Readiness Matrix (HIP, HIU & Consent Flow)
|
||||
|
||||
### A. HIP (Health Information Provider — M1 & M2)
|
||||
| Requirement | Status | HMS Implementation Reference |
|
||||
| :--- | :---: | :--- |
|
||||
| **Capture patient records digitally & link with ABHA** | Ready | Patient Registration & OPD/IPD Profile (`patient_master.abha_id`, `patient_master.abha_address`), plus Scan & Share Queue (`app/Controllers/AbdmOpdQueue.php`). |
|
||||
| **Generate ABDM-compliant FHIR R4 records** | Ready | Generates NRCeS FHIR R4 `Bundle` (`type: "document"`, `Composition` first entry, SNOMED CT + LOINC coded) for all 7 HI types: `Prescription`, `OPConsultation`, `DischargeSummary`, `DiagnosticReport`, `ImmunizationRecord`, `WellnessRecord`, `Invoice`. |
|
||||
| **Canonical Care Context Reference & Notification** | Ready | Structured care context references (`PRESC-{pid}-S{sid}-{YYYYMMDD}`, `OPD-...`, `DISCHARGE-...`, `LAB-...`, `RAD-...`, `INVOICE-...`, `WELLNESS-...`, `IMM-...`) notified via `/v3/hip/link/carecontext` and `/v3/hip/patients/sms/notify2` (`app/Controllers/AbdmGateway.php`). |
|
||||
| **Share records only after valid patient consent** | Ready | HIP consent webhook (`POST /api/v3/hiu/consent/on-notify` -> `AbdmGateway::hipConsentNotifyCallback`) verifies `GRANTED` state and date range before Fidelius (ECDH Curve25519) encrypted health record push. |
|
||||
|
||||
### B. HIU (Health Information User — M3)
|
||||
| Requirement | Status | HMS Implementation Reference |
|
||||
| :--- | :---: | :--- |
|
||||
| **Raise consent requests** | Ready | Available in Patient OPD Profile (`Patient::abdm_content_request_custom`) and HIU Console (`AbdmHiu::consentRequest`). Supports purpose (`CAREMGT`), date range, expiry (`dataEraseAt`), and HI type selection. |
|
||||
| **Fetch records after consent approval** | Ready | Processes `/v3/hiu/consent/request/on-status`, `/on-notify`, and `/health-information/on-request` callbacks (`app/Libraries/Abdm/M3HiuWorkflowService.php`), decrypts payloads, and stores documents in `abdm_hiu_documents` (`app/Libraries/Abdm/M3HiuDocumentRepository.php`). |
|
||||
| **Honor consent expiry and revocation** | Ready | Enforced in `Patient::abdm_documents` and `AbdmGateway::consentRevokedCallback`: when consent transitions to `REVOKED`, `DENIED`, or passes `dataEraseAt` (`EXPIRED`), cached HIU documents are automatically purged (`purgeAbdmDocumentsByRefs`) and UI access is blocked. |
|
||||
| **Display fetched records in HMS** | Ready | Clinical summary viewer, attachment viewer, and printable PDF (`Patient::abdm_records_pdf`, `app/Views/abdm/hiu_documents.php`). |
|
||||
|
||||
---
|
||||
|
||||
## 2. Exact Screens, URLs & Database Tables for Audit Evidence
|
||||
|
||||
Keep these **5 browser tabs** open during the HTC interview:
|
||||
|
||||
### Tab 1: Live ABDM API & Bridge Logs
|
||||
* **UI URL:** `/AbdmBridgeLog`
|
||||
* **Controller / View:** `app/Controllers/AbdmBridgeLog.php` | `app/Views/abdm/bridge_log.php`
|
||||
* **What to show the auditor:**
|
||||
* Filter by channel (`eatria_bridge`, `bridge`, `abdm`) or status (`success`, `error`).
|
||||
* Click **Detail** on any row to display the exact **Request JSON**, **Response JSON**, HTTP status code (`200` / `202`), and timestamp.
|
||||
* **Database Table:** `abdm_api_logs`
|
||||
|
||||
### Tab 2: FHIR R4 Bundle Inspector & Care Context Link Verifier
|
||||
* **UI URL:** `/AbdmFhirInspector`
|
||||
* **Controller / View:** `app/Controllers/AbdmFhirInspector.php` | `app/Views/abdm/fhir_inspector.php`
|
||||
* **What to show the auditor:**
|
||||
* Search any ABHA-linked patient.
|
||||
* Show all Care Context references (`OPD-...`, `PRESC-...`, `LAB-...`, `RAD-...`, `DISCHARGE-...`, `INVOICE-...`) and `LINKED` status.
|
||||
* Click **Preview FHIR Bundle** to display the NRCeS FHIR R4 `Bundle` JSON (`Composition`, `Patient`, `Practitioner`, `Organization`, `MedicationRequest`, `Observation`, etc.).
|
||||
* **Database Tables:** `health_records`, `record_links`, `abdm_link_transactions`
|
||||
|
||||
### Tab 3: ABDM HIP Task Board (Record Creation & Sync Queue)
|
||||
* **UI URL:** `/AbdmTaskBoard`
|
||||
* **Controller / View:** `app/Controllers/AbdmTaskBoard.php` | `app/Views/abdm/task_board.php`
|
||||
* **What to show the auditor:**
|
||||
* Real-time queue of OPD consultations, prescriptions, lab/radiology reports, discharge summaries, wellness, immunization, and invoices with their ABDM push and link status.
|
||||
* **Database Tables:** `abdm_work_tasks`, `abdm_sync_outbox`
|
||||
|
||||
### Tab 4: HIU Consent Request List & Fetched Documents Console
|
||||
* **UI URL 1 (Consent Request List):** `/AbdmHiu` (`app/Views/abdm/patient_request_list.php`)
|
||||
* Shows every consent request with `Status` (`REQUESTED`, `GRANTED`, `COMPLETED`, `REVOKED`, `EXPIRED`, `DENIED`), `HI Types`, `Requested On`, and `Expiry`.
|
||||
* **UI URL 2 (Fetched Documents Viewer):** `/AbdmHiu/documents` (`app/Views/abdm/hiu_documents.php`)
|
||||
* Displays decrypted health records fetched via HIU consent.
|
||||
* **UI URL 3 (Patient OPD Profile ABDM Tab):** `/billing/patient/show_profile_opd/<patient_id>`
|
||||
* Raise new consent requests, check live status, view fetched documents, or generate PDF (`/billing/patient/abdm_records_pdf/<patient_id>`).
|
||||
* **Database Tables:**
|
||||
* `abdm_hiu_workflows` (tracks M3 lifecycle states, `request_id`, `transaction_id`, `abdm_consent_request_id`, `abdm_consent_artifact_id`, `revoked_at`, `expired_at`)
|
||||
* `abdm_hiu_consent_artifacts` (stores HIE-CM consent artefacts)
|
||||
* `abdm_hiu_documents` (stores decrypted FHIR bundles)
|
||||
* `abdm_consent_records` (stores HIP-side consent records)
|
||||
|
||||
### Tab 5: Security & Compliance Audit Logs
|
||||
* **Service:** `app/Libraries/AbdmAuditService.php`
|
||||
* **Database Tables:** `audit_logs` and `abdm_audit_logs`
|
||||
* Captures `actor_user_id`, `actor_name`, `ip_address`, `user_agent`, `abha_id`, `patient_id`, `consent_id`, `transaction_id`, `action` (`push_record`, `link_record`, `consent_request`, `consent_grant`, `consent_revoke`, `record_linked`, `consent_callback`, `abha_validate`), `outcome`, and JSON details.
|
||||
* **Server File Logs:** `writable/logs/log-YYYY-MM-DD.log`
|
||||
|
||||
---
|
||||
|
||||
## 3. Step-by-Step Live Demo Script for the Auditor
|
||||
|
||||
1. **M1 — Patient Registration & ABHA Linkage:**
|
||||
* Open Patient Registration or `/AbdmOpdQueue` (Scan & Share).
|
||||
* Show a patient linked with an ABHA Number and `@sbx` ABHA Address.
|
||||
2. **M2 (HIP) — Record Creation, FHIR R4 Generation & Care Context Linking:**
|
||||
* Create/save an OPD Consultation or Prescription for the patient.
|
||||
* Open `/AbdmFhirInspector` -> select the patient -> click **Preview Bundle** to show the NRCeS FHIR R4 JSON.
|
||||
* Link the Care Context and show it appearing inside the **ABHA PHR Sandbox App** (`phrsbx.abdm.gov.in`) under `PRESC-...` / `OPD-...`.
|
||||
* Show the corresponding API call log in `/AbdmBridgeLog`.
|
||||
3. **M3 (HIU) — Consent Request, Patient Approval & Record Fetch:**
|
||||
* Open the patient's OPD Profile (or `/AbdmHiu`) and click **Request Consent**.
|
||||
* Approve the consent request in the **ABHA PHR Sandbox App**.
|
||||
* Show status changing to `GRANTED` / `COMPLETED` in `/AbdmHiu`.
|
||||
* Open the fetched records in the patient's ABDM Documents view (or `/AbdmHiu/documents`) to show the rendered clinical data.
|
||||
4. **M3 Security Control — Consent Revocation & Expiry Enforcement:**
|
||||
* In the **ABHA PHR Sandbox App**, revoke the granted consent.
|
||||
* Refresh/click the consent in HMS — show that the status updates to `REVOKED`, cached documents for that consent are purged from `abdm_hiu_documents`, and HMS blocks viewing the records.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user