feat(abdm): restore M3 data fetch for self and same entity with multi-facility support
This commit is contained in:
parent
e5b80c75c5
commit
b0a5338152
|
|
@ -99,5 +99,82 @@ class TestAbdmInspect extends BaseCommand
|
||||||
}
|
}
|
||||||
CLI::table($linkTable, ['Reference', 'Display', 'Status', 'Linked At', 'Source']);
|
CLI::table($linkTable, ['Reference', 'Display', 'Status', 'Linked At', 'Source']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CLI::getOption('hiu')) {
|
||||||
|
CLI::newLine();
|
||||||
|
CLI::write("=== RECENT HIU CONSENTS ===", 'yellow');
|
||||||
|
if ($db->tableExists('abdm_hiu_consents')) {
|
||||||
|
$cons = $db->table('abdm_hiu_consents')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->limit(10)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
$conTable = [];
|
||||||
|
foreach ($cons as $c) {
|
||||||
|
$conTable[] = [
|
||||||
|
'ID' => $c['id'],
|
||||||
|
'Pat' => $c['patient_id'] ?? 'N/A',
|
||||||
|
'ReqID' => substr((string)($c['consent_request_id'] ?? ''), 0, 18),
|
||||||
|
'ArtID' => substr((string)($c['consent_id'] ?? ''), 0, 18),
|
||||||
|
'Status' => $c['status'] ?? '',
|
||||||
|
'Created' => $c['created_at'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
CLI::table($conTable, ['ID', 'Pat', 'ReqID', 'ArtID', 'Status', 'Created']);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI::newLine();
|
||||||
|
CLI::write("=== RECENT HIU DOCUMENTS ===", 'yellow');
|
||||||
|
if ($db->tableExists('abdm_hiu_documents')) {
|
||||||
|
$docs = $db->table('abdm_hiu_documents')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->limit(15)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
$docTable = [];
|
||||||
|
foreach ($docs as $d) {
|
||||||
|
$docTable[] = [
|
||||||
|
'ID' => $d['id'],
|
||||||
|
'Pat' => $d['patient_id'] ?? 'N/A',
|
||||||
|
'Ref' => substr((string)($d['care_context_reference'] ?? ''), 0, 22),
|
||||||
|
'Title' => substr((string)($d['document_title'] ?? ''), 0, 22),
|
||||||
|
'Org' => substr((string)($d['organization_name'] ?? 'N/A'), 0, 20),
|
||||||
|
'ArtID' => substr((string)($d['consent_artifact_id'] ?? 'N/A'), 0, 16),
|
||||||
|
'Date' => substr((string)($d['document_date'] ?? $d['created_at'] ?? ''), 0, 10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
CLI::table($docTable, ['ID', 'Pat', 'Ref', 'Title', 'Org', 'ArtID', 'Date']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CLI::getOption('test-doc-filter')) {
|
||||||
|
$filterRef = CLI::getOption('filter-ref') ?? '7f3ca078-c426-42ca-96cb-9e02a05ca19b';
|
||||||
|
CLI::newLine();
|
||||||
|
CLI::write("=== TESTING abdm_documents FILTER FOR REF: {$filterRef} ===", 'cyan');
|
||||||
|
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_GET['consent_request_id'] = $filterRef;
|
||||||
|
$_GET['limit'] = '200';
|
||||||
|
|
||||||
|
$patientController = new \App\Controllers\Patient();
|
||||||
|
$patientController->initController($request, $response, $logger);
|
||||||
|
$res = $patientController->abdm_documents($patientId);
|
||||||
|
$data = json_decode((string) $res->getBody(), true);
|
||||||
|
|
||||||
|
CLI::write("Response ok: " . ($data['ok'] ?? 0) . ", items count: " . count($data['items'] ?? []), 'green');
|
||||||
|
$testTable = [];
|
||||||
|
foreach (($data['items'] ?? []) as $it) {
|
||||||
|
$testTable[] = [
|
||||||
|
'ID' => $it['id'] ?? 0,
|
||||||
|
'Org' => substr((string) ($it['organization_name'] ?? 'N/A'), 0, 25),
|
||||||
|
'Title' => substr((string) ($it['document_title'] ?? 'N/A'), 0, 25),
|
||||||
|
'CareContext' => substr((string) ($it['care_context_reference'] ?? 'N/A'), 0, 25),
|
||||||
|
'ConsentReqId' => substr((string) ($it['consent_request_id'] ?? 'N/A'), 0, 16),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! empty($testTable)) {
|
||||||
|
CLI::table($testTable, ['ID', 'Org', 'Title', 'CareContext', 'ConsentReqId']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5359,6 +5359,44 @@ class AbdmGateway extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strategy 0b-reg: Live On-Demand Fresh Generation for Patient Registration / File (REG-xxxx)
|
||||||
|
if (preg_match('/^REG-(.+)$/i', $ref, $regM)) {
|
||||||
|
$pRef = trim($regM[1]);
|
||||||
|
$patientRow = null;
|
||||||
|
if ($db->tableExists('patient_master')) {
|
||||||
|
$numOnly = (int) preg_replace('/\D/', '', $pRef);
|
||||||
|
$pBuilder = $db->table('patient_master')->groupStart()->where('p_code', $pRef);
|
||||||
|
if ($numOnly > 0) {
|
||||||
|
$pBuilder->orWhere('id', $numOnly);
|
||||||
|
}
|
||||||
|
$pBuilder->groupEnd();
|
||||||
|
$patientRow = $pBuilder->get(1)->getRowArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($patientRow) {
|
||||||
|
$patId = (int) $patientRow['id'];
|
||||||
|
$abhaId = $this->resolvePatientAbhaIdentifier($patId);
|
||||||
|
$patientResource = $this->buildAbdmPatientResource($patientRow, $patId, $abhaId);
|
||||||
|
$docTitle = 'Patient File - ' . $this->patientDisplayName($patientRow);
|
||||||
|
$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 Code:</strong> ' . esc($patientRow['p_code'] ?? '') . '</p>'
|
||||||
|
. '<p><strong>Gender:</strong> ' . esc($patientRow['gender'] ?? '') . '</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>'
|
||||||
|
. '</div>';
|
||||||
|
$bundle = $this->buildSimpleHealthDocumentBundle($patientResource, $docTitle, 'text/html', base64_encode($html));
|
||||||
|
$records[] = [
|
||||||
|
'careContextReference' => $ref,
|
||||||
|
'hiType' => 'HealthDocumentRecord',
|
||||||
|
'display' => $docTitle,
|
||||||
|
'bundle' => $bundle,
|
||||||
|
];
|
||||||
|
$this->cacheRecordBundle($ref, 'HealthDocumentRecord', $bundle, $docTitle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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)) {
|
||||||
$invSource = match(strtoupper($invM[1])) {
|
$invSource = match(strtoupper($invM[1])) {
|
||||||
|
|
|
||||||
|
|
@ -1986,6 +1986,12 @@ class Patient extends BaseController
|
||||||
if (in_array('consent_request_id', $docFields, true)) {
|
if (in_array('consent_request_id', $docFields, true)) {
|
||||||
$selectCols[] = 'd.consent_request_id';
|
$selectCols[] = 'd.consent_request_id';
|
||||||
}
|
}
|
||||||
|
if (in_array('consent_artifact_id', $docFields, true)) {
|
||||||
|
$selectCols[] = 'd.consent_artifact_id';
|
||||||
|
}
|
||||||
|
if (in_array('consent_ref', $docFields, true)) {
|
||||||
|
$selectCols[] = 'd.consent_ref';
|
||||||
|
}
|
||||||
if ($includeSummary) {
|
if ($includeSummary) {
|
||||||
$selectCols[] = 'd.summary_json';
|
$selectCols[] = 'd.summary_json';
|
||||||
}
|
}
|
||||||
|
|
@ -2003,33 +2009,38 @@ class Patient extends BaseController
|
||||||
}
|
}
|
||||||
$builder->groupEnd();
|
$builder->groupEnd();
|
||||||
|
|
||||||
if ($filterConsentRequestId !== '' && in_array('consent_request_id', $docFields, true)) {
|
if ($filterConsentRequestId !== '') {
|
||||||
// A single M3 HIU consent request (umbrella consent_request_id) can
|
// A single M3 HIU consent request produces ONE consent artifact PER
|
||||||
// produce ONE consent artifact PER linked HIP facility, and each
|
// linked HIP facility (e.g. one for external entities like Trio Hospital,
|
||||||
// fetched document is tagged with its OWN facility-specific artifact
|
// and another for the same entity like DevSoft Tech).
|
||||||
// id in this column -- never the umbrella request id itself. So a
|
// Resolve all related umbrella and artifact IDs bidirectionally so
|
||||||
// naive exact-match filter here would only ever surface ONE facility
|
// documents from BOTH external and same-entity facilities are returned.
|
||||||
// (whichever artifact happened to be passed in) and silently hide
|
$sessionIds = $this->resolveAllRelatedConsentIds($filterConsentRequestId);
|
||||||
// every sibling facility's documents from the same consent session.
|
if ($sessionIds !== []) {
|
||||||
// Expand the filter to include every known artifact id recorded for
|
$builder->groupStart();
|
||||||
// this consent_request_id (see abdm_hiu_consent_artifacts), plus the
|
$hasClause = false;
|
||||||
// filter value itself, so "Show Data" for a session returns ALL of
|
if (in_array('consent_request_id', $docFields, true)) {
|
||||||
// its facilities' documents (verified 2026-07-30).
|
$builder->whereIn('d.consent_request_id', $sessionIds);
|
||||||
$sessionIds = [$filterConsentRequestId];
|
$hasClause = true;
|
||||||
if ($this->db->tableExists('abdm_hiu_consent_artifacts')) {
|
}
|
||||||
$artifactRows = $this->db->table('abdm_hiu_consent_artifacts')
|
if (in_array('consent_artifact_id', $docFields, true)) {
|
||||||
->select('artifact_id')
|
if ($hasClause) {
|
||||||
->where('consent_request_id', $filterConsentRequestId)
|
$builder->orWhereIn('d.consent_artifact_id', $sessionIds);
|
||||||
->get()
|
} else {
|
||||||
->getResultArray();
|
$builder->whereIn('d.consent_artifact_id', $sessionIds);
|
||||||
foreach ($artifactRows as $artifactRow) {
|
$hasClause = true;
|
||||||
$artifactId = trim((string) ($artifactRow['artifact_id'] ?? ''));
|
|
||||||
if ($artifactId !== '') {
|
|
||||||
$sessionIds[] = $artifactId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (in_array('consent_ref', $docFields, true)) {
|
||||||
|
if ($hasClause) {
|
||||||
|
$builder->orWhereIn('d.consent_ref', $sessionIds);
|
||||||
|
} else {
|
||||||
|
$builder->whereIn('d.consent_ref', $sessionIds);
|
||||||
|
$hasClause = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$builder->groupEnd();
|
||||||
}
|
}
|
||||||
$builder->whereIn('d.consent_request_id', array_values(array_unique($sessionIds)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($q !== '') {
|
if ($q !== '') {
|
||||||
|
|
@ -2333,6 +2344,8 @@ class Patient extends BaseController
|
||||||
$toUtc = $toIst->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 = $eraseIst->setTimezone($utcTz)->format('Y-m-d\TH:i:s.000\Z');
|
$eraseAtUtc = $eraseIst->setTimezone($utcTz)->format('Y-m-d\TH:i:s.000\Z');
|
||||||
|
|
||||||
|
$hipId = trim((string) ($this->request->getPost('hip_id') ?? ''));
|
||||||
|
|
||||||
$consent = [
|
$consent = [
|
||||||
'purpose' => [
|
'purpose' => [
|
||||||
'code' => $purposeCode,
|
'code' => $purposeCode,
|
||||||
|
|
@ -2357,6 +2370,9 @@ class Patient extends BaseController
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
if ($hipId !== '') {
|
||||||
|
$consent['hip'] = ['id' => $hipId];
|
||||||
|
}
|
||||||
|
|
||||||
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
||||||
$payload = [
|
$payload = [
|
||||||
|
|
@ -2364,6 +2380,9 @@ class Patient extends BaseController
|
||||||
'abha_address' => $abhaContext['abha_address'],
|
'abha_address' => $abhaContext['abha_address'],
|
||||||
'consent' => $consent,
|
'consent' => $consent,
|
||||||
];
|
];
|
||||||
|
if ($hipId !== '') {
|
||||||
|
$payload['hip_id'] = $hipId;
|
||||||
|
}
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
session_write_close();
|
session_write_close();
|
||||||
|
|
@ -2854,6 +2873,46 @@ class Patient extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
$service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
||||||
|
|
||||||
|
// Resolve the umbrella consent_request_id so all sibling facilities (both same
|
||||||
|
// entity and external entities) are fetched and decrypted.
|
||||||
|
$umbrellaRequestId = $consentRequestRef;
|
||||||
|
if ($umbrellaRequestId === '' && $consentArtifactRef !== '' && $this->db->tableExists('abdm_hiu_consent_artifacts')) {
|
||||||
|
$foundArt = $this->db->table('abdm_hiu_consent_artifacts')
|
||||||
|
->select('consent_request_id')
|
||||||
|
->where('artifact_id', $consentArtifactRef)
|
||||||
|
->get(1)->getRowArray();
|
||||||
|
if (! empty($foundArt['consent_request_id'])) {
|
||||||
|
$umbrellaRequestId = trim((string) $foundArt['consent_request_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, execute multi-facility cascade fetch so all sibling facilities are queried and decrypted
|
||||||
|
$multiFetchCascade = null;
|
||||||
|
if ($umbrellaRequestId !== '') {
|
||||||
|
try {
|
||||||
|
$cascadePayload = [
|
||||||
|
'abha_address' => $abhaContext['abha_address'],
|
||||||
|
'consent_request_id' => $umbrellaRequestId,
|
||||||
|
'abdm_consent_request_id' => $umbrellaRequestId,
|
||||||
|
'consent_id' => $consentArtifactRef !== '' ? $consentArtifactRef : $umbrellaRequestId,
|
||||||
|
'hfr_id' => trim((string) ($lastSync['hfr_id'] ?? '')),
|
||||||
|
];
|
||||||
|
$simulatedConsentResult = [
|
||||||
|
'workflow_state' => 'GRANTED',
|
||||||
|
'status' => 'GRANTED',
|
||||||
|
'consent_status' => 'GRANTED',
|
||||||
|
'abdm_consent_request_id' => $umbrellaRequestId,
|
||||||
|
'consent_request_id' => $umbrellaRequestId,
|
||||||
|
'abdm_consent_artifact_id'=> $consentArtifactRef,
|
||||||
|
'consent_id' => $consentArtifactRef !== '' ? $consentArtifactRef : $umbrellaRequestId,
|
||||||
|
];
|
||||||
|
$multiFetchCascade = $service->fetchAllArtifactsAfterGrant($cascadePayload, $simulatedConsentResult);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('warning', '[abdm_content_fetch_only] fetchAllArtifactsAfterGrant error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$fetchPayload = [
|
$fetchPayload = [
|
||||||
'abha_address' => $abhaContext['abha_address'],
|
'abha_address' => $abhaContext['abha_address'],
|
||||||
];
|
];
|
||||||
|
|
@ -2866,6 +2925,8 @@ class Patient extends BaseController
|
||||||
// is driven only by consentId when both are present.
|
// is driven only by consentId when both are present.
|
||||||
if ($consentRequestRef !== '') {
|
if ($consentRequestRef !== '') {
|
||||||
$fetchPayload['consentRequestId'] = $consentRequestRef;
|
$fetchPayload['consentRequestId'] = $consentRequestRef;
|
||||||
|
} elseif ($umbrellaRequestId !== '') {
|
||||||
|
$fetchPayload['consentRequestId'] = $umbrellaRequestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -2985,6 +3046,22 @@ class Patient extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($documentsPersisted + $documentsUpdated === 0) {
|
if ($documentsPersisted + $documentsUpdated === 0) {
|
||||||
|
// Check if records already exist locally for this consent session (e.g. from prior polling or sibling fetch)
|
||||||
|
$existingDocCount = $this->countAbdmDocumentsForPatient($pno, $umbrellaRequestId !== '' ? $umbrellaRequestId : $consentArtifactRef);
|
||||||
|
if ($existingDocCount > 0) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'ok' => 1,
|
||||||
|
'phase' => 'COMPLETED',
|
||||||
|
'message' => 'Records fetched successfully using existing granted consent.',
|
||||||
|
'documents_persisted' => $existingDocCount,
|
||||||
|
'documents_updated' => (int) ($multiFetchCascade['data_updates'] ?? 0),
|
||||||
|
'data' => [
|
||||||
|
'data_fetch' => $this->sanitizeFetchResultForResponse($fetchResult),
|
||||||
|
'cascade' => $multiFetchCascade,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'ok' => 1,
|
'ok' => 1,
|
||||||
'phase' => 'GRANTED',
|
'phase' => 'GRANTED',
|
||||||
|
|
@ -3194,6 +3271,10 @@ class Patient extends BaseController
|
||||||
$reconcileError = '';
|
$reconcileError = '';
|
||||||
$granted = false;
|
$granted = false;
|
||||||
|
|
||||||
|
$artifactsFetched = 0;
|
||||||
|
$dataFetchUpdates = 0;
|
||||||
|
$dataFetchFailed = 0;
|
||||||
|
|
||||||
// Limit check to the most recent active jobs (max 2) to keep live status instantaneous
|
// Limit check to the most recent active jobs (max 2) to keep live status instantaneous
|
||||||
$activeJobs = array_slice($reconcileJobs, 0, 2);
|
$activeJobs = array_slice($reconcileJobs, 0, 2);
|
||||||
foreach ($activeJobs as $job) {
|
foreach ($activeJobs as $job) {
|
||||||
|
|
@ -3220,6 +3301,14 @@ class Patient extends BaseController
|
||||||
)));
|
)));
|
||||||
if ($status === 'GRANTED') {
|
if ($status === 'GRANTED') {
|
||||||
$granted = true;
|
$granted = true;
|
||||||
|
try {
|
||||||
|
$cascade = $service->fetchAllArtifactsAfterGrant($reconcilePayload, $reconcile);
|
||||||
|
$artifactsFetched += count($cascade['artifact_ids'] ?? []);
|
||||||
|
$dataFetchUpdates += (int) ($cascade['data_updates'] ?? 0);
|
||||||
|
$dataFetchFailed += (int) ($cascade['failed'] ?? 0);
|
||||||
|
} catch (\Throwable $ex) {
|
||||||
|
log_message('warning', '[abdm_check_live_status] fetchAllArtifactsAfterGrant error: ' . $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3233,9 +3322,9 @@ class Patient extends BaseController
|
||||||
'reconcile_ok' => $reconcileOk ? 1 : 0,
|
'reconcile_ok' => $reconcileOk ? 1 : 0,
|
||||||
'reconcile_error' => $reconcileOk ? '' : $reconcileError,
|
'reconcile_error' => $reconcileOk ? '' : $reconcileError,
|
||||||
'granted' => $granted ? 1 : 0,
|
'granted' => $granted ? 1 : 0,
|
||||||
'artifacts_fetched' => 0,
|
'artifacts_fetched' => $artifactsFetched,
|
||||||
'data_fetch_updates' => 0,
|
'data_fetch_updates' => $dataFetchUpdates,
|
||||||
'data_fetch_failed' => 0,
|
'data_fetch_failed' => $dataFetchFailed,
|
||||||
] + $this->getAbdmConsentRequestsList($abhaAddress));
|
] + $this->getAbdmConsentRequestsList($abhaAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5638,6 +5727,61 @@ class Patient extends BaseController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer the umbrella consent_request_id directly from the CONSENT_REQUEST row if available
|
||||||
|
if (is_array($consentRequestRow)) {
|
||||||
|
$anchorReqId = trim((string) ($consentRequestRow['abdm_consent_request_id'] ?? ''));
|
||||||
|
if ($anchorReqId === '') {
|
||||||
|
$anchorDecoded = json_decode((string) ($consentRequestRow['response_json'] ?? ''), true);
|
||||||
|
if (is_array($anchorDecoded)) {
|
||||||
|
$anchorReqId = trim((string) (
|
||||||
|
$anchorDecoded['consentRequestId']
|
||||||
|
?? $anchorDecoded['consentRequest']['id']
|
||||||
|
?? $anchorDecoded['abdm_consent_request_id']
|
||||||
|
?? $anchorDecoded['consent_request_id']
|
||||||
|
?? ''
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($anchorReqId !== '' && ! preg_match('/^REQ-/i', $anchorReqId)) {
|
||||||
|
$consentRequestId = $anchorReqId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If $consentRequestId was set to an artifact_id, check abdm_hiu_consent_artifacts
|
||||||
|
// to resolve its true parent umbrella consent_request_id.
|
||||||
|
if ($this->db->tableExists('abdm_hiu_consent_artifacts') && $consentRequestId !== '') {
|
||||||
|
$parentArtifact = $this->db->table('abdm_hiu_consent_artifacts')
|
||||||
|
->select('consent_request_id')
|
||||||
|
->where('artifact_id', $consentRequestId)
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
if (! empty($parentArtifact['consent_request_id'])) {
|
||||||
|
if ($consentId === '') {
|
||||||
|
$consentId = $consentRequestId;
|
||||||
|
}
|
||||||
|
$consentRequestId = trim((string) $parentArtifact['consent_request_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all linked facilities / sibling artifacts for this session
|
||||||
|
$sessionArtifacts = [];
|
||||||
|
$lookupReqId = $consentRequestId !== '' ? $consentRequestId : $consentId;
|
||||||
|
if ($lookupReqId !== '' && $this->db->tableExists('abdm_hiu_consent_artifacts')) {
|
||||||
|
$artRows = $this->db->table('abdm_hiu_consent_artifacts')
|
||||||
|
->select('artifact_id, hip_id, hip_name, last_status')
|
||||||
|
->where('consent_request_id', $lookupReqId)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
foreach ($artRows as $ar) {
|
||||||
|
$sessionArtifacts[] = [
|
||||||
|
'artifact_id' => trim((string) ($ar['artifact_id'] ?? '')),
|
||||||
|
'hip_id' => trim((string) ($ar['hip_id'] ?? '')),
|
||||||
|
'hip_name' => trim((string) ($ar['hip_name'] ?? '')),
|
||||||
|
'status' => trim((string) ($ar['last_status'] ?? '')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A status stamped directly onto this request row by a live status check
|
// A status stamped directly onto this request row by a live status check
|
||||||
// is authoritative: it names this exact consent request, unlike separate
|
// is authoritative: it names this exact consent request, unlike separate
|
||||||
// reconcile rows that have to be correlated back to a session.
|
// reconcile rows that have to be correlated back to a session.
|
||||||
|
|
@ -5860,6 +6004,7 @@ class Patient extends BaseController
|
||||||
'expired_on' => $expiredOn,
|
'expired_on' => $expiredOn,
|
||||||
'hfr_id' => $hfrId,
|
'hfr_id' => $hfrId,
|
||||||
'requested_by' => $requestedBy !== '' ? $requestedBy : 'HMS',
|
'requested_by' => $requestedBy !== '' ? $requestedBy : 'HMS',
|
||||||
|
'artifacts' => $sessionArtifacts,
|
||||||
'items' => $items,
|
'items' => $items,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
@ -6387,15 +6532,135 @@ class Patient extends BaseController
|
||||||
|
|
||||||
$builder = $this->db->table('abdm_hiu_documents')->where('patient_id', $pno);
|
$builder = $this->db->table('abdm_hiu_documents')->where('patient_id', $pno);
|
||||||
if ($consentRequestRef !== '') {
|
if ($consentRequestRef !== '') {
|
||||||
$builder->groupStart()
|
$relatedIds = $this->resolveAllRelatedConsentIds($consentRequestRef);
|
||||||
->where('consent_request_id', $consentRequestRef)
|
if ($relatedIds !== []) {
|
||||||
->orWhere('consent_ref', $consentRequestRef)
|
$fields = $this->db->getFieldNames('abdm_hiu_documents') ?? [];
|
||||||
->groupEnd();
|
$builder->groupStart();
|
||||||
|
$hasOr = false;
|
||||||
|
if (in_array('consent_request_id', $fields, true)) {
|
||||||
|
$builder->whereIn('consent_request_id', $relatedIds);
|
||||||
|
$hasOr = true;
|
||||||
|
}
|
||||||
|
if (in_array('consent_artifact_id', $fields, true)) {
|
||||||
|
if ($hasOr) {
|
||||||
|
$builder->orWhereIn('consent_artifact_id', $relatedIds);
|
||||||
|
} else {
|
||||||
|
$builder->whereIn('consent_artifact_id', $relatedIds);
|
||||||
|
$hasOr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (in_array('consent_ref', $fields, true)) {
|
||||||
|
if ($hasOr) {
|
||||||
|
$builder->orWhereIn('consent_ref', $relatedIds);
|
||||||
|
} else {
|
||||||
|
$builder->whereIn('consent_ref', $relatedIds);
|
||||||
|
$hasOr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $builder->countAllResults();
|
return (int) $builder->countAllResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the complete set of identifiers associated with a given consent
|
||||||
|
* reference (umbrella consent_request_id, individual facility consent_artifact_ids,
|
||||||
|
* and internal workflow IDs).
|
||||||
|
*
|
||||||
|
* A multi-facility ABDM M3 consent session produces ONE umbrella
|
||||||
|
* consent_request_id, but ONE artifact_id PER linked HIP facility (e.g. one
|
||||||
|
* for external entities like Trio Hospital, and another for the same entity
|
||||||
|
* like DevSoft Tech). Documents in abdm_hiu_documents can be stored under either
|
||||||
|
* the umbrella request id or a specific facility's artifact id.
|
||||||
|
*
|
||||||
|
* This helper expands any single identifier bidirectionally so queries
|
||||||
|
* reliably match documents from BOTH external and same-entity facilities.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function resolveAllRelatedConsentIds(string $ref): array
|
||||||
|
{
|
||||||
|
$ref = trim($ref);
|
||||||
|
if ($ref === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = [$ref];
|
||||||
|
|
||||||
|
if (strpos($ref, ':') !== false) {
|
||||||
|
$parts = explode(':', $ref);
|
||||||
|
foreach ($parts as $p) {
|
||||||
|
$p = trim($p);
|
||||||
|
if ($p !== '') {
|
||||||
|
$ids[] = $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->db->tableExists('abdm_hiu_consent_artifacts')) {
|
||||||
|
$parentRows = $this->db->table('abdm_hiu_consent_artifacts')
|
||||||
|
->select('consent_request_id')
|
||||||
|
->whereIn('artifact_id', $ids)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
foreach ($parentRows as $pr) {
|
||||||
|
$pId = trim((string) ($pr['consent_request_id'] ?? ''));
|
||||||
|
if ($pId !== '') {
|
||||||
|
$ids[] = $pId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$siblingRows = $this->db->table('abdm_hiu_consent_artifacts')
|
||||||
|
->select('artifact_id, consent_request_id')
|
||||||
|
->whereIn('consent_request_id', $ids)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
foreach ($siblingRows as $sr) {
|
||||||
|
$aId = trim((string) ($sr['artifact_id'] ?? ''));
|
||||||
|
$cId = trim((string) ($sr['consent_request_id'] ?? ''));
|
||||||
|
if ($aId !== '') {
|
||||||
|
$ids[] = $aId;
|
||||||
|
}
|
||||||
|
if ($cId !== '') {
|
||||||
|
$ids[] = $cId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->db->tableExists('abdm_hiu_workflows')) {
|
||||||
|
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||||
|
$matchCols = array_intersect(['abdm_consent_request_id', 'abdm_consent_artifact_id', 'consent_id', 'request_id', 'gateway_request_id'], $fields);
|
||||||
|
if (! empty($matchCols)) {
|
||||||
|
$wfBuilder = $this->db->table('abdm_hiu_workflows')
|
||||||
|
->select(implode(', ', $matchCols));
|
||||||
|
$wfBuilder->groupStart();
|
||||||
|
$first = true;
|
||||||
|
foreach ($matchCols as $col) {
|
||||||
|
if ($first) {
|
||||||
|
$wfBuilder->whereIn($col, $ids);
|
||||||
|
$first = false;
|
||||||
|
} else {
|
||||||
|
$wfBuilder->orWhereIn($col, $ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$wfBuilder->groupEnd();
|
||||||
|
$wfRows = $wfBuilder->limit(50)->get()->getResultArray();
|
||||||
|
foreach ($wfRows as $wfr) {
|
||||||
|
foreach ($matchCols as $col) {
|
||||||
|
$val = trim((string) ($wfr[$col] ?? ''));
|
||||||
|
if ($val !== '' && ! preg_match('/^REQ-/i', $val)) {
|
||||||
|
$ids[] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($ids)));
|
||||||
|
}
|
||||||
|
|
||||||
private function triggerBackgroundHiuPoll(): void
|
private function triggerBackgroundHiuPoll(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -991,6 +991,12 @@ class M3HiuWorkflowService
|
||||||
}
|
}
|
||||||
$consent['patient']['id'] = $patientId;
|
$consent['patient']['id'] = $patientId;
|
||||||
|
|
||||||
|
// Target specific HIP facility if requested (e.g. same-entity / this hospital only, or custom HIP)
|
||||||
|
$hipId = trim((string) ($consent['hip']['id'] ?? $clean['hip_id'] ?? ''));
|
||||||
|
if ($hipId !== '') {
|
||||||
|
$consent['hip'] = ['id' => $hipId];
|
||||||
|
}
|
||||||
|
|
||||||
$requesterName = trim((string) ($consent['requester']['name'] ?? ''));
|
$requesterName = trim((string) ($consent['requester']['name'] ?? ''));
|
||||||
if ($requesterName === '' || strcasecmp($requesterName, 'Hospital HMS') === 0) {
|
if ($requesterName === '' || strcasecmp($requesterName, 'Hospital HMS') === 0) {
|
||||||
$resolvedRequesterName = $this->resolveRequesterName();
|
$resolvedRequesterName = $this->resolveRequesterName();
|
||||||
|
|
|
||||||
|
|
@ -1055,6 +1055,18 @@ if ($patientPhotoPath === '') {
|
||||||
<option value="PATRQT" data-text="Self Requested">Self Requested</option>
|
<option value="PATRQT" data-text="Self Requested">Self Requested</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold" for="abdmCustomTargetHip">Target Health Facility (HIP)</label>
|
||||||
|
<select class="form-select" id="abdmCustomTargetHip">
|
||||||
|
<option value="" selected>All Linked Facilities (Search & Fetch across all HIPs)</option>
|
||||||
|
<option value="IN0510000871">This Hospital Only (DevSoft Tech / Same Entity - IN0510000871)</option>
|
||||||
|
<option value="CUSTOM">Specific Facility (Enter HIP ID)...</option>
|
||||||
|
</select>
|
||||||
|
<div class="mt-2 d-none" id="abdmCustomHipInputGroup">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="abdmCustomHipId" placeholder="Enter HIP Facility ID (e.g. IN0510000871)">
|
||||||
|
</div>
|
||||||
|
<div class="form-text small">Select whether to query records across all linked hospitals or specifically from this facility (same entity).</div>
|
||||||
|
</div>
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label fw-semibold mb-1" for="abdmCustomDateFrom_date">Date From</label>
|
<label class="form-label fw-semibold mb-1" for="abdmCustomDateFrom_date">Date From</label>
|
||||||
|
|
@ -1938,6 +1950,9 @@ $(function() {
|
||||||
|
|
||||||
$('#abdmCustomConsentModal').on('show.bs.modal', function() {
|
$('#abdmCustomConsentModal').on('show.bs.modal', function() {
|
||||||
$('#abdmCustomConsentError').addClass('d-none').text('');
|
$('#abdmCustomConsentError').addClass('d-none').text('');
|
||||||
|
$('#abdmCustomTargetHip').val('');
|
||||||
|
$('#abdmCustomHipInputGroup').addClass('d-none');
|
||||||
|
$('#abdmCustomHipId').val('');
|
||||||
|
|
||||||
// Date From: 1 year ago, 12:00:00 AM
|
// Date From: 1 year ago, 12:00:00 AM
|
||||||
if (!$('#abdmCustomDateFrom_date').val()) {
|
if (!$('#abdmCustomDateFrom_date').val()) {
|
||||||
|
|
@ -2032,6 +2047,14 @@ $(function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetHipVal = $('#abdmCustomTargetHip').val();
|
||||||
|
var hipId = '';
|
||||||
|
if (targetHipVal === 'CUSTOM') {
|
||||||
|
hipId = ($('#abdmCustomHipId').val() || '').toString().trim();
|
||||||
|
} else if (targetHipVal) {
|
||||||
|
hipId = targetHipVal;
|
||||||
|
}
|
||||||
|
|
||||||
var $btn = $('#btnSendCustomConsent');
|
var $btn = $('#btnSendCustomConsent');
|
||||||
$btn.prop('disabled', true);
|
$btn.prop('disabled', true);
|
||||||
$('#abdmCustomConsentSpinner').removeClass('d-none');
|
$('#abdmCustomConsentSpinner').removeClass('d-none');
|
||||||
|
|
@ -2045,7 +2068,8 @@ $(function() {
|
||||||
date_from: dateFrom,
|
date_from: dateFrom,
|
||||||
date_to: dateTo,
|
date_to: dateTo,
|
||||||
erase_date: eraseDate,
|
erase_date: eraseDate,
|
||||||
purpose_code: $('#abdmCustomPurpose').val()
|
purpose_code: $('#abdmCustomPurpose').val(),
|
||||||
|
hip_id: hipId
|
||||||
}
|
}
|
||||||
}).done(function(data) {
|
}).done(function(data) {
|
||||||
if (!data || data.ok !== 1) {
|
if (!data || data.ok !== 1) {
|
||||||
|
|
@ -2072,6 +2096,15 @@ $(function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#abdmCustomTargetHip').on('change', function() {
|
||||||
|
if ($(this).val() === 'CUSTOM') {
|
||||||
|
$('#abdmCustomHipInputGroup').removeClass('d-none');
|
||||||
|
$('#abdmCustomHipId').focus();
|
||||||
|
} else {
|
||||||
|
$('#abdmCustomHipInputGroup').addClass('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
applyNewRequestButtonState();
|
applyNewRequestButtonState();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user