fix(abdm): enforce granted hi_types and dateRange, check live consent status before fetch, safe json parsing
This commit is contained in:
parent
7f6ae881e7
commit
e41e6bd96f
|
|
@ -5296,6 +5296,91 @@ class AbdmGateway extends BaseController
|
||||||
$db = \Config\Database::connect();
|
$db = \Config\Database::connect();
|
||||||
$records = [];
|
$records = [];
|
||||||
|
|
||||||
|
// Parse granted HI types and date range filters (from ABDM consent)
|
||||||
|
$rawHiTypes = $body['hiTypes'] ?? $body['hi_types'] ?? null;
|
||||||
|
if (is_string($rawHiTypes)) {
|
||||||
|
$rawHiTypes = [$rawHiTypes];
|
||||||
|
}
|
||||||
|
$grantedHiTypes = null;
|
||||||
|
$normMap = [
|
||||||
|
'opconsultrecord' => 'OPConsultation',
|
||||||
|
'opconsultation' => 'OPConsultation',
|
||||||
|
'diagnosticreportrecord' => 'DiagnosticReport',
|
||||||
|
'diagnosticreport' => 'DiagnosticReport',
|
||||||
|
'prescriptionrecord' => 'Prescription',
|
||||||
|
'prescription' => 'Prescription',
|
||||||
|
'dischargesummaryrecord' => 'DischargeSummary',
|
||||||
|
'dischargesummary' => 'DischargeSummary',
|
||||||
|
'healthdocumentrecord' => 'HealthDocumentRecord',
|
||||||
|
'healthdocument' => 'HealthDocumentRecord',
|
||||||
|
'immunizationrecord' => 'ImmunizationRecord',
|
||||||
|
'immunization' => 'ImmunizationRecord',
|
||||||
|
'wellnessrecord' => 'WellnessRecord',
|
||||||
|
'wellness' => 'WellnessRecord',
|
||||||
|
'invoicerecord' => 'Invoice',
|
||||||
|
'invoice' => 'Invoice',
|
||||||
|
];
|
||||||
|
if (is_array($rawHiTypes) && ! empty($rawHiTypes)) {
|
||||||
|
$grantedHiTypes = [];
|
||||||
|
foreach ($rawHiTypes as $ht) {
|
||||||
|
$k = strtolower(trim((string) $ht));
|
||||||
|
$grantedHiTypes[] = $normMap[$k] ?? trim((string) $ht);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateRange = $body['dateRange'] ?? $body['date_range'] ?? null;
|
||||||
|
$dateFrom = null;
|
||||||
|
$dateTo = null;
|
||||||
|
if (is_array($dateRange)) {
|
||||||
|
if (! empty($dateRange['from'])) {
|
||||||
|
$dateFrom = substr(trim((string) $dateRange['from']), 0, 10);
|
||||||
|
}
|
||||||
|
if (! empty($dateRange['to'])) {
|
||||||
|
$dateTo = substr(trim((string) $dateRange['to']), 0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$isTypeAllowed = static function (string $type) use ($grantedHiTypes, $normMap): bool {
|
||||||
|
if ($grantedHiTypes === null || empty($grantedHiTypes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$std = $normMap[strtolower(trim($type))] ?? trim($type);
|
||||||
|
return in_array($std, $grantedHiTypes, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$isDateInRange = static function (?string $dateStr) use ($dateFrom, $dateTo): bool {
|
||||||
|
if ($dateStr === null || trim($dateStr) === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$d = substr(trim($dateStr), 0, 10);
|
||||||
|
if ($dateFrom !== null && $d < $dateFrom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($dateTo !== null && $d > $dateTo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$extractBundleDate = static function (array $bundle): ?string {
|
||||||
|
if (! empty($bundle['timestamp'])) {
|
||||||
|
return substr((string) $bundle['timestamp'], 0, 10);
|
||||||
|
}
|
||||||
|
foreach ($bundle['entry'] ?? [] as $e) {
|
||||||
|
$res = $e['resource'] ?? [];
|
||||||
|
if (! empty($res['date'])) {
|
||||||
|
return substr((string) $res['date'], 0, 10);
|
||||||
|
}
|
||||||
|
if (! empty($res['effectiveDateTime'])) {
|
||||||
|
return substr((string) $res['effectiveDateTime'], 0, 10);
|
||||||
|
}
|
||||||
|
if (! empty($res['authoredOn'])) {
|
||||||
|
return substr((string) $res['authoredOn'], 0, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
foreach ($careContextRefs as $ref) {
|
foreach ($careContextRefs as $ref) {
|
||||||
$ref = trim((string) $ref);
|
$ref = trim((string) $ref);
|
||||||
if ($ref === '') {
|
if ($ref === '') {
|
||||||
|
|
@ -5304,9 +5389,16 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0a: Live On-Demand Fresh Generation for LAB / Diagnostic Report (LAB-xxxx or RAD-xxxx)
|
// Strategy 0a: Live On-Demand Fresh Generation for LAB / Diagnostic Report (LAB-xxxx or RAD-xxxx)
|
||||||
if (preg_match('/^(?:LAB|RAD)-/i', $ref)) {
|
if (preg_match('/^(?:LAB|RAD)-/i', $ref)) {
|
||||||
|
if (! $isTypeAllowed('DiagnosticReport')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$diag = $this->buildDiagnosticReportBundle($ref);
|
$diag = $this->buildDiagnosticReportBundle($ref);
|
||||||
if ($diag !== null && !empty($diag['bundle'])) {
|
if ($diag !== null && ! empty($diag['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($diag['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => $diag['hiType'] ?? 'DiagnosticReportRecord',
|
'hiType' => $diag['hiType'] ?? 'DiagnosticReportRecord',
|
||||||
|
|
@ -5323,9 +5415,16 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0b: Live On-Demand Fresh Generation for Discharge Summary (DISCHARGE-xxxx or IPD-xxxx)
|
// Strategy 0b: Live On-Demand Fresh Generation for Discharge Summary (DISCHARGE-xxxx or IPD-xxxx)
|
||||||
if (preg_match('/^(?:DISCHARGE|IPD)-/i', $ref)) {
|
if (preg_match('/^(?:DISCHARGE|IPD)-/i', $ref)) {
|
||||||
|
if (! $isTypeAllowed('DischargeSummary')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$discharge = $this->buildDischargeSummaryBundle($ref);
|
$discharge = $this->buildDischargeSummaryBundle($ref);
|
||||||
if ($discharge !== null && !empty($discharge['bundle'])) {
|
if ($discharge !== null && ! empty($discharge['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($discharge['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => $discharge['hiType'] ?? 'DischargeSummaryRecord',
|
'hiType' => $discharge['hiType'] ?? 'DischargeSummaryRecord',
|
||||||
|
|
@ -5342,9 +5441,19 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0c: Live On-Demand Fresh Generation for OPD Consult & Prescription Records
|
// Strategy 0c: Live On-Demand Fresh Generation for OPD Consult & Prescription Records
|
||||||
if (preg_match('/^(OPD|PRESCRIPTION|PRESC|RX)-(\d+)(?:-S(\d+))?/i', $ref, $m)) {
|
if (preg_match('/^(OPD|PRESCRIPTION|PRESC|RX)-(\d+)(?:-S(\d+))?/i', $ref, $m)) {
|
||||||
|
$refPrefix = strtoupper($m[1]);
|
||||||
|
$isPresc = in_array($refPrefix, ['PRESC', 'PRESCRIPTION', 'RX'], true);
|
||||||
|
$reqType = $isPresc ? 'Prescription' : 'OPConsultation';
|
||||||
|
if (! $isTypeAllowed($reqType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$opd = $this->buildOpConsultationBundle($ref);
|
$opd = $this->buildOpConsultationBundle($ref);
|
||||||
if ($opd !== null && !empty($opd['bundle'])) {
|
if ($opd !== null && ! empty($opd['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($opd['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => $opd['hiType'] ?? 'OPConsultRecord',
|
'hiType' => $opd['hiType'] ?? 'OPConsultRecord',
|
||||||
|
|
@ -5361,6 +5470,9 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0b-reg: Live On-Demand Fresh Generation for Patient Registration / File (REG-xxxx)
|
// Strategy 0b-reg: Live On-Demand Fresh Generation for Patient Registration / File (REG-xxxx)
|
||||||
if (preg_match('/^REG-(.+)$/i', $ref, $regM)) {
|
if (preg_match('/^REG-(.+)$/i', $ref, $regM)) {
|
||||||
|
if (! $isTypeAllowed('HealthDocumentRecord')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$pRef = trim($regM[1]);
|
$pRef = trim($regM[1]);
|
||||||
$patientRow = null;
|
$patientRow = null;
|
||||||
if ($db->tableExists('patient_master')) {
|
if ($db->tableExists('patient_master')) {
|
||||||
|
|
@ -5378,12 +5490,16 @@ class AbdmGateway extends BaseController
|
||||||
$abhaId = $this->resolvePatientAbhaIdentifier($patId);
|
$abhaId = $this->resolvePatientAbhaIdentifier($patId);
|
||||||
$patientResource = $this->buildAbdmPatientResource($patientRow, $patId, $abhaId);
|
$patientResource = $this->buildAbdmPatientResource($patientRow, $patId, $abhaId);
|
||||||
$docTitle = 'Patient File - ' . $this->patientDisplayName($patientRow);
|
$docTitle = 'Patient File - ' . $this->patientDisplayName($patientRow);
|
||||||
|
$regDate = $patientRow['created_at'] ?? $patientRow['insert_date'] ?? date('Y-m-d');
|
||||||
|
if (! $isDateInRange($regDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$html = '<div xmlns="http://www.w3.org/1999/xhtml"><h3>' . esc($docTitle) . '</h3>'
|
$html = '<div xmlns="http://www.w3.org/1999/xhtml"><h3>' . esc($docTitle) . '</h3>'
|
||||||
. '<p><strong>Patient Name:</strong> ' . esc($this->patientDisplayName($patientRow)) . '</p>'
|
. '<p><strong>Patient Name:</strong> ' . esc($this->patientDisplayName($patientRow)) . '</p>'
|
||||||
. '<p><strong>Patient Code:</strong> ' . esc($patientRow['p_code'] ?? '') . '</p>'
|
. '<p><strong>Patient Code:</strong> ' . esc($patientRow['p_code'] ?? '') . '</p>'
|
||||||
. '<p><strong>Gender:</strong> ' . esc($patientRow['gender'] ?? '') . '</p>'
|
. '<p><strong>Gender:</strong> ' . esc($patientRow['gender'] ?? '') . '</p>'
|
||||||
. '<p><strong>Age:</strong> ' . esc($patientRow['age'] ?? '') . '</p>'
|
. '<p><strong>Age:</strong> ' . esc($patientRow['age'] ?? '') . '</p>'
|
||||||
. '<p><strong>Registration Date:</strong> ' . esc($patientRow['created_at'] ?? $patientRow['insert_date'] ?? date('Y-m-d')) . '</p>'
|
. '<p><strong>Registration Date:</strong> ' . esc($regDate) . '</p>'
|
||||||
. '</div>';
|
. '</div>';
|
||||||
$bundle = $this->buildSimpleHealthDocumentBundle($patientResource, $docTitle, 'text/html', base64_encode($html));
|
$bundle = $this->buildSimpleHealthDocumentBundle($patientResource, $docTitle, 'text/html', base64_encode($html));
|
||||||
$records[] = [
|
$records[] = [
|
||||||
|
|
@ -5399,6 +5515,9 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0b: Live On-Demand Fresh Generation for Invoice Record
|
// Strategy 0b: Live On-Demand Fresh Generation for Invoice Record
|
||||||
if (preg_match('/^INVOICE-(CHG|OPD|IPD)-(\d+)/i', $ref, $invM)) {
|
if (preg_match('/^INVOICE-(CHG|OPD|IPD)-(\d+)/i', $ref, $invM)) {
|
||||||
|
if (! $isTypeAllowed('Invoice')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$invSource = match(strtoupper($invM[1])) {
|
$invSource = match(strtoupper($invM[1])) {
|
||||||
'CHG' => 'charges_invoice',
|
'CHG' => 'charges_invoice',
|
||||||
'OPD' => 'opd_invoice',
|
'OPD' => 'opd_invoice',
|
||||||
|
|
@ -5407,7 +5526,11 @@ class AbdmGateway extends BaseController
|
||||||
$invId = (int) $invM[2];
|
$invId = (int) $invM[2];
|
||||||
try {
|
try {
|
||||||
$invPayload = $this->buildInvoiceSourceRecordPayload($invSource, $invId, 0, '');
|
$invPayload = $this->buildInvoiceSourceRecordPayload($invSource, $invId, 0, '');
|
||||||
if ($invPayload !== null && !empty($invPayload['bundle'])) {
|
if ($invPayload !== null && ! empty($invPayload['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($invPayload['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => 'InvoiceRecord',
|
'hiType' => 'InvoiceRecord',
|
||||||
|
|
@ -5423,6 +5546,9 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0c: Live Retrieval & On-Demand Fresh Generation for Wellness Record
|
// Strategy 0c: Live Retrieval & On-Demand Fresh Generation for Wellness Record
|
||||||
if (str_starts_with(strtoupper($ref), 'WELLNESS-')) {
|
if (str_starts_with(strtoupper($ref), 'WELLNESS-')) {
|
||||||
|
if (! $isTypeAllowed('WellnessRecord')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$foundBundle = null;
|
$foundBundle = null;
|
||||||
$foundDisplay = null;
|
$foundDisplay = null;
|
||||||
|
|
||||||
|
|
@ -5518,6 +5644,10 @@ class AbdmGateway extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($foundBundle !== null && ! empty($foundBundle)) {
|
if ($foundBundle !== null && ! empty($foundBundle)) {
|
||||||
|
$bDate = $extractBundleDate($foundBundle);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => 'WellnessRecord',
|
'hiType' => 'WellnessRecord',
|
||||||
|
|
@ -5530,10 +5660,17 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0d: Live On-Demand Fresh Generation for Health Document Record (DOC-file-{id}, DOC-{id})
|
// Strategy 0d: Live On-Demand Fresh Generation for Health Document Record (DOC-file-{id}, DOC-{id})
|
||||||
if (preg_match('/^DOC-(?:file-)?(\d+)/i', $ref, $docM)) {
|
if (preg_match('/^DOC-(?:file-)?(\d+)/i', $ref, $docM)) {
|
||||||
|
if (! $isTypeAllowed('HealthDocumentRecord')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$docTargetId = (int) $docM[1];
|
$docTargetId = (int) $docM[1];
|
||||||
try {
|
try {
|
||||||
$docPayload = $this->buildHealthDocumentRecordPayload(0, '', $docTargetId);
|
$docPayload = $this->buildHealthDocumentRecordPayload(0, '', $docTargetId);
|
||||||
if ($docPayload !== null && !empty($docPayload['bundle'])) {
|
if ($docPayload !== null && ! empty($docPayload['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($docPayload['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => 'HealthDocumentRecord',
|
'hiType' => 'HealthDocumentRecord',
|
||||||
|
|
@ -5563,7 +5700,7 @@ class AbdmGateway extends BaseController
|
||||||
'push_status' => 'linked',
|
'push_status' => 'linked',
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -5575,11 +5712,18 @@ class AbdmGateway extends BaseController
|
||||||
|
|
||||||
// Strategy 0e: Live On-Demand Fresh Generation for Immunization Record (IMM-{id})
|
// Strategy 0e: Live On-Demand Fresh Generation for Immunization Record (IMM-{id})
|
||||||
if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $immM)) {
|
if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $immM)) {
|
||||||
|
if (! $isTypeAllowed('ImmunizationRecord')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$immTargetId = (int) (! empty($immM[3]) ? $immM[3] : (! empty($immM[2]) ? $immM[2] : ($immM[1] ?? 0)));
|
$immTargetId = (int) (! empty($immM[3]) ? $immM[3] : (! empty($immM[2]) ? $immM[2] : ($immM[1] ?? 0)));
|
||||||
$immPatientId = (int) (! empty($immM[1]) ? $immM[1] : (! empty($immM[2]) ? $immM[2] : 0));
|
$immPatientId = (int) (! empty($immM[1]) ? $immM[1] : (! empty($immM[2]) ? $immM[2] : 0));
|
||||||
try {
|
try {
|
||||||
$immPayload = $this->buildImmunizationGatewayPayload($immPatientId, $immTargetId, '', false);
|
$immPayload = $this->buildImmunizationGatewayPayload($immPatientId, $immTargetId, '', false);
|
||||||
if ($immPayload !== null && ! empty($immPayload['bundle'])) {
|
if ($immPayload !== null && ! empty($immPayload['bundle'])) {
|
||||||
|
$bDate = $extractBundleDate($immPayload['bundle']);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$vaccineName = 'Vaccine';
|
$vaccineName = 'Vaccine';
|
||||||
if (! empty($immPayload['records'][0]['vaccine_name'])) {
|
if (! empty($immPayload['records'][0]['vaccine_name'])) {
|
||||||
$vaccineName = trim((string) $immPayload['records'][0]['vaccine_name']);
|
$vaccineName = trim((string) $immPayload['records'][0]['vaccine_name']);
|
||||||
|
|
@ -5645,7 +5789,7 @@ class AbdmGateway extends BaseController
|
||||||
$hrRow = $hrBuilder->get(1)->getRowArray();
|
$hrRow = $hrBuilder->get(1)->getRowArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hrRow && !empty($hrRow['record_data'])) {
|
if ($hrRow && ! empty($hrRow['record_data'])) {
|
||||||
$bundleData = json_decode((string) $hrRow['record_data'], true);
|
$bundleData = json_decode((string) $hrRow['record_data'], true);
|
||||||
if (is_array($bundleData)) {
|
if (is_array($bundleData)) {
|
||||||
$recHiType = $hrRow['hi_type'] ?? $hrRow['record_type'] ?? 'OPConsultRecord';
|
$recHiType = $hrRow['hi_type'] ?? $hrRow['record_type'] ?? 'OPConsultRecord';
|
||||||
|
|
@ -5654,6 +5798,13 @@ class AbdmGateway extends BaseController
|
||||||
} elseif (str_starts_with($ref, 'OPD-')) {
|
} elseif (str_starts_with($ref, 'OPD-')) {
|
||||||
$recHiType = 'OPConsultRecord';
|
$recHiType = 'OPConsultRecord';
|
||||||
}
|
}
|
||||||
|
if (! $isTypeAllowed($recHiType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$bDate = $extractBundleDate($bundleData);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$defaultDisp = match ($recHiType) {
|
$defaultDisp = match ($recHiType) {
|
||||||
'ImmunizationRecord' => 'Immunization Record',
|
'ImmunizationRecord' => 'Immunization Record',
|
||||||
'DiagnosticReportRecord' => 'Diagnostic Report',
|
'DiagnosticReportRecord' => 'Diagnostic Report',
|
||||||
|
|
@ -5683,15 +5834,23 @@ class AbdmGateway extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hrRow && !empty($hrRow['fhir_bundle_enc'])) {
|
if ($hrRow && ! empty($hrRow['fhir_bundle_enc'])) {
|
||||||
try {
|
try {
|
||||||
$enc = new FhirEncryptionService();
|
$enc = new FhirEncryptionService();
|
||||||
$decrypted = $enc->decrypt((string) $hrRow['fhir_bundle_enc']);
|
$decrypted = $enc->decrypt((string) $hrRow['fhir_bundle_enc']);
|
||||||
$bundleData = json_decode($decrypted, true);
|
$bundleData = json_decode($decrypted, true);
|
||||||
if (is_array($bundleData)) {
|
if (is_array($bundleData)) {
|
||||||
|
$recHiType = $hrRow['hi_type'] ?? $hrRow['record_type'] ?? 'OPConsultRecord';
|
||||||
|
if (! $isTypeAllowed($recHiType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$bDate = $extractBundleDate($bundleData);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => $hrRow['hi_type'] ?? $hrRow['record_type'] ?? 'OPConsultRecord',
|
'hiType' => $recHiType,
|
||||||
'display' => $hrRow['care_context_display'] ?? 'Consultation Record',
|
'display' => $hrRow['care_context_display'] ?? 'Consultation Record',
|
||||||
'bundle' => $bundleData,
|
'bundle' => $bundleData,
|
||||||
];
|
];
|
||||||
|
|
@ -5701,14 +5860,21 @@ class AbdmGateway extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Strategy B: Build compliant FHIR Document Bundle from patient_master & OPD
|
// Strategy B: Build compliant FHIR Document Bundle from patient_master & OPD
|
||||||
// STRICTLY restricted to OPD/consultation care contexts.
|
// STRICTLY restricted to OPD/consultation care contexts.
|
||||||
// NEVER synthesize dummy consultation records for invoices, discharge, labs, radiology, etc.
|
// NEVER synthesize dummy consultation records for invoices, discharge, labs, radiology, etc.
|
||||||
if (preg_match('/^(?:OPD|PRESCRIPTION|PRESC|CONSULT)/i', $ref)) {
|
if (preg_match('/^(?:OPD|PRESCRIPTION|PRESC|CONSULT)/i', $ref)) {
|
||||||
|
$isPresc = (bool) preg_match('/^(?:PRESC|PRESCRIPTION)/i', $ref);
|
||||||
|
$reqType = $isPresc ? 'Prescription' : 'OPConsultation';
|
||||||
|
if (! $isTypeAllowed($reqType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$bundle = $this->assembleFhirBundleForCareContext($ref);
|
$bundle = $this->assembleFhirBundleForCareContext($ref);
|
||||||
if ($bundle !== null) {
|
if ($bundle !== null) {
|
||||||
$isPresc = (bool) preg_match('/^(?:PRESC|PRESCRIPTION)/i', $ref);
|
$bDate = $extractBundleDate($bundle);
|
||||||
|
if (! $isDateInRange($bDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$records[] = [
|
$records[] = [
|
||||||
'careContextReference' => $ref,
|
'careContextReference' => $ref,
|
||||||
'hiType' => $isPresc ? 'PrescriptionRecord' : 'OPConsultRecord',
|
'hiType' => $isPresc ? 'PrescriptionRecord' : 'OPConsultRecord',
|
||||||
|
|
|
||||||
|
|
@ -2017,36 +2017,37 @@ class Patient extends BaseController
|
||||||
// documents from BOTH external and same-entity facilities are returned.
|
// documents from BOTH external and same-entity facilities are returned.
|
||||||
$sessionIds = $this->resolveAllRelatedConsentIds($filterConsentRequestId);
|
$sessionIds = $this->resolveAllRelatedConsentIds($filterConsentRequestId);
|
||||||
|
|
||||||
// ABDM M3 HIU compliance: check if this consent request is expired
|
// ABDM M3 HIU compliance: check if this consent request is expired, revoked, or denied
|
||||||
$wfCheck = $this->db->table('abdm_hiu_workflows')
|
$wfCheckQuery = $this->db->table('abdm_hiu_workflows')
|
||||||
->select(['id', 'workflow_state', 'request_json', 'response_json', 'expired_at'])
|
->select(['id', 'workflow_state', 'request_json', 'expired_at'])
|
||||||
->groupStart()
|
->groupStart()
|
||||||
->where('request_id', $filterConsentRequestId)
|
->where('request_id', $filterConsentRequestId)
|
||||||
->orWhere('consent_id', $filterConsentRequestId)
|
->orWhere('consent_id', $filterConsentRequestId)
|
||||||
->orWhereIn('abdm_consent_request_id', $sessionIds !== [] ? $sessionIds : [$filterConsentRequestId])
|
->orWhereIn('abdm_consent_request_id', $sessionIds !== [] ? $sessionIds : [$filterConsentRequestId])
|
||||||
->orWhereIn('abdm_consent_artifact_id', $sessionIds !== [] ? $sessionIds : [$filterConsentRequestId])
|
->orWhereIn('abdm_consent_artifact_id', $sessionIds !== [] ? $sessionIds : [$filterConsentRequestId]);
|
||||||
->groupEnd()
|
if (ctype_digit($filterConsentRequestId)) {
|
||||||
|
$wfCheckQuery->orWhere('id', (int) $filterConsentRequestId);
|
||||||
|
}
|
||||||
|
$wfCheck = $wfCheckQuery->groupEnd()
|
||||||
->orderBy('id', 'DESC')
|
->orderBy('id', 'DESC')
|
||||||
->get(1)
|
->get(1)
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|
||||||
$isFilterExpired = false;
|
$wfState = is_array($wfCheck) ? strtoupper(trim((string) ($wfCheck['workflow_state'] ?? ''))) : '';
|
||||||
if (is_array($wfCheck)) {
|
$isFilterTerminal = in_array($wfState, ['EXPIRED', 'DENIED', 'REVOKED'], true);
|
||||||
if (strtoupper(trim((string) ($wfCheck['workflow_state'] ?? ''))) === 'EXPIRED') {
|
if (! $isFilterTerminal && is_array($wfCheck)) {
|
||||||
$isFilterExpired = true;
|
$filterEraseAt = $this->extractDataEraseAtFromWorkflowRow($wfCheck);
|
||||||
} else {
|
if ($filterEraseAt !== '') {
|
||||||
$filterEraseAt = $this->extractDataEraseAtFromWorkflowRow($wfCheck);
|
$filterTs = strtotime($filterEraseAt);
|
||||||
if ($filterEraseAt !== '') {
|
if ($filterTs !== false && $filterTs <= time()) {
|
||||||
$filterTs = strtotime($filterEraseAt);
|
$isFilterTerminal = true;
|
||||||
if ($filterTs !== false && $filterTs <= time()) {
|
$wfState = 'EXPIRED';
|
||||||
$isFilterExpired = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isFilterExpired) {
|
if ($isFilterTerminal) {
|
||||||
// Purge cached records for this expired consent as per ABDM dataEraseAt guidelines
|
// Purge cached records for this expired/revoked/denied consent as per ABDM dataEraseAt guidelines
|
||||||
$purgeRefs = array_unique(array_merge([$filterConsentRequestId], $sessionIds));
|
$purgeRefs = array_unique(array_merge([$filterConsentRequestId], $sessionIds));
|
||||||
$this->purgeAbdmDocumentsByRefs($purgeRefs);
|
$this->purgeAbdmDocumentsByRefs($purgeRefs);
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
|
|
@ -2054,7 +2055,8 @@ class Patient extends BaseController
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'items' => [],
|
'items' => [],
|
||||||
'expired' => 1,
|
'expired' => 1,
|
||||||
'error' => 'This consent request has expired. Health records are erased and no longer accessible as per ABDM guidelines.',
|
'status' => $wfState !== '' ? $wfState : 'EXPIRED',
|
||||||
|
'error' => 'This consent request is ' . ($wfState !== '' ? $wfState : 'EXPIRED') . '. Health records are erased and no longer accessible as per ABDM guidelines.',
|
||||||
'abha' => $abhaContext,
|
'abha' => $abhaContext,
|
||||||
'last_sync' => $lastSync,
|
'last_sync' => $lastSync,
|
||||||
]);
|
]);
|
||||||
|
|
@ -2216,24 +2218,28 @@ class Patient extends BaseController
|
||||||
$docFields = $this->db->getFieldNames('abdm_hiu_documents') ?? [];
|
$docFields = $this->db->getFieldNames('abdm_hiu_documents') ?? [];
|
||||||
$sessionIds = $this->resolveAllRelatedConsentIds($consentRequestId);
|
$sessionIds = $this->resolveAllRelatedConsentIds($consentRequestId);
|
||||||
|
|
||||||
// ABDM M3 HIU compliance: check if consent has expired
|
// ABDM M3 HIU compliance: check if consent has expired, been denied, or revoked
|
||||||
$wfCheck = $this->db->table('abdm_hiu_workflows')
|
$wfCheckQuery = $this->db->table('abdm_hiu_workflows')
|
||||||
->select(['id', 'workflow_state', 'request_json', 'response_json', 'expired_at'])
|
->select(['id', 'workflow_state', 'request_json', 'expired_at'])
|
||||||
->groupStart()
|
->groupStart()
|
||||||
->where('request_id', $consentRequestId)
|
->where('request_id', $consentRequestId)
|
||||||
->orWhere('consent_id', $consentRequestId)
|
->orWhere('consent_id', $consentRequestId)
|
||||||
->orWhereIn('abdm_consent_request_id', $sessionIds !== [] ? $sessionIds : [$consentRequestId])
|
->orWhereIn('abdm_consent_request_id', $sessionIds !== [] ? $sessionIds : [$consentRequestId])
|
||||||
->orWhereIn('abdm_consent_artifact_id', $sessionIds !== [] ? $sessionIds : [$consentRequestId])
|
->orWhereIn('abdm_consent_artifact_id', $sessionIds !== [] ? $sessionIds : [$consentRequestId]);
|
||||||
->groupEnd()
|
if (ctype_digit($consentRequestId)) {
|
||||||
|
$wfCheckQuery->orWhere('id', (int) $consentRequestId);
|
||||||
|
}
|
||||||
|
$wfCheck = $wfCheckQuery->groupEnd()
|
||||||
->orderBy('id', 'DESC')
|
->orderBy('id', 'DESC')
|
||||||
->get(1)
|
->get(1)
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|
||||||
if (is_array($wfCheck)) {
|
if (is_array($wfCheck)) {
|
||||||
$eraseAt = $this->extractDataEraseAtFromWorkflowRow($wfCheck);
|
$eraseAt = $this->extractDataEraseAtFromWorkflowRow($wfCheck);
|
||||||
if (strtoupper(trim((string) ($wfCheck['workflow_state'] ?? ''))) === 'EXPIRED' || ($eraseAt !== '' && strtotime($eraseAt) <= time())) {
|
$wfState = strtoupper(trim((string) ($wfCheck['workflow_state'] ?? '')));
|
||||||
|
if (in_array($wfState, ['EXPIRED', 'DENIED', 'REVOKED'], true) || ($eraseAt !== '' && strtotime($eraseAt) <= time())) {
|
||||||
$this->purgeAbdmDocumentsByRefs(array_merge([$consentRequestId], $sessionIds));
|
$this->purgeAbdmDocumentsByRefs(array_merge([$consentRequestId], $sessionIds));
|
||||||
return $this->response->setStatusCode(403)->setBody('This consent request has expired. Health records are erased and cannot be viewed or printed as per ABDM guidelines.');
|
return $this->response->setStatusCode(403)->setBody('This consent request is ' . ($wfState !== '' ? $wfState : 'EXPIRED') . '. Health records are erased and cannot be viewed or printed as per ABDM guidelines.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3098,46 +3104,95 @@ class Patient extends BaseController
|
||||||
?? ''
|
?? ''
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
||||||
|
|
||||||
if ($overrideConsentId !== '' || $overrideConsentRequestId !== '') {
|
if ($overrideConsentId !== '' || $overrideConsentRequestId !== '') {
|
||||||
$consentArtifactRef = $overrideConsentId;
|
$consentArtifactRef = $overrideConsentId;
|
||||||
$consentRequestRef = $overrideConsentRequestId;
|
$consentRequestRef = $overrideConsentRequestId;
|
||||||
$lookupTarget = $consentRequestRef !== '' ? $consentRequestRef : $consentArtifactRef;
|
$lookupTarget = $consentRequestRef !== '' ? $consentRequestRef : $consentArtifactRef;
|
||||||
|
|
||||||
// Verify whether this specific consent request is expired under ABDM dataEraseAt rules
|
// Actively reconcile live status with the bridge before fetching records
|
||||||
$checkWf = $this->db->table('abdm_hiu_workflows')
|
$reconcilePayload = [
|
||||||
->select(['id', 'workflow_state', 'request_json', 'response_json', 'expired_at'])
|
'abha_address' => $abhaContext['abha_address'],
|
||||||
|
'request_id' => $lookupTarget,
|
||||||
|
'abdm_consent_request_id' => $consentRequestRef,
|
||||||
|
'abdm_consent_artifact_id'=> $consentArtifactRef,
|
||||||
|
'consent_id' => $consentArtifactRef !== '' ? $consentArtifactRef : $lookupTarget,
|
||||||
|
];
|
||||||
|
|
||||||
|
$liveStatus = '';
|
||||||
|
try {
|
||||||
|
$reconcile = $service->runOperation('consent_reconcile', $reconcilePayload);
|
||||||
|
if (is_array($reconcile) && (int) ($reconcile['ok'] ?? 0) === 1) {
|
||||||
|
$liveStatus = strtoupper(trim((string) (
|
||||||
|
$reconcile['workflow_state']
|
||||||
|
?? $reconcile['status']
|
||||||
|
?? $reconcile['consent_status']
|
||||||
|
?? $reconcile['consent']['status']
|
||||||
|
?? $reconcile['consentDetail']['status']
|
||||||
|
?? ''
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('warning', '[abdm_content_fetch_only] consent_reconcile pre-fetch check warning: ' . $e->getMessage());
|
||||||
|
$reconcile = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check local workflow anchor row
|
||||||
|
$checkWfQuery = $this->db->table('abdm_hiu_workflows')
|
||||||
|
->select(['id', 'workflow_state', 'request_json', 'expired_at'])
|
||||||
->groupStart()
|
->groupStart()
|
||||||
->where('request_id', $lookupTarget)
|
->where('request_id', $lookupTarget)
|
||||||
->orWhere('consent_id', $lookupTarget)
|
->orWhere('consent_id', $lookupTarget)
|
||||||
->orWhere('abdm_consent_request_id', $lookupTarget)
|
->orWhere('abdm_consent_request_id', $lookupTarget)
|
||||||
->orWhere('abdm_consent_artifact_id', $lookupTarget)
|
->orWhere('abdm_consent_artifact_id', $lookupTarget);
|
||||||
->groupEnd()
|
if (ctype_digit($lookupTarget)) {
|
||||||
|
$checkWfQuery->orWhere('id', (int) $lookupTarget);
|
||||||
|
}
|
||||||
|
$checkWf = $checkWfQuery->groupEnd()
|
||||||
->orderBy('id', 'DESC')
|
->orderBy('id', 'DESC')
|
||||||
->get(1)
|
->get(1)
|
||||||
->getRowArray();
|
->getRowArray();
|
||||||
|
|
||||||
$isTargetExpired = false;
|
$anchorRowId = is_array($checkWf) ? (int) ($checkWf['id'] ?? 0) : 0;
|
||||||
|
if ($anchorRowId > 0 && ! empty($reconcile) && (int) ($reconcile['ok'] ?? 0) === 1) {
|
||||||
|
$this->persistConsentPhaseOnRequestRow($anchorRowId, $reconcile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentStatus = $liveStatus !== '' ? $liveStatus : strtoupper(trim((string) ($checkWf['workflow_state'] ?? '')));
|
||||||
|
|
||||||
|
// Verify whether this specific consent request is expired under ABDM dataEraseAt rules
|
||||||
|
$isTargetExpired = ($currentStatus === 'EXPIRED');
|
||||||
$targetEraseAt = '';
|
$targetEraseAt = '';
|
||||||
if (is_array($checkWf)) {
|
if (! $isTargetExpired && is_array($checkWf)) {
|
||||||
if (strtoupper(trim((string) ($checkWf['workflow_state'] ?? ''))) === 'EXPIRED') {
|
|
||||||
$isTargetExpired = true;
|
|
||||||
}
|
|
||||||
$targetEraseAt = $this->extractDataEraseAtFromWorkflowRow($checkWf);
|
$targetEraseAt = $this->extractDataEraseAtFromWorkflowRow($checkWf);
|
||||||
if ($targetEraseAt !== '') {
|
if ($targetEraseAt !== '') {
|
||||||
$targetTs = strtotime($targetEraseAt);
|
$targetTs = strtotime($targetEraseAt);
|
||||||
if ($targetTs !== false && $targetTs <= time()) {
|
if ($targetTs !== false && $targetTs <= time()) {
|
||||||
$isTargetExpired = true;
|
$isTargetExpired = true;
|
||||||
|
$currentStatus = 'EXPIRED';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isTargetExpired) {
|
if ($isTargetExpired || in_array($currentStatus, ['EXPIRED', 'DENIED', 'REVOKED'], true)) {
|
||||||
|
$statusWord = $currentStatus !== '' ? $currentStatus : 'EXPIRED';
|
||||||
$relatedPurgeIds = $this->resolveAllRelatedConsentIds($lookupTarget);
|
$relatedPurgeIds = $this->resolveAllRelatedConsentIds($lookupTarget);
|
||||||
$this->purgeAbdmDocumentsByRefs(array_merge([$lookupTarget], $relatedPurgeIds));
|
$this->purgeAbdmDocumentsByRefs(array_merge([$lookupTarget], $relatedPurgeIds));
|
||||||
return $this->response->setStatusCode(422)->setJSON([
|
return $this->response->setStatusCode(422)->setJSON([
|
||||||
'ok' => 0,
|
'ok' => 0,
|
||||||
'phase' => 'EXPIRED',
|
'phase' => $statusWord,
|
||||||
'error' => 'This consent request expired on ' . ($targetEraseAt ? date('d/m/Y h:i:s A', strtotime($targetEraseAt)) : 'an earlier date') . '. Under ABDM guidelines, records cannot be fetched or accessed under an expired consent. Please create a new consent request.',
|
'status' => $statusWord,
|
||||||
|
'error' => "This consent request is {$statusWord}. Under ABDM guidelines, records cannot be fetched or accessed under an {$statusWord} consent. Please create a new consent request.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array($currentStatus, ['GRANTED', 'COMPLETED', 'ACTIVE', 'APPROVED'], true)) {
|
||||||
|
return $this->response->setStatusCode(422)->setJSON([
|
||||||
|
'ok' => 0,
|
||||||
|
'phase' => $currentStatus !== '' ? $currentStatus : 'REQUESTED',
|
||||||
|
'status' => $currentStatus !== '' ? $currentStatus : 'REQUESTED',
|
||||||
|
'error' => "Consent is not granted yet (current status: {$currentStatus}). Once approved by the patient in their ABHA app, please try again.",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3469,7 +3524,35 @@ class Patient extends BaseController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->response->setJSON($this->getAbdmConsentRequestsList($abhaAddress));
|
$rawList = $this->getAbdmConsentRequestsList($abhaAddress);
|
||||||
|
$latestRequest = $rawList['requests'][0] ?? null;
|
||||||
|
if ($latestRequest && ! in_array(strtoupper(trim((string) ($latestRequest['status'] ?? ''))), ['EXPIRED', 'DENIED', 'REVOKED'], true)) {
|
||||||
|
$sessionRequestId = trim((string) ($latestRequest['request_id'] ?? ''));
|
||||||
|
$sessionConsentRequestId = trim((string) ($latestRequest['consent_request_id'] ?? ''));
|
||||||
|
$rawConsentId = trim((string) ($latestRequest['consent_id'] ?? ''));
|
||||||
|
$sessionConsentId = $rawConsentId !== '' ? trim((string) explode(':', $rawConsentId)[0]) : '';
|
||||||
|
$anchorRowId = (int) ($latestRequest['id'] ?? 0);
|
||||||
|
if ($sessionRequestId !== '' || $sessionConsentRequestId !== '' || $sessionConsentId !== '') {
|
||||||
|
try {
|
||||||
|
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
||||||
|
$reconcile = $service->runOperation('consent_reconcile', [
|
||||||
|
'abha_address' => $abhaAddress,
|
||||||
|
'request_id' => $sessionRequestId,
|
||||||
|
'abdm_consent_request_id' => $sessionConsentRequestId,
|
||||||
|
'abdm_consent_artifact_id'=> $sessionConsentId,
|
||||||
|
'consent_id' => $sessionConsentId,
|
||||||
|
]);
|
||||||
|
if ((int) ($reconcile['ok'] ?? 0) === 1 && $anchorRowId > 0) {
|
||||||
|
$this->persistConsentPhaseOnRequestRow($anchorRowId, $reconcile);
|
||||||
|
$rawList = $this->getAbdmConsentRequestsList($abhaAddress);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Silent fallback to cached list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON($rawList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -7003,6 +7086,27 @@ class Patient extends BaseController
|
||||||
|
|
||||||
$ids = [$ref];
|
$ids = [$ref];
|
||||||
|
|
||||||
|
if (ctype_digit($ref) && $this->db->tableExists('abdm_hiu_workflows')) {
|
||||||
|
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||||
|
$selCols = ['id'];
|
||||||
|
foreach (['abdm_consent_request_id', 'abdm_consent_artifact_id', 'consent_id', 'request_id', 'gateway_request_id'] as $sc) {
|
||||||
|
if (in_array($sc, $fields, true)) {
|
||||||
|
$selCols[] = $sc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$anchorRow = $this->db->table('abdm_hiu_workflows')->select(implode(', ', $selCols))->where('id', (int) $ref)->get(1)->getRowArray();
|
||||||
|
if (is_array($anchorRow)) {
|
||||||
|
foreach ($selCols as $col) {
|
||||||
|
if ($col !== 'id') {
|
||||||
|
$val = trim((string) ($anchorRow[$col] ?? ''));
|
||||||
|
if ($val !== '' && ! preg_match('/^REQ-/i', $val)) {
|
||||||
|
$ids[] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (strpos($ref, ':') !== false) {
|
if (strpos($ref, ':') !== false) {
|
||||||
$parts = explode(':', $ref);
|
$parts = explode(':', $ref);
|
||||||
foreach ($parts as $p) {
|
foreach ($parts as $p) {
|
||||||
|
|
@ -7097,8 +7201,12 @@ class Patient extends BaseController
|
||||||
*/
|
*/
|
||||||
private function extractDataEraseAtFromWorkflowRow(array $row): string
|
private function extractDataEraseAtFromWorkflowRow(array $row): string
|
||||||
{
|
{
|
||||||
|
if (! empty($row['expired_at'])) {
|
||||||
|
return (string) $row['expired_at'];
|
||||||
|
}
|
||||||
$req = json_decode((string) ($row['request_json'] ?? ''), true);
|
$req = json_decode((string) ($row['request_json'] ?? ''), true);
|
||||||
$resp = json_decode((string) ($row['response_json'] ?? ''), true);
|
$respStr = (string) ($row['response_json'] ?? '');
|
||||||
|
$resp = (strlen($respStr) > 0 && strlen($respStr) <= 65536) ? json_decode($respStr, true) : null;
|
||||||
$containers = [
|
$containers = [
|
||||||
is_array($req) ? ($req['consent'] ?? null) : null,
|
is_array($req) ? ($req['consent'] ?? null) : null,
|
||||||
is_array($req) ? ($req['consentDetail'] ?? null) : null,
|
is_array($req) ? ($req['consentDetail'] ?? null) : null,
|
||||||
|
|
|
||||||
|
|
@ -1153,6 +1153,22 @@ $(function() {
|
||||||
var fetchOnlyRunningIdx = -1;
|
var fetchOnlyRunningIdx = -1;
|
||||||
var currentAbdmFetchModalIdx = -1;
|
var currentAbdmFetchModalIdx = -1;
|
||||||
|
|
||||||
|
function safeParseJson(resp) {
|
||||||
|
return resp.text().then(function(text) {
|
||||||
|
var data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
var cleanText = (text || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||||
|
throw new Error('Server returned an unexpected response (HTTP ' + resp.status + '): ' + (cleanText ? cleanText.substring(0, 150) : 'Empty response'));
|
||||||
|
}
|
||||||
|
if (!resp.ok && (!data || data.ok !== 1)) {
|
||||||
|
throw new Error((data && data.error) || ('Server returned HTTP ' + resp.status));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function consentStatusBadgeClass(status) {
|
function consentStatusBadgeClass(status) {
|
||||||
switch ((status || '').toString().toUpperCase()) {
|
switch ((status || '').toString().toUpperCase()) {
|
||||||
case 'GRANTED': return 'bg-info-subtle text-info border border-info-subtle';
|
case 'GRANTED': return 'bg-info-subtle text-info border border-info-subtle';
|
||||||
|
|
@ -1276,8 +1292,6 @@ $(function() {
|
||||||
$('#btnNewAbdmRequest').prop('disabled', autoFlowRunning || fetchOnlyRunningIdx !== -1);
|
$('#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) {
|
function showAbdmConsentDetailForIndex(idx) {
|
||||||
var consent = currentAbdmRequests[idx];
|
var consent = currentAbdmRequests[idx];
|
||||||
$('#abdmConsentDetailBody').html(renderAbdmConsentDetail(consent));
|
$('#abdmConsentDetailBody').html(renderAbdmConsentDetail(consent));
|
||||||
|
|
@ -1337,7 +1351,7 @@ $(function() {
|
||||||
$btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Refreshing...');
|
$btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Refreshing...');
|
||||||
|
|
||||||
fetch(abdmConsentRequestsUrl, { credentials: 'same-origin' })
|
fetch(abdmConsentRequestsUrl, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Unable to load consent request history.');
|
throw new Error((data && data.error) || 'Unable to load consent request history.');
|
||||||
|
|
@ -1355,7 +1369,7 @@ $(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-pulls data for a specific (already GRANTED/COMPLETED) request row using
|
// Re-pulls data for a specific (already GRANTED/COMPLETED) request row using
|
||||||
// its saved consent reference, without creating a brand-new consent request.
|
// its saved consent reference, verifying consent status first.
|
||||||
function runFetchRecordsForRow(idx) {
|
function runFetchRecordsForRow(idx) {
|
||||||
if (autoFlowRunning || fetchOnlyRunningIdx !== -1) {
|
if (autoFlowRunning || fetchOnlyRunningIdx !== -1) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1374,21 +1388,22 @@ $(function() {
|
||||||
isExpired = true;
|
isExpired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isExpired) {
|
if (isExpired || status === 'DENIED' || status === 'REVOKED') {
|
||||||
setAbdmStatus('This consent request has expired. Under ABDM guidelines, records cannot be fetched or accessed under an expired consent.', true);
|
setAbdmStatus('This consent request is ' + (status || 'EXPIRED') + '. Under ABDM guidelines, records cannot be fetched or accessed.', true);
|
||||||
|
loadAbdmConsentRequests();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchOnlyRunningIdx = idx;
|
fetchOnlyRunningIdx = idx;
|
||||||
currentAbdmFetchModalIdx = idx;
|
currentAbdmFetchModalIdx = idx;
|
||||||
applyNewRequestButtonState();
|
applyNewRequestButtonState();
|
||||||
setAbdmStatus('Fetching latest records using existing granted consent...', false);
|
setAbdmStatus('Verifying consent status and fetching latest records...', false);
|
||||||
openAbdmFetchResultModal();
|
openAbdmFetchResultModal();
|
||||||
$('#abdmFetchResultModalBody').html('<div class="text-muted small text-center">Fetching latest records...</div>');
|
$('#abdmFetchResultModalBody').html('<div class="text-muted small text-center p-4"><div class="spinner-border spinner-border-sm me-2 text-primary" role="status"></div>Verifying consent status & fetching records...</div>');
|
||||||
|
|
||||||
var url = abdmFetchOnlyUrl + '?consent_id=' + encodeURIComponent(consent.consent_id || '') + '&consent_request_id=' + encodeURIComponent(consent.consent_request_id || '');
|
var url = abdmFetchOnlyUrl + '?consent_id=' + encodeURIComponent(consent.consent_id || '') + '&consent_request_id=' + encodeURIComponent(consent.consent_request_id || '');
|
||||||
fetch(url, { credentials: 'same-origin' })
|
fetch(url, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Fetch failed.');
|
throw new Error((data && data.error) || 'Fetch failed.');
|
||||||
|
|
@ -1404,9 +1419,10 @@ $(function() {
|
||||||
fetchOnlyRunningIdx = -1;
|
fetchOnlyRunningIdx = -1;
|
||||||
applyNewRequestButtonState();
|
applyNewRequestButtonState();
|
||||||
setAbdmStatus('Fetch failed: ' + (err.message || err), true);
|
setAbdmStatus('Fetch failed: ' + (err.message || err), true);
|
||||||
// Even if the fetch call itself failed, still show whatever records
|
// Immediately refresh table so that if consent is expired/denied/revoked,
|
||||||
// are already saved locally for this consent request.
|
// the row badge turns EXPIRED/DENIED and the "Show Data" button is removed!
|
||||||
loadAbdmFetchResultModalData(consent);
|
loadAbdmConsentRequests();
|
||||||
|
$('#abdmFetchResultModalBody').html('<div class="alert alert-warning small text-center m-3">' + escHtml(err.message || err) + '</div>');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1428,9 +1444,9 @@ $(function() {
|
||||||
isExpired = true;
|
isExpired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isExpired) {
|
if (isExpired || status === 'DENIED' || status === 'REVOKED') {
|
||||||
$('#abdmPrintFetchedRecordsBtn').hide();
|
$('#abdmPrintFetchedRecordsBtn').hide();
|
||||||
$('#abdmFetchResultModalBody').html('<div class="text-muted small text-center p-3">This consent request expired on <strong>' + escHtml(fmtConsentTs(eraseAt)) + '</strong>.<br>As per ABDM guidelines, health records are erased and no longer accessible.</div>');
|
$('#abdmFetchResultModalBody').html('<div class="text-muted small text-center p-3">This consent request is <strong>' + escHtml(status || 'EXPIRED') + '</strong>.<br>As per ABDM guidelines, health records are erased and no longer accessible.</div>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1440,7 +1456,7 @@ $(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url, { credentials: 'same-origin' })
|
fetch(url, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Unable to load fetched records.');
|
throw new Error((data && data.error) || 'Unable to load fetched records.');
|
||||||
|
|
@ -1789,7 +1805,7 @@ $(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url, { credentials: 'same-origin' })
|
fetch(url, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Unable to load ABDM records.');
|
throw new Error((data && data.error) || 'Unable to load ABDM records.');
|
||||||
|
|
@ -1816,7 +1832,7 @@ $(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url, { credentials: 'same-origin' })
|
fetch(url, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Auto flow failed.');
|
throw new Error((data && data.error) || 'Auto flow failed.');
|
||||||
|
|
@ -1868,7 +1884,7 @@ $(function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch(abdmDocDetailBaseUrl + '/' + encodeURIComponent(docId), { credentials: 'same-origin' })
|
fetch(abdmDocDetailBaseUrl + '/' + encodeURIComponent(docId), { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Unable to load document detail.');
|
throw new Error((data && data.error) || 'Unable to load document detail.');
|
||||||
|
|
@ -1899,7 +1915,7 @@ $(function() {
|
||||||
setAbdmStatus('Checking live status with ABDM bridge…');
|
setAbdmStatus('Checking live status with ABDM bridge…');
|
||||||
|
|
||||||
fetch(abdmCheckLiveStatusUrl, { credentials: 'same-origin' })
|
fetch(abdmCheckLiveStatusUrl, { credentials: 'same-origin' })
|
||||||
.then(function(resp) { return resp.json(); })
|
.then(safeParseJson)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
throw new Error((data && data.error) || 'Live status check failed.');
|
throw new Error((data && data.error) || 'Live status check failed.');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user