fix(abdm): make FHIR creation async local-only in web requests, auto-link every minute via cron, disable cooling delay
This commit is contained in:
parent
a7d83551fe
commit
b3e7a2175f
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Libraries\Abdm\AbdmConnectorFactory;
|
||||
use App\Libraries\Abdm\Sync\AbdmSyncWorkerService;
|
||||
use App\Libraries\Abdm\Sync\AbdmTaskBoardSyncService;
|
||||
use App\Libraries\FhirEncryptionService;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
|
|
@ -11,11 +13,11 @@ class AbdmPushSync extends BaseCommand
|
|||
{
|
||||
protected $group = 'ABDM';
|
||||
protected $name = 'abdm:push-sync';
|
||||
protected $description = 'Process ABDM M2 sync outbox and ABDM Work Task Board care-context linking.';
|
||||
protected $description = 'Process ABDM M2 sync outbox, ABDM Work Task Board, and pending health_records care-context linking.';
|
||||
protected $usage = 'abdm:push-sync [--limit 20] [--worker worker-name] [--taskboard-only]';
|
||||
protected $arguments = [];
|
||||
protected $options = [
|
||||
'--limit' => 'Maximum outbox and taskboard rows to process in this run.',
|
||||
'--limit' => 'Maximum outbox, taskboard, and health_records rows to process in this run.',
|
||||
'--worker' => 'Worker identifier for lock tracking.',
|
||||
'--taskboard-only' => 'Skip outbox queue and only sync ABDM Work Task Board records.',
|
||||
];
|
||||
|
|
@ -46,7 +48,7 @@ class AbdmPushSync extends BaseCommand
|
|||
CLI::write('Skipped: ' . (int) ($summary['skipped'] ?? 0));
|
||||
}
|
||||
|
||||
// Also sync ABDM Work Task Board records (OPD Consults & Work Queue items)
|
||||
// 2. Sync ABDM Work Task Board records (OPD Consults & Work Queue items)
|
||||
CLI::newLine();
|
||||
CLI::write('ABDM Work Task Board Sync', 'cyan');
|
||||
$tbService = new AbdmTaskBoardSyncService();
|
||||
|
|
@ -54,5 +56,150 @@ class AbdmPushSync extends BaseCommand
|
|||
|
||||
CLI::write('OPD Consults -> Eligible: ' . ($tbSummary['opd']['eligible'] ?? 0) . ' | Linked: ' . ($tbSummary['opd']['linked'] ?? 0) . ' | Cooling: ' . ($tbSummary['opd']['cooling'] ?? 0) . ' | Failed: ' . ($tbSummary['opd']['failed'] ?? 0), 'green');
|
||||
CLI::write('Work Tasks -> Eligible: ' . ($tbSummary['tasks']['eligible'] ?? 0) . ' | Linked: ' . ($tbSummary['tasks']['linked'] ?? 0) . ' | Cooling: ' . ($tbSummary['tasks']['cooling'] ?? 0) . ' | Failed: ' . ($tbSummary['tasks']['failed'] ?? 0), 'green');
|
||||
|
||||
// 3. Process pending health_records directly (instant background sync for newly saved records)
|
||||
$this->pushPendingHealthRecords($limit);
|
||||
}
|
||||
|
||||
private function pushPendingHealthRecords(int $limit): void
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
if (! $db->tableExists('health_records')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = $db->table('health_records')
|
||||
->whereNotIn('push_status', ['pushed', 'linked', 'local_only'])
|
||||
->where('care_context_reference !=', '')
|
||||
->where('abha_id !=', '')
|
||||
->orderBy('id', 'ASC')
|
||||
->get($limit)
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::newLine();
|
||||
CLI::write('--- Pending Health Records Sync (Immediate Cron Link) ---', 'cyan');
|
||||
CLI::write('Found ' . count($rows) . ' pending record(s) to push...', 'yellow');
|
||||
|
||||
$connector = AbdmConnectorFactory::make();
|
||||
$pushed = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$bundle = $this->resolveFhirBundle($row);
|
||||
if ($bundle === null) {
|
||||
CLI::write(' Record #' . $row['id'] . ' (' . ($row['hi_type'] ?? '') . ') - no valid FHIR bundle', 'red');
|
||||
$failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$patientId = (int) ($row['patient_id'] ?? 0);
|
||||
$patient = $this->loadPatientDemographics($db, $patientId);
|
||||
$abhaId = trim((string) ($row['abha_id'] ?? ''));
|
||||
|
||||
try {
|
||||
$result = $connector->pushRecord([
|
||||
'patient_id' => (string) $patientId,
|
||||
'patient_name' => $patient['name'] !== '' ? $patient['name'] : ('PATIENT-' . $patientId),
|
||||
'abha_id' => str_contains($abhaId, '@') ? '' : $abhaId,
|
||||
'abha_address' => str_contains($abhaId, '@') ? $abhaId : $patient['abha_address'],
|
||||
'gender' => $patient['gender'],
|
||||
'year_of_birth' => $patient['year_of_birth'],
|
||||
'hi_type' => (string) ($row['hi_type'] ?? ''),
|
||||
'record_type' => (string) ($row['hi_type'] ?? ''),
|
||||
'visit_date' => substr((string) ($row['created_at'] ?? date('Y-m-d')), 0, 10),
|
||||
'care_context_reference' => (string) ($row['care_context_reference'] ?? ''),
|
||||
'care_context_display' => (string) ($row['care_context_display'] ?? (($row['hi_type'] ?? '') . ' ' . substr((string) ($row['created_at'] ?? ''), 0, 10))),
|
||||
'record_data' => $bundle,
|
||||
]);
|
||||
|
||||
$ok = ! empty($result['ok']) && (int) $result['ok'] === 1;
|
||||
$httpCode = (int) ($result['http_code'] ?? 0);
|
||||
$statusVal = strtolower((string) ($result['status'] ?? ''));
|
||||
|
||||
if ($ok || in_array($httpCode, [200, 201, 202, 409], true) || in_array($statusVal, ['queued', 'pushed', 'linked', 'duplicate'], true)) {
|
||||
$db->table('health_records')->where('id', $row['id'])->update([
|
||||
'push_status' => 'pushed',
|
||||
'push_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$pushed++;
|
||||
CLI::write(' Record #' . $row['id'] . ' (' . ($row['hi_type'] ?? '') . ' ' . ($row['care_context_reference'] ?? '') . ') -> pushed', 'green');
|
||||
} else {
|
||||
$failed++;
|
||||
$errMsg = (string) ($result['message'] ?? $result['error'] ?? 'Push failed');
|
||||
$db->table('health_records')->where('id', $row['id'])->update([
|
||||
'push_status' => 'failed',
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
CLI::write(' Record #' . $row['id'] . ' (' . ($row['hi_type'] ?? '') . ') -> failed: ' . $errMsg, 'red');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$failed++;
|
||||
CLI::write(' Record #' . $row['id'] . ' (' . ($row['hi_type'] ?? '') . ') -> error: ' . $e->getMessage(), 'red');
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write('Pending Records: ' . $pushed . ' pushed, ' . $failed . ' failed.', $pushed > 0 ? 'green' : 'yellow');
|
||||
}
|
||||
|
||||
private function resolveFhirBundle(array $row): ?array
|
||||
{
|
||||
$plain = trim((string) ($row['fhir_bundle'] ?? $row['record_data'] ?? ''));
|
||||
if ($plain === '') {
|
||||
$encrypted = trim((string) ($row['fhir_bundle_enc'] ?? ''));
|
||||
if ($encrypted !== '') {
|
||||
try {
|
||||
$plain = (new FhirEncryptionService())->decrypt($encrypted);
|
||||
} catch (\Throwable $e) {
|
||||
$plain = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($plain === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($plain, true);
|
||||
if (! is_array($decoded)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bundle = $decoded['bundle'] ?? $decoded['fhir_bundle'] ?? $decoded;
|
||||
|
||||
return is_array($bundle) && ($bundle['resourceType'] ?? '') === 'Bundle' ? $bundle : null;
|
||||
}
|
||||
|
||||
private function loadPatientDemographics($db, int $patientId): array
|
||||
{
|
||||
$out = ['name' => '', 'gender' => '', 'year_of_birth' => '', 'abha_address' => ''];
|
||||
if ($patientId <= 0 || ! $db->tableExists('patient_master')) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
$fields = $db->getFieldNames('patient_master') ?? [];
|
||||
$row = $db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray();
|
||||
if (! $row) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
$out['name'] = trim((string) ($row['p_fname'] ?? ''));
|
||||
$gender = (int) ($row['gender'] ?? 0);
|
||||
$out['gender'] = $gender === 1 ? 'M' : ($gender === 2 ? 'F' : '');
|
||||
|
||||
$dob = trim((string) ($row['dob'] ?? ''));
|
||||
if ($dob !== '' && $dob !== '0000-00-00') {
|
||||
$out['year_of_birth'] = substr($dob, 0, 4);
|
||||
}
|
||||
|
||||
if (in_array('abha_address', $fields, true)) {
|
||||
$out['abha_address'] = trim((string) ($row['abha_address'] ?? ''));
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,10 +153,10 @@ class AbdmConnector extends BaseConfig
|
|||
// ------------------------------------------------------------------
|
||||
public bool $autoLinkEnabled = true;
|
||||
|
||||
/** Default cooling period for OPD, Lab, Radiology, and all other HI types (in minutes). */
|
||||
public int $autoLinkDelayMinutes = 60;
|
||||
/** Default cooling period for OPD, Lab, Radiology, and all other HI types (in minutes). 0 = immediate 1-minute cron linking. */
|
||||
public int $autoLinkDelayMinutes = 0;
|
||||
|
||||
/** Cooling period for IPD Discharge Summary (in hours). */
|
||||
public int $autoLinkDelayDischargeHours = 24;
|
||||
/** Cooling period for IPD Discharge Summary (in hours). 0 = immediate 1-minute cron linking. */
|
||||
public int $autoLinkDelayDischargeHours = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2127,7 +2127,8 @@ class AbdmGateway extends BaseController
|
|||
string $abhaId = '',
|
||||
string $abhaAddressPost = '',
|
||||
string $consentHandle = '',
|
||||
string $careContextId = ''
|
||||
string $careContextId = '',
|
||||
bool $skipRemotePush = false
|
||||
): array {
|
||||
if ($labReqId <= 0) {
|
||||
return ['ok' => 0, 'error' => 'lab_req_id is required'];
|
||||
|
|
@ -2330,6 +2331,24 @@ class AbdmGateway extends BaseController
|
|||
'consent_handle' => $effectiveConsent,
|
||||
]);
|
||||
|
||||
if ($skipRemotePush) {
|
||||
if ($healthRecordId > 0 && $this->db->tableExists('health_records')) {
|
||||
$this->db->table('health_records')
|
||||
->where('id', $healthRecordId)
|
||||
->update([
|
||||
'push_status' => 'pending',
|
||||
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
return [
|
||||
'ok' => 1,
|
||||
'status' => 'queued_for_cron',
|
||||
'care_context_reference' => $ccRef,
|
||||
'health_record_id' => $healthRecordId,
|
||||
'message' => 'Diagnostic report stored locally and queued for 1-minute cron job link.',
|
||||
];
|
||||
}
|
||||
|
||||
if (! $hasAbha) {
|
||||
if ($healthRecordId > 0 && $this->db->tableExists('health_records')) {
|
||||
$this->db->table('health_records')
|
||||
|
|
@ -10121,7 +10140,7 @@ class AbdmGateway extends BaseController
|
|||
};
|
||||
}
|
||||
|
||||
public function pushAdditionalHiRecord(array $payload, int $patientId, string $abhaId, string $consentHandle = '')
|
||||
public function pushAdditionalHiRecord(array $payload, int $patientId, string $abhaId, string $consentHandle = '', bool $skipRemotePush = false)
|
||||
{
|
||||
$abhaIdentity = $this->resolvePatientAbhaIdentity($patientId, $abhaId);
|
||||
$abhaNumber = $abhaIdentity['abha_id'];
|
||||
|
|
@ -10157,6 +10176,24 @@ class AbdmGateway extends BaseController
|
|||
'consent_handle' => $effectiveConsent,
|
||||
]);
|
||||
|
||||
if ($skipRemotePush) {
|
||||
if ($healthRecordId > 0 && $this->db->tableExists('health_records')) {
|
||||
$this->db->table('health_records')->where('id', $healthRecordId)->update([
|
||||
'push_status' => 'pending',
|
||||
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
|
||||
$resp = [
|
||||
'ok' => 1,
|
||||
'status' => 'queued_for_cron',
|
||||
'health_record_id' => $healthRecordId,
|
||||
'care_context_reference' => $ccRef,
|
||||
'message' => 'Record saved locally and queued for 1-minute cron job link.',
|
||||
];
|
||||
return $this->response !== null ? $this->response->setJSON($resp) : $resp;
|
||||
}
|
||||
|
||||
if ($abhaId === '') {
|
||||
if ($healthRecordId > 0 && $this->db->tableExists('health_records')) {
|
||||
$this->db->table('health_records')->where('id', $healthRecordId)->update([
|
||||
|
|
@ -10499,7 +10536,7 @@ class AbdmGateway extends BaseController
|
|||
case 'diagnostic_report':
|
||||
case 'lab':
|
||||
case 'radiology':
|
||||
return $this->pushDiagnosisReportBundleInternal($entityId, $patientId);
|
||||
return $this->pushDiagnosisReportBundleInternal($entityId, $patientId, '', '', '', '', true);
|
||||
|
||||
case 'discharge':
|
||||
case 'discharge_summary':
|
||||
|
|
@ -10516,8 +10553,8 @@ class AbdmGateway extends BaseController
|
|||
if ($payload === null) {
|
||||
return ['ok' => 0, 'status' => 'payload_null'];
|
||||
}
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId);
|
||||
return ['ok' => 1, 'status' => 'pushed'];
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId, '', true);
|
||||
return ['ok' => 1, 'status' => 'queued_for_cron'];
|
||||
|
||||
case 'invoice':
|
||||
$abhaId = $patientId > 0 ? $this->resolvePatientAbhaIdentifier($patientId) : '';
|
||||
|
|
@ -10525,8 +10562,8 @@ class AbdmGateway extends BaseController
|
|||
if ($payload === null) {
|
||||
return ['ok' => 0, 'status' => 'payload_null'];
|
||||
}
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId);
|
||||
return ['ok' => 1, 'status' => 'pushed'];
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId, '', true);
|
||||
return ['ok' => 1, 'status' => 'queued_for_cron'];
|
||||
|
||||
case 'wellness':
|
||||
$abhaId = $patientId > 0 ? $this->resolvePatientAbhaIdentifier($patientId) : '';
|
||||
|
|
@ -10534,8 +10571,8 @@ class AbdmGateway extends BaseController
|
|||
if ($payload === null) {
|
||||
return ['ok' => 0, 'status' => 'payload_null'];
|
||||
}
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId);
|
||||
return ['ok' => 1, 'status' => 'pushed'];
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId, '', true);
|
||||
return ['ok' => 1, 'status' => 'queued_for_cron'];
|
||||
|
||||
case 'immunization':
|
||||
$abhaId = $patientId > 0 ? $this->resolvePatientAbhaIdentifier($patientId) : '';
|
||||
|
|
@ -10543,16 +10580,13 @@ class AbdmGateway extends BaseController
|
|||
if ($payload === null) {
|
||||
return ['ok' => 0, 'status' => 'payload_null'];
|
||||
}
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId);
|
||||
$res = $this->pushAdditionalHiRecord($payload, (int) ($payload['patient_id'] ?? $patientId), $abhaId, '', true);
|
||||
$hrId = 0;
|
||||
$isOk = false;
|
||||
if ($res instanceof \CodeIgniter\HTTP\ResponseInterface) {
|
||||
$resData = json_decode($res->getBody(), true);
|
||||
$hrId = (int) ($resData['health_record_id'] ?? 0);
|
||||
$isOk = ! empty($resData['ok']);
|
||||
} elseif (is_array($res)) {
|
||||
$hrId = (int) ($res['health_record_id'] ?? 0);
|
||||
$isOk = ! empty($res['ok']);
|
||||
}
|
||||
if ($entityId > 0 && $this->db->tableExists('immunization_records')) {
|
||||
$this->db->table('immunization_records')
|
||||
|
|
@ -10560,10 +10594,10 @@ class AbdmGateway extends BaseController
|
|||
->update([
|
||||
'abdm_care_context_reference' => (string) ($payload['care_context_reference'] ?? ('IMM-' . $entityId)),
|
||||
'abdm_health_record_id' => $hrId > 0 ? $hrId : null,
|
||||
'abdm_push_status' => $isOk ? 'linked' : 'queued',
|
||||
'abdm_push_status' => 'queued',
|
||||
]);
|
||||
}
|
||||
return ['ok' => 1, 'status' => 'pushed', 'health_record_id' => $hrId, 'care_context_reference' => $payload['care_context_reference']];
|
||||
return ['ok' => 1, 'status' => 'queued_for_cron', 'health_record_id' => $hrId, 'care_context_reference' => $payload['care_context_reference']];
|
||||
|
||||
default:
|
||||
return ['ok' => 0, 'status' => 'unknown_doc_type', 'message' => 'Unknown document type: ' . $docType];
|
||||
|
|
@ -10596,105 +10630,22 @@ class AbdmGateway extends BaseController
|
|||
return ['ok' => 0, 'status' => 'no_abha', 'message' => 'Patient has no ABHA'];
|
||||
}
|
||||
|
||||
// Ensure FHIR bundle is built and stored
|
||||
// Ensure FHIR bundle is built and stored locally
|
||||
if (class_exists('\App\Controllers\Opd_prescription')) {
|
||||
try {
|
||||
$rxCtrl = new \App\Controllers\Opd_prescription();
|
||||
$rxCtrl->storePrescriptionFhirBundle($opdId, $sessionId, $patientRow, $opdRow);
|
||||
$rxCtrl->createOpdPrescriptionWorkTask($opdId, $sessionId);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', '[autoPushPrescriptionRecord] storePrescriptionFhirBundle error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($sessionId <= 0 && $this->db->tableExists('opd_prescription')) {
|
||||
$presRow = $this->db->table('opd_prescription')->select('id')->where('opd_id', $opdId)->orderBy('id', 'DESC')->get(1)->getRowArray();
|
||||
if (! empty($presRow['id'])) {
|
||||
$sessionId = (int) $presRow['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$abhaIdentity = $this->resolvePatientAbhaIdentity($patientId, $abhaId);
|
||||
$abhaNumber = $abhaIdentity['abha_id'];
|
||||
$abhaAddress = $abhaIdentity['abha_address'];
|
||||
$patientBirthYear = $this->resolvePatientBirthYear($patientRow, $abhaAddress, $abhaNumber);
|
||||
$patientName = $this->patientDisplayName($patientRow);
|
||||
$visitDateRaw = trim((string) ($opdRow['date_opd_visit'] ?? $opdRow['apointment_date'] ?? ''));
|
||||
$visitDate = $visitDateRaw !== '' ? date('Y-m-d', strtotime($visitDateRaw)) : date('Y-m-d');
|
||||
$cleanDate = str_replace('-', '', $visitDate);
|
||||
|
||||
$results = [];
|
||||
|
||||
// 1. Push OPConsultRecord
|
||||
$opcDoc = $this->db->table('opd_fhir_documents')
|
||||
->where('opd_id', $opdId)
|
||||
->where('bundle_type', 'OPConsultRecord')
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! empty($opcDoc['bundle_json'])) {
|
||||
$opcBundle = json_decode((string) $opcDoc['bundle_json'], true);
|
||||
if (is_array($opcBundle)) {
|
||||
$ccRef = 'OPD-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
|
||||
$ccDisplay = 'OPD Consultation - ' . date('d M Y', strtotime($visitDate));
|
||||
try {
|
||||
$res = $this->connector->pushRecord([
|
||||
'patient_id' => (string) $patientId,
|
||||
'patient_name' => $patientName,
|
||||
'abha_id' => $abhaNumber,
|
||||
'abha_address' => $abhaAddress,
|
||||
'year_of_birth' => $patientBirthYear,
|
||||
'hi_type' => 'OPConsultRecord',
|
||||
'record_type' => 'OPConsultRecord',
|
||||
'visit_date' => $visitDate,
|
||||
'care_context_reference' => $ccRef,
|
||||
'care_context_display' => $ccDisplay,
|
||||
'notes' => $ccDisplay,
|
||||
'queue_id' => $ccRef,
|
||||
'record_data' => $opcBundle,
|
||||
]);
|
||||
$results['opd'] = $res;
|
||||
} catch (\Throwable $e) {
|
||||
$results['opd_error'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Push PrescriptionRecord
|
||||
$prescDoc = $this->db->table('opd_fhir_documents')
|
||||
->where('opd_id', $opdId)
|
||||
->where('bundle_type', 'PrescriptionRecord')
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! empty($prescDoc['bundle_json'])) {
|
||||
$prescBundle = json_decode((string) $prescDoc['bundle_json'], true);
|
||||
if (is_array($prescBundle)) {
|
||||
$prescCcRef = 'PRESC-' . $patientId . '-S' . $sessionId . '-' . $cleanDate;
|
||||
$prescCcDisplay = 'Prescription - ' . date('d M Y', strtotime($visitDate));
|
||||
try {
|
||||
$res = $this->connector->pushRecord([
|
||||
'patient_id' => (string) $patientId,
|
||||
'patient_name' => $patientName,
|
||||
'abha_id' => $abhaNumber,
|
||||
'abha_address' => $abhaAddress,
|
||||
'year_of_birth' => $patientBirthYear,
|
||||
'hi_type' => 'PrescriptionRecord',
|
||||
'record_type' => 'PrescriptionRecord',
|
||||
'visit_date' => $visitDate,
|
||||
'care_context_reference' => $prescCcRef,
|
||||
'care_context_display' => $prescCcDisplay,
|
||||
'notes' => $prescCcDisplay,
|
||||
'queue_id' => $prescCcRef,
|
||||
'record_data' => $prescBundle,
|
||||
]);
|
||||
$results['prescription'] = $res;
|
||||
} catch (\Throwable $e) {
|
||||
$results['prescription_error'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['ok' => 1, 'status' => 'processed', 'results' => $results];
|
||||
return [
|
||||
'ok' => 1,
|
||||
'status' => 'queued_for_cron',
|
||||
'message' => 'Prescription FHIR bundle saved and queued for 1-minute cron linking',
|
||||
];
|
||||
}
|
||||
|
||||
private function buildHealthDocumentRecordPayload(int $patientId, string $abhaId = '', int $recordId = 0, array $patientRow = []): ?array
|
||||
|
|
|
|||
|
|
@ -2130,17 +2130,6 @@ class Opd extends BaseController
|
|||
return $this->response->setStatusCode(404)->setBody('OPD not found');
|
||||
}
|
||||
|
||||
// Auto create/update FHIR bundle when printing consult form
|
||||
try {
|
||||
$resolvedSessionId = $sessionId > 0 ? $sessionId : (int) ($data['opd_prescription']->id ?? 0);
|
||||
$prescCtrl = new \App\Controllers\Opd_prescription();
|
||||
$patientRowArr = ! empty($data['patient_master'][0]) ? (array) $data['patient_master'][0] : null;
|
||||
$opdRowArr = ! empty($data['opd_master'][0]) ? (array) $data['opd_master'][0] : null;
|
||||
$prescCtrl->storePrescriptionFhirBundle($opdId, $resolvedSessionId, $patientRowArr, $opdRowArr);
|
||||
} catch (\Throwable $fhirEx) {
|
||||
log_message('warning', '[opd_lettre_print] FHIR bundle sync on print warning: ' . $fhirEx->getMessage());
|
||||
}
|
||||
|
||||
$data['print_layout_mode'] = $layoutMode;
|
||||
|
||||
return view('billing/opd_letter_head', $data);
|
||||
|
|
@ -2176,17 +2165,6 @@ class Opd extends BaseController
|
|||
return $this->response->setStatusCode(404)->setBody('OPD not found');
|
||||
}
|
||||
|
||||
// Auto create/update FHIR bundle when generating consult form PDF
|
||||
try {
|
||||
$resolvedSessionId = $sessionId > 0 ? $sessionId : (int) ($data['opd_prescription']->id ?? 0);
|
||||
$prescCtrl = new \App\Controllers\Opd_prescription();
|
||||
$patientRowArr = ! empty($data['patient_master'][0]) ? (array) $data['patient_master'][0] : null;
|
||||
$opdRowArr = ! empty($data['opd_master'][0]) ? (array) $data['opd_master'][0] : null;
|
||||
$prescCtrl->storePrescriptionFhirBundle($opdId, $resolvedSessionId, $patientRowArr, $opdRowArr);
|
||||
} catch (\Throwable $fhirEx) {
|
||||
log_message('warning', '[opd_lettre_pdf] FHIR bundle sync on PDF print warning: ' . $fhirEx->getMessage());
|
||||
}
|
||||
|
||||
$data['print_layout_mode'] = $layoutMode;
|
||||
|
||||
$isComposedTemplate = $templateKey !== '' && str_starts_with($templateKey, 'compose_');
|
||||
|
|
|
|||
|
|
@ -12057,16 +12057,7 @@ class Opd_prescription extends BaseController
|
|||
$this->db->table('health_records')
|
||||
->whereIn('care_context_reference', $redundantRefs)
|
||||
->delete();
|
||||
|
||||
// If patient has ABHA, auto-push to Bridge Gateway so care context is linked immediately
|
||||
try {
|
||||
$hasValidAbha = (str_contains($abhaAddress, '@') || (strlen(preg_replace('/\D/', '', $abhaAddress)) === 14));
|
||||
if ($hasValidAbha) {
|
||||
\App\Controllers\AbdmGateway::autoPushRecord('prescription', $opdId, $patientId, ['session_id' => $sessionId]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fail-safe
|
||||
}
|
||||
// Record is stored with push_status = pending and queued for the 1-minute cron job
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', '[storePrescriptionFhirBundle] auto health_records insert error: ' . $e->getMessage());
|
||||
}
|
||||
|
|
@ -13961,12 +13952,6 @@ OPD SNAPSHOT JSON: " . $payload;
|
|||
$this->createOpdPrescriptionWorkTask($opdId, $opdSessionId);
|
||||
$this->markOpdVisitedOnConsultCompletion((int) $opdId);
|
||||
|
||||
// Auto-link/push prescription to ABDM if patient has ABHA (fail-safe)
|
||||
try {
|
||||
\App\Controllers\AbdmGateway::autoPushRecord('prescription', $opdId, 0, ['session_id' => $opdSessionId]);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', 'ABDM Prescription print auto-link failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$printConfig = $this->resolvePrescriptionLayoutByDoctorField($opdId, $printType);
|
||||
$layoutMode = (string) ($printConfig['layout'] ?? 'content_only');
|
||||
|
|
|
|||
|
|
@ -635,6 +635,8 @@ class AbdmTaskBoardSyncService
|
|||
'immunization_record_publish',
|
||||
'wellness_record_publish',
|
||||
'health_document_publish',
|
||||
'invoice_record_publish',
|
||||
'invoice_publish',
|
||||
'ipd_discharge_publish',
|
||||
];
|
||||
|
||||
|
|
@ -806,6 +808,7 @@ class AbdmTaskBoardSyncService
|
|||
'immunization_record_publish' => 'ImmunizationRecord',
|
||||
'wellness_record_publish' => 'WellnessRecord',
|
||||
'health_document_publish' => 'HealthDocumentRecord',
|
||||
'invoice_record_publish', 'invoice_publish' => 'InvoiceRecord',
|
||||
'ipd_discharge_publish' => 'DischargeSummaryRecord',
|
||||
default => 'HealthDocumentRecord',
|
||||
};
|
||||
|
|
@ -825,6 +828,7 @@ class AbdmTaskBoardSyncService
|
|||
'immunization_record_publish' => 'IMM-',
|
||||
'wellness_record_publish' => 'WELLNESS-',
|
||||
'health_document_publish' => 'DOC-',
|
||||
'invoice_record_publish', 'invoice_publish' => 'INV-',
|
||||
'ipd_discharge_publish' => 'DISCHARGE-',
|
||||
default => 'REC-',
|
||||
};
|
||||
|
|
@ -935,6 +939,45 @@ class AbdmTaskBoardSyncService
|
|||
log_message('warning', '[processIndividualWorkTask] Immunization bundle generation error: ' . $ie->getMessage());
|
||||
}
|
||||
}
|
||||
} elseif (in_array($taskType, ['invoice_record_publish', 'invoice_publish'], true)) {
|
||||
if (empty($pushData['bundle']) && class_exists('\App\Controllers\AbdmGateway')) {
|
||||
try {
|
||||
$gw = new \App\Controllers\AbdmGateway();
|
||||
$invPayload = $gw->buildInvoiceRecordPayload((int) $entityId, $patientId, $abhaAddress !== '' ? $abhaAddress : $abhaNumber);
|
||||
if (! empty($invPayload['bundle'])) {
|
||||
$pushData['bundle'] = $invPayload['bundle'];
|
||||
$pushData['fhir_bundle'] = $invPayload['bundle'];
|
||||
$pushData['record_data'] = $invPayload['bundle'];
|
||||
$bundleJson = (string) json_encode($invPayload['bundle'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
} catch (\Throwable $inve) {
|
||||
log_message('warning', '[processIndividualWorkTask] Invoice bundle generation error: ' . $inve->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generic fallback: check if bundle already exists in health_records
|
||||
if (empty($pushData['bundle']) && $this->db->tableExists('health_records')) {
|
||||
$hr = $this->db->table('health_records')
|
||||
->where('entity_id', (string) $entityId)
|
||||
->where('patient_id', $patientId)
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! empty($hr['fhir_bundle'])) {
|
||||
$decoded = json_decode((string) $hr['fhir_bundle'], true);
|
||||
if (is_array($decoded) && ! empty($decoded)) {
|
||||
$pushData['bundle'] = $decoded;
|
||||
$pushData['fhir_bundle'] = $decoded;
|
||||
$pushData['record_data'] = $decoded;
|
||||
$bundleJson = (string) $hr['fhir_bundle'];
|
||||
if (! empty($hr['care_context_reference'])) {
|
||||
$careContextRef = (string) $hr['care_context_reference'];
|
||||
$pushData['care_context_reference'] = $careContextRef;
|
||||
$pushData['queue_id'] = $careContextRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -1093,13 +1136,25 @@ class AbdmTaskBoardSyncService
|
|||
|
||||
$typeLower = strtolower(trim($taskType));
|
||||
if (in_array($typeLower, ['ipd_discharge_publish', 'ipd_discharge', 'dischargesummaryrecord', 'ipd'], true)) {
|
||||
$hours = max(1, (int) ($cfg->autoLinkDelayDischargeHours ?? 24));
|
||||
$hours = max(0, (int) ($cfg->autoLinkDelayDischargeHours ?? 0));
|
||||
$requiredSeconds = $hours * 3600;
|
||||
} else {
|
||||
$minutes = max(1, (int) ($cfg->autoLinkDelayMinutes ?? 60));
|
||||
$minutes = max(0, (int) ($cfg->autoLinkDelayMinutes ?? 0));
|
||||
$requiredSeconds = $minutes * 60;
|
||||
}
|
||||
|
||||
if ($requiredSeconds <= 0) {
|
||||
return [
|
||||
'is_cooling_active' => false,
|
||||
'cooling_active' => false,
|
||||
'remaining_seconds' => 0,
|
||||
'remaining_minutes' => 0,
|
||||
'required_seconds' => 0,
|
||||
'last_modified' => (string) $lastModified,
|
||||
'auto_link_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
$lastTimestamp = ! empty($lastModified) ? strtotime($lastModified) : 0;
|
||||
if ($lastTimestamp <= 0) {
|
||||
$lastTimestamp = time();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user