From 48d19f0ff76086987c56c72ebce06e50ed111f8c Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Tue, 28 Jul 2026 16:23:47 +0530 Subject: [PATCH] ABDM: add manual Consent Request form with editable HI types + date range (Phase 2) - New Patient::abdm_content_request_custom(int \) endpoint builds a custom consent{} block (purpose, hiTypes, permission.dateRange, dataEraseAt) from user-submitted hi_types[]/date_from/date_to/purpose form fields, instead of always using M3HiuWorkflowService::buildDefaultConsent()'s hardcoded OPConsultation+DiagnosticReport / 365-day defaults. Reuses the existing requester/hiu-id resolution and hiTypes normalization already built into preparePayloadForOperation() for the consent_request operation, so no M3HiuWorkflowService changes were needed. - New route (POST) patient/abdm_content_request_custom/(:num). - New 'Custom Request' button + '#abdmCustomConsentModal' form (Patient_Profile_Opd_V.php) with HI type checkboxes (OPConsultation, DiagnosticReport, Prescription, DischargeSummary, HealthDocument, ImmunizationRecord, Wellness, Invoice), Valid From/To date inputs (defaulted to today-365d..today), and a Purpose text field. On submit, reuses the existing auto-flow polling loop (runAutoFlowStep) with the returned request_id to track status through to data fetch. - Live-verified end-to-end on patient 11 (Devender Singh): submitted a request for OPConsultation + DiagnosticReport + Prescription; sandbox granted only OPConsultation/DiagnosticReport, and the Phase 1 consent detail modal correctly showed Prescription as DENIED alongside the two GRANTED types, confirming the partial-grant per-HI-type breakdown works correctly end-to-end. --- app/Config/Routes.php | 1 + app/Controllers/Patient.php | 159 +++++++++++++++++++ app/Views/billing/Patient_Profile_Opd_V.php | 166 ++++++++++++++++++++ 3 files changed, 326 insertions(+) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index de87c0b..11cf13b 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->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'); $routes->match(['GET', 'POST'], 'patient/patient_file_upload/(:num)', 'Patient::patient_file_upload/$1'); diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index 1a4138f..48b1acf 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -1320,6 +1320,165 @@ class Patient extends BaseController ]); } + /** + * Manual consent request form submission — lets the user pick specific Health + * Information Types and an editable validity date range instead of always + * using the hardcoded defaults from M3HiuWorkflowService::buildDefaultConsent(). + * Returns the same shape as abdm_content_request() so the existing JS auto-flow + * polling (runAutoFlowStep) can pick up the request_id and continue tracking + * consent status / data fetch to completion. + */ + public function abdm_content_request_custom(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); + if ($abhaContext['abha_address'] === '') { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'ABHA Address not available for this patient.', + 'abha' => $abhaContext, + ]); + } + + if ((int) ($abhaContext['is_verified'] ?? 0) !== 1) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'ABHA is not marked as verified in HMS. Please verify ABHA first, then request content.', + 'abha' => $abhaContext, + ]); + } + + $hiTypesInput = $this->request->getPost('hi_types'); + if (! is_array($hiTypesInput)) { + $decoded = json_decode((string) $hiTypesInput, true); + $hiTypesInput = is_array($decoded) ? $decoded : []; + } + $hiTypes = $this->normalizeHiTypesList($hiTypesInput); + if ($hiTypes === []) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'Select at least one Health Information Type.', + ]); + } + + $dateFromInput = trim((string) $this->request->getPost('date_from')); + $dateToInput = trim((string) $this->request->getPost('date_to')); + $purposeInput = trim((string) $this->request->getPost('purpose')); + + $istTz = new \DateTimeZone('Asia/Kolkata'); + $utcTz = new \DateTimeZone('UTC'); + $nowIst = new \DateTimeImmutable('now', $istTz); + + try { + $fromIst = $dateFromInput !== '' ? new \DateTimeImmutable($dateFromInput, $istTz) : $nowIst->modify('-365 days'); + } catch (\Throwable $e) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'Invalid "Valid From" date.', + ]); + } + try { + $toIst = $dateToInput !== '' ? new \DateTimeImmutable($dateToInput, $istTz) : $nowIst; + } catch (\Throwable $e) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => 'Invalid "Valid To" date.', + ]); + } + if ($fromIst->getTimestamp() >= $toIst->getTimestamp()) { + return $this->response->setStatusCode(422)->setJSON([ + 'ok' => 0, + 'error' => '"Valid From" date must be earlier than "Valid To" date.', + ]); + } + + $safeNowIst = $nowIst->modify('-120 seconds'); + if ($toIst->getTimestamp() > $safeNowIst->getTimestamp()) { + $toIst = $safeNowIst; + } + + $fromUtc = $fromIst->setTime(0, 0, 0)->setTimezone($utcTz)->format('Y-m-d\TH:i:s.000\Z'); + $toUtc = $toIst->setTimezone($utcTz)->format('Y-m-d\TH:i:s.000\Z'); + $eraseAtUtc = $nowIst->modify('+365 days')->setTime(23, 59, 59)->setTimezone($utcTz)->format('Y-m-d\TH:i:s.000\Z'); + + $consent = [ + 'purpose' => [ + 'code' => 'CAREMGT', + 'text' => $purposeInput !== '' ? $purposeInput : 'Care Management', + 'refUri' => 'https://abdm.gov.in', + ], + 'patient' => [ + 'id' => $abhaContext['abha_address'], + ], + 'hiTypes' => $hiTypes, + 'permission' => [ + 'accessMode' => 'VIEW', + 'dateRange' => [ + 'from' => $fromUtc, + 'to' => $toUtc, + ], + 'dataEraseAt' => $eraseAtUtc, + 'frequency' => [ + 'unit' => 'HOUR', + 'value' => 1, + 'repeats' => 0, + ], + ], + ]; + + $service = new \App\Libraries\Abdm\M3HiuWorkflowService(); + $payload = [ + 'patient_id' => $pno, + 'abha_address' => $abhaContext['abha_address'], + 'consent' => $consent, + ]; + + try { + $result = $service->runOperation('consent_request', $payload); + } catch (\Throwable $e) { + return $this->response->setStatusCode(500)->setJSON([ + 'ok' => 0, + 'error' => 'Unable to start content request: ' . $e->getMessage(), + ]); + } + + $ok = (int) ($result['ok'] ?? 0) === 1; + $httpCode = (int) ($result['http_code'] ?? ($ok ? 200 : 422)); + if ($httpCode < 100 || $httpCode > 599) { + $httpCode = $ok ? 200 : 422; + } + + if (! $ok) { + return $this->response->setStatusCode($httpCode)->setJSON([ + 'ok' => 0, + 'error' => (string) ($result['error_text'] ?? 'Content request failed.'), + 'data' => $result, + ]); + } + + return $this->response->setStatusCode($httpCode)->setJSON([ + 'ok' => 1, + 'message' => 'Consent/content request created successfully.', + 'workflow_state' => 'REQUESTED', + 'request_id' => (string) ($result['request_id'] ?? ''), + 'data' => $result, + ]); + } + public function abdm_content_auto_flow(int $pno) { if (! $this->db->tableExists('patient_master')) { diff --git a/app/Views/billing/Patient_Profile_Opd_V.php b/app/Views/billing/Patient_Profile_Opd_V.php index ea5b6d2..2cece43 100644 --- a/app/Views/billing/Patient_Profile_Opd_V.php +++ b/app/Views/billing/Patient_Profile_Opd_V.php @@ -316,6 +316,9 @@ if ($patientPhotoPath === '') { Fetch ABDM Records + + + + + + +