fix(abdm): eliminate dummy consultation bundles, fix Strategy 0 return bundle, add on-demand invoice FHIR, and ensure full multi-artifact HIU polling

This commit is contained in:
DevSoftTech Server 2026-10-02 01:11:22 +05:30
parent 1c41b4555b
commit 48d11b20df
3 changed files with 95 additions and 20 deletions

View File

@ -4942,8 +4942,13 @@ class AbdmGateway extends BaseController
$targetOpdId = (int) ($pRow['opd_id'] ?? 0);
}
if ($targetOpdId <= 0 && $targetPatientId > 0 && $db->tableExists('opd_master')) {
$opdRow = $db->table('opd_master')->select('opd_id')->where('p_id', $targetPatientId)->orderBy('opd_id', 'DESC')->get(1)->getRowArray();
$targetOpdId = (int) ($opdRow['opd_id'] ?? 0);
$opdRow = $db->table('opd_master')->select('opd_id')->where('opd_id', $targetPatientId)->get(1)->getRowArray();
if ($opdRow) {
$targetOpdId = (int) $opdRow['opd_id'];
} else {
$opdRow = $db->table('opd_master')->select('opd_id')->where('p_id', $targetPatientId)->orderBy('opd_id', 'DESC')->get(1)->getRowArray();
$targetOpdId = (int) ($opdRow['opd_id'] ?? 0);
}
}
if ($targetOpdId > 0) {
@ -4966,6 +4971,30 @@ class AbdmGateway extends BaseController
}
}
// Strategy 0b: Live On-Demand Fresh Generation for Invoice Record
if (preg_match('/^INVOICE-(CHG|OPD|IPD)-(\d+)/i', $ref, $invM)) {
$invSource = match(strtoupper($invM[1])) {
'CHG' => 'charges_invoice',
'OPD' => 'opd_invoice',
'IPD' => 'ipd_invoice',
};
$invId = (int) $invM[2];
try {
$invPayload = $this->buildInvoiceSourceRecordPayload($invSource, $invId, 0, '');
if ($invPayload !== null && !empty($invPayload['bundle'])) {
$records[] = [
'careContextReference' => $ref,
'hiType' => 'InvoiceRecord',
'display' => $invPayload['care_context_display'] ?? 'Invoice Record',
'bundle' => $invPayload['bundle'],
];
continue;
}
} catch (\Throwable $ex) {
log_message('warning', 'Live Invoice FHIR generation in recordsFetch failed: ' . $ex->getMessage());
}
}
// Strategy A: Check if health_records already has stored FHIR record_data
$hrRow = null;
if ($db->tableExists('health_records')) {
@ -5017,14 +5046,18 @@ class AbdmGateway extends BaseController
// Strategy B: Build compliant FHIR Document Bundle from patient_master & OPD
$bundle = $this->assembleFhirBundleForCareContext($ref);
if ($bundle !== null) {
$records[] = [
'careContextReference' => $ref,
'hiType' => 'OPConsultRecord',
'display' => 'OPConsultRecord - ' . date('d M Y'),
'bundle' => $bundle,
];
// STRICTLY restricted to OPD/consultation care contexts.
// NEVER synthesize dummy consultation records for invoices, discharge, labs, radiology, etc.
if (preg_match('/^(?:OPD|PRESCRIPTION|CONSULT)/i', $ref)) {
$bundle = $this->assembleFhirBundleForCareContext($ref);
if ($bundle !== null) {
$records[] = [
'careContextReference' => $ref,
'hiType' => 'OPConsultRecord',
'display' => 'OPConsultRecord - ' . date('d M Y'),
'bundle' => $bundle,
];
}
}
}
@ -5049,10 +5082,25 @@ class AbdmGateway extends BaseController
{
$db = \Config\Database::connect();
// Extract patient ID from reference (e.g. "OPD-16-S1-20260909" -> patient_id 16)
// Extract patient ID and doctor from reference (e.g. "OPD-16-S1-20260909" -> patient_id 16 or opd_id)
$patientId = 0;
$doctorName = 'Treating Doctor';
if (preg_match('/OPD-(\d+)/i', $careContextRef, $matches)) {
$patientId = (int) $matches[1];
$extractedId = (int) $matches[1];
if ($db->tableExists('opd_master')) {
$opd = $db->table('opd_master')->where('opd_id', $extractedId)->get(1)->getRowArray();
if (!empty($opd)) {
if (!empty($opd['p_id'])) {
$patientId = (int) $opd['p_id'];
}
if (!empty($opd['doc_name'])) {
$doctorName = trim((string) $opd['doc_name']);
}
}
}
if ($patientId === 0) {
$patientId = $extractedId;
}
} elseif (preg_match('/(?:HR|PAT|REG|P)-(\d+)/i', $careContextRef, $matches)) {
$patientId = (int) $matches[1];
}
@ -5121,7 +5169,7 @@ class AbdmGateway extends BaseController
],
'date' => $nowIso,
'author' => [
['reference' => 'Practitioner/prac-1', 'display' => 'Treating Doctor'],
['reference' => 'Practitioner/prac-1', 'display' => $doctorName],
],
'title' => 'Consultation Record',
'section' => [
@ -5155,7 +5203,7 @@ class AbdmGateway extends BaseController
'resource' => [
'resourceType' => 'Practitioner',
'id' => 'prac-1',
'name' => [['text' => 'Treating Doctor']],
'name' => [['text' => $doctorName]],
],
],
],

View File

@ -4936,8 +4936,15 @@ class Opd_prescription extends BaseController
$bundle = $this->fhirR4Builder->buildOpConsultBundle($patient, $encounter, $medications, $conditions, $clinicalContext);
$bundleJson = (string) json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$user = function_exists('auth') ? auth()->user() : null;
$generatedBy = (string) ($user->id ?? session('user_id') ?? 'system');
$generatedBy = 'system';
try {
if (session_status() === PHP_SESSION_ACTIVE) {
$user = function_exists('auth') ? auth()->user() : null;
$generatedBy = (string) ($user->id ?? session('user_id') ?? 'system');
}
} catch (\Throwable) {
$generatedBy = 'system';
}
$generatedAt = Time::now('Asia/Kolkata')->toDateTimeString();
// Overwrite the latest stored bundle for this opd_id / session
@ -4975,6 +4982,7 @@ class Opd_prescription extends BaseController
'ok' => 1,
'message' => 'FHIR bundle regenerated successfully',
'document_id' => $documentId,
'bundle' => $bundle,
'generated_at' => $generatedAt,
'entry_count' => isset($bundle['entry']) ? count($bundle['entry']) : 0,
];

View File

@ -243,11 +243,11 @@ class M3HiuWorkflowService
$consentRowId = (int) ($row['id'] ?? 0);
// Fast-check: if a child reconcile/data_fetch workflow for this request_id has already reached terminal state
// Fast-check: if a child reconcile/data_fetch workflow for this request_id has reached terminal state (EXPIRED, REVOKED, DENIED)
$terminalCheck = $this->db->table('abdm_hiu_workflows')
->select('id, workflow_state, operation')
->where('request_id', $requestId)
->whereIn('workflow_state', ['COMPLETED', 'EXPIRED', 'REVOKED', 'DENIED'])
->whereIn('workflow_state', ['EXPIRED', 'REVOKED', 'DENIED'])
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
@ -289,9 +289,27 @@ class M3HiuWorkflowService
?? ''
)));
$allArtifactsReceived = true;
$resolvedConsentId = trim((string) (
$consentResult['abdm_consent_request_id']
?? $consentResult['consent_request_id']
?? $consentResult['abdm_consent_artifact_id']
?? $consentResult['consent_id']
?? ''
));
if ($resolvedConsentId !== '' && $this->db->tableExists('abdm_hiu_consent_artifacts')) {
$unreceived = $this->db->table('abdm_hiu_consent_artifacts')
->where('consent_request_id', $resolvedConsentId)
->whereNotIn('last_status', ['RECEIVED', 'DATA_RECEIVED', 'EXPIRED', 'REVOKED', 'DENIED', 'COMPLETED'])
->countAllResults();
if ($unreceived > 0) {
$allArtifactsReceived = false;
}
}
if (in_array($consentState, ['revoked', 'denied', 'expired'], true)) {
$this->markConsentRequestTerminal($consentRowId, strtoupper($consentState));
} elseif ($cascade['granted'] && $cascade['failed'] === 0 && $cascade['data_updates'] > 0) {
} elseif ($cascade['granted'] && $cascade['failed'] === 0 && $cascade['data_updates'] > 0 && $allArtifactsReceived) {
$this->markConsentRequestTerminal($consentRowId, 'COMPLETED');
}
} else {
@ -432,7 +450,8 @@ class M3HiuWorkflowService
$artifactResult = $this->runOperation('data_fetch', $artifactPayload);
if ((int) ($artifactResult['ok'] ?? 0) === 1) {
$result['data_updates']++;
$this->markArtifactPolled($resolvedConsentId, $artifactId, (string) ($artifactResult['consent_status'] ?? ''));
$status = (string) ($artifactResult['workflow_state'] ?? $artifactResult['status'] ?? $artifactResult['consent_status'] ?? '');
$this->markArtifactPolled($resolvedConsentId, $artifactId, $status);
} else {
$result['failed']++;
}