Enhance IPD discharge summary FHIR bundle: dual admission/discharge vitals, full medication dosage directions, dietary/discharge advice care plans, and IPD invoice PDF attachment
This commit is contained in:
parent
4058850941
commit
4776bfb37b
|
|
@ -2747,22 +2747,22 @@ class AbdmGateway extends BaseController
|
|||
}
|
||||
|
||||
$medications = [];
|
||||
$legacyMeds = $this->ipdRows('ipd_discharge_prescrption_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days'], $ipdId);
|
||||
$legacyMeds = $this->ipdRows('ipd_discharge_prescrption_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'remark'], $ipdId);
|
||||
if (empty($legacyMeds)) {
|
||||
$legacyMeds = $this->ipdRows('ipd_discharge_prescription_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days'], $ipdId);
|
||||
$legacyMeds = $this->ipdRows('ipd_discharge_prescription_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'remark'], $ipdId);
|
||||
}
|
||||
foreach ($legacyMeds as $row) {
|
||||
$name = trim((string) ($row['med_name'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$dosageText = $this->resolveDischargeMedDosageLabel(
|
||||
(int) ($row['dosage'] ?? 0),
|
||||
(int) ($row['dosage_when'] ?? 0),
|
||||
(int) ($row['dosage_freq'] ?? 0)
|
||||
$dosage = $this->resolveDischargeMedDosageLabel(
|
||||
$row['dosage'] ?? '',
|
||||
$row['dosage_when'] ?? '',
|
||||
$row['dosage_freq'] ?? '',
|
||||
(string) ($row['no_of_days'] ?? ''),
|
||||
(string) ($row['remark'] ?? '')
|
||||
);
|
||||
$days = trim((string) ($row['no_of_days'] ?? ''));
|
||||
$dosage = trim($dosageText . (($days !== '' && $days !== '0') ? ' for ' . $days . ' days' : ''));
|
||||
$medications[] = ['name' => $name, 'dosage' => $dosage];
|
||||
}
|
||||
if (empty($medications)) {
|
||||
|
|
@ -2861,6 +2861,51 @@ class AbdmGateway extends BaseController
|
|||
];
|
||||
}
|
||||
|
||||
// Dietary Advice from food items
|
||||
$foodIds = is_array($instructionMeta['food_ids'] ?? null) ? ($instructionMeta['food_ids'] ?? []) : [];
|
||||
if (empty($foodIds) && $this->db->tableExists('ipd_discharge_drug_food_interaction')) {
|
||||
$legacyFoodRows = $this->ipdRows('ipd_discharge_drug_food_interaction', ['food_id_list'], $ipdId);
|
||||
if (! empty($legacyFoodRows)) {
|
||||
$foodIds = array_values(array_filter(array_map('trim', explode(',', (string) ($legacyFoodRows[0]['food_id_list'] ?? ''))), 'strlen'));
|
||||
}
|
||||
}
|
||||
if (! empty($foodIds) && $this->db->tableExists('ipd_discharge_master_food')) {
|
||||
$foodRows = $this->db->table('ipd_discharge_master_food')
|
||||
->select('id,food_short,food_desc,food_desc_lang')
|
||||
->whereIn('id', array_map('intval', $foodIds))
|
||||
->get()
|
||||
->getResultArray();
|
||||
$foodMap = [];
|
||||
foreach ($foodRows as $fr) {
|
||||
$foodMap[(int) $fr['id']] = $fr;
|
||||
}
|
||||
$foodLines = [];
|
||||
$fIndex = 1;
|
||||
foreach ($foodIds as $fid) {
|
||||
$fr = $foodMap[(int) $fid] ?? null;
|
||||
if (! $fr) {
|
||||
continue;
|
||||
}
|
||||
$heading = trim((string) ($fr['food_short'] ?? ''));
|
||||
$line = trim((string) ($fr['food_desc_lang'] ?? ''));
|
||||
if ($line === '') {
|
||||
$line = trim((string) ($fr['food_desc'] ?? ''));
|
||||
}
|
||||
if ($heading === '' && $line === '') {
|
||||
continue;
|
||||
}
|
||||
$foodLines[] = $fIndex . '. ' . ($heading !== '' ? $heading . ': ' : '') . $line;
|
||||
$fIndex++;
|
||||
}
|
||||
if (! empty($foodLines)) {
|
||||
$carePlans[] = [
|
||||
'title' => 'Dietary Advice',
|
||||
'description' => implode("\n", $foodLines),
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$otherAdvice = trim((string) ($instructionMeta['other_text'] ?? ''));
|
||||
if ($otherAdvice !== '') {
|
||||
$carePlans[] = [
|
||||
|
|
@ -2872,9 +2917,16 @@ class AbdmGateway extends BaseController
|
|||
|
||||
$reviewAfter = trim((string) ($instructionRow['review_after'] ?? ''));
|
||||
if ($reviewAfter !== '') {
|
||||
$reviewDate = '';
|
||||
if ($dischargeRaw !== '' && is_numeric($reviewAfter)) {
|
||||
$reviewTs = strtotime($dischargeRaw . ' +' . (int) $reviewAfter . ' days');
|
||||
if ($reviewTs !== false) {
|
||||
$reviewDate = ' (' . date('d-m-Y', $reviewTs) . ')';
|
||||
}
|
||||
}
|
||||
$carePlans[] = [
|
||||
'title' => 'Follow Up',
|
||||
'description' => 'Review After: ' . $reviewAfter,
|
||||
'description' => 'Review After: ' . $reviewAfter . ' Days' . $reviewDate . ' / as and when required',
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
|
|
@ -2963,10 +3015,27 @@ class AbdmGateway extends BaseController
|
|||
{
|
||||
$directory = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'abdm' . DIRECTORY_SEPARATOR . 'ipd' . DIRECTORY_SEPARATOR . $ipdId;
|
||||
$createdAt = $this->toIsoDateTimeOrNow($dischargeRaw);
|
||||
|
||||
// Ensure IPD Bill PDF is cached if missing
|
||||
$billPath = $directory . DIRECTORY_SEPARATOR . 'ipd-bill.pdf';
|
||||
if (! is_file($billPath) || filesize($billPath) === 0) {
|
||||
try {
|
||||
(new \App\Controllers\Billing\Ipd())->generateIpdBillPdfBinary($ipdId, 1, true);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Auto-generating IPD Bill PDF failed for IPD {ipd}: {msg}', [
|
||||
'ipd' => $ipdId,
|
||||
'msg' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$documents = [];
|
||||
foreach ([
|
||||
['file' => 'discharge-summary.pdf', 'title' => 'IPD Discharge Summary', 'loinc' => '18842-5'],
|
||||
] as $definition) {
|
||||
$definitions = [
|
||||
['file' => 'discharge-summary.pdf', 'title' => 'IPD Discharge Summary', 'loinc' => '18842-5', 'snomed' => '373942005'],
|
||||
['file' => 'ipd-bill.pdf', 'title' => 'IPD Bill / Invoice', 'loinc' => '75490-3', 'snomed' => '823651000000106'],
|
||||
];
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$path = $directory . DIRECTORY_SEPARATOR . $definition['file'];
|
||||
if (! is_file($path) || filesize($path) === 0) {
|
||||
continue;
|
||||
|
|
@ -3208,38 +3277,187 @@ class AbdmGateway extends BaseController
|
|||
return ! in_array($normalized, ['-', '--', 'na', 'n/a', 'nil', 'none', 'not done'], true);
|
||||
}
|
||||
|
||||
/** Resolve dose/when/freq IDs to a human-readable label for FHIR dosage text. */
|
||||
private function resolveDischargeMedDosageLabel(int $doseId, int $whenId, int $freqId): string
|
||||
/** Resolve dose/when/freq/days to a comprehensive human-readable label for FHIR dosage text. */
|
||||
private function resolveDischargeMedDosageLabel($doseVal, $whenVal, $freqVal, string $days = '', string $remark = ''): string
|
||||
{
|
||||
static $whenCodeDescMap = [
|
||||
'BF' => 'BF (BEFORE FOOD)', 'AF' => 'AF (AFTER FOOD)',
|
||||
'WF' => 'WF (WITH FOOD)', 'ES' => 'ES (EMPTY STOMACH)',
|
||||
'BBF' => 'BBF (BEFORE BREAKFAST)', 'ABF' => 'ABF (AFTER BREAKFAST)',
|
||||
'BL' => 'BL (BEFORE LUNCH)', 'AL' => 'AL (AFTER LUNCH)',
|
||||
'BD' => 'BD (BEFORE DINNER)', 'AD' => 'AD (AFTER DINNER)',
|
||||
'BT' => 'BT (BED TIME)',
|
||||
];
|
||||
static $whenHindiMap = [
|
||||
'BF' => 'भोजन से पहले', 'BEFORE FOOD' => 'भोजन से पहले',
|
||||
'AF' => 'भोजन के बाद', 'AFTER FOOD' => 'भोजन के बाद',
|
||||
'WF' => 'भोजन के साथ', 'WITH FOOD' => 'भोजन के साथ',
|
||||
'ES' => 'सुबह खाली पेट', 'EMPTY STOMACH' => 'सुबह खाली पेट',
|
||||
'BBF'=> 'नाश्ते से पहले', 'BEFORE BREAKFAST' => 'नाश्ते से पहले',
|
||||
'ABF'=> 'नाश्ते के बाद', 'AFTER BREAKFAST' => 'नाश्ते के बाद',
|
||||
'BL' => 'दोपहर के भोजन से पहले', 'BEFORE LUNCH' => 'दोपहर के भोजन से पहले',
|
||||
'AL' => 'दोपहर के भोजन के बाद', 'AFTER LUNCH' => 'दोपहर के भोजन के बाद',
|
||||
'BD' => 'रात के भोजन से पहले', 'BEFORE DINNER'=> 'रात के भोजन से पहले',
|
||||
'AD' => 'रात के भोजन के बाद', 'AFTER DINNER' => 'रात के भोजन के बाद',
|
||||
'BT' => 'रात को सोते समय', 'BED TIME' => 'रात को सोते समय',
|
||||
];
|
||||
static $freqHindiMap = [
|
||||
'OD' => 'दिन में एक बार (OD)', 'BD' => 'दिन में दो बार (BD)',
|
||||
'TDS' => 'दिन में तीन बार (TDS)', 'TID' => 'दिन में तीन बार (TID)',
|
||||
'QID' => 'दिन में चार बार (QID)', 'HS' => 'रात को सोते समय (HS)',
|
||||
'SOS' => 'ज़रूरत पड़ने पर (SOS)', 'STAT'=> 'तुरंत एक बार (STAT)',
|
||||
'ALTERNATE DAY' => 'एक दिन छोड़कर',
|
||||
'DAILY' => 'प्रतिदिन',
|
||||
'WEEKLY' => 'हफ़्ते में एक बार',
|
||||
'MONTHLY' => 'महीने में एक बार',
|
||||
];
|
||||
static $remarkHindiMap = [
|
||||
'TAKE WITH MILK' => 'दूध के साथ लें',
|
||||
'TAKE WITH WARM WATER' => 'गुनगुने पानी के साथ लें',
|
||||
'AVOID SOUR FOOD AND DAIRY PRODUCTS'=> 'खट्टा और डेयरी उत्पाद न लें',
|
||||
'TAKE AFTER MEALS' => 'भोजन के बाद लें',
|
||||
'TAKE ON AN EMPTY STOMACH EARLY MORNING' => 'सुबह खाली पेट लें',
|
||||
'CHEW WELL BEFORE SWALLOWING' => 'चबाकर खाएं',
|
||||
'DISSOLVE IN HALF GLASS OF WATER' => 'आधे गिलास पानी में घोलकर लें',
|
||||
'APPLY LOCALLY TWICE DAILY' => 'दिन में दो बार लगाएं',
|
||||
'DO NOT CRUSH OR CHEW TABLET' => 'गोली को तोड़े या चबाएं नहीं',
|
||||
'AVOID ALCOHOL WHILE TAKING THIS MEDICINE' => 'शराब का सेवन न करें',
|
||||
'COMPLETE FULL COURSE OF ANTIBIOTICS' => 'एंटीबायोटिक का पूरा कोर्स लें',
|
||||
'DRINK PLENTY OF FLUIDS / WATER' => 'प्रचुर मात्रा में पानी पिएं',
|
||||
];
|
||||
|
||||
static $doseCache = null;
|
||||
static $whenCache = null;
|
||||
static $freqCache = null;
|
||||
$load = function (string $table) {
|
||||
$map = [];
|
||||
if (! $this->db->tableExists($table)) {
|
||||
return $map;
|
||||
static $whenHindiCache = null;
|
||||
static $freqHindiCache = null;
|
||||
|
||||
if ($doseCache === null && $this->db->tableExists('opd_dose_shed')) {
|
||||
$doseCache = [];
|
||||
foreach ($this->db->table('opd_dose_shed')->select('id, dose_desc')->get()->getResultArray() as $r) {
|
||||
$doseCache[(int) $r['id']] = trim((string) ($r['dose_desc'] ?? ''));
|
||||
}
|
||||
$fields = $this->db->getFieldNames($table) ?? [];
|
||||
$labelField = in_array('dose_desc', $fields, true) ? 'dose_desc'
|
||||
: (in_array('freq_desc', $fields, true) ? 'freq_desc'
|
||||
: (in_array('when_desc', $fields, true) ? 'when_desc' : null));
|
||||
if ($labelField === null) {
|
||||
return $map;
|
||||
}
|
||||
if ($whenCache === null && $this->db->tableExists('opd_dose_when')) {
|
||||
$whenCache = [];
|
||||
$whenHindiCache = [];
|
||||
$fields = $this->db->getFieldNames('opd_dose_when') ?? [];
|
||||
$hasHindi = in_array('dose_sign_hindi', $fields, true);
|
||||
foreach ($this->db->table('opd_dose_when')->select('id, dose_sign' . ($hasHindi ? ', dose_sign_hindi' : ''))->get()->getResultArray() as $r) {
|
||||
$whenCache[(int) $r['id']] = trim((string) ($r['dose_sign'] ?? ''));
|
||||
if ($hasHindi) {
|
||||
$whenHindiCache[(int) $r['id']] = trim((string) ($r['dose_sign_hindi'] ?? ''));
|
||||
}
|
||||
}
|
||||
foreach ($this->db->table($table)->select('id,' . $labelField)->get()->getResultArray() as $r) {
|
||||
$map[(int) $r['id']] = trim((string) $r[$labelField]);
|
||||
}
|
||||
if ($freqCache === null && $this->db->tableExists('opd_dose_frequency')) {
|
||||
$freqCache = [];
|
||||
$freqHindiCache = [];
|
||||
$fields = $this->db->getFieldNames('opd_dose_frequency') ?? [];
|
||||
$hasHindi = in_array('dose_sign_hindi', $fields, true);
|
||||
foreach ($this->db->table('opd_dose_frequency')->select('id, dose_sign' . ($hasHindi ? ', dose_sign_hindi' : ''))->get()->getResultArray() as $r) {
|
||||
$freqCache[(int) $r['id']] = trim((string) ($r['dose_sign'] ?? ''));
|
||||
if ($hasHindi) {
|
||||
$freqHindiCache[(int) $r['id']] = trim((string) ($r['dose_sign_hindi'] ?? ''));
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
};
|
||||
if ($doseCache === null) { $doseCache = $load('opd_dose_shed'); }
|
||||
if ($whenCache === null) { $whenCache = $load('opd_dose_when'); }
|
||||
if ($freqCache === null) { $freqCache = $load('opd_dose_frequency'); }
|
||||
$parts = array_filter([
|
||||
$doseId > 0 ? ($doseCache[$doseId] ?? '') : '',
|
||||
$whenId > 0 ? ($whenCache[$whenId] ?? '') : '',
|
||||
$freqId > 0 ? ($freqCache[$freqId] ?? '') : '',
|
||||
], fn ($v) => $v !== '');
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
$doseText = '';
|
||||
if (is_numeric($doseVal) && (int) $doseVal > 0) {
|
||||
$doseText = $doseCache[(int) $doseVal] ?? '';
|
||||
} elseif (is_string($doseVal) && trim($doseVal) !== '' && trim($doseVal) !== '0') {
|
||||
$doseText = trim($doseVal);
|
||||
}
|
||||
|
||||
$whenText = '';
|
||||
$whenHindi = '';
|
||||
if (is_numeric($whenVal) && (int) $whenVal > 0) {
|
||||
$whenText = $whenCache[(int) $whenVal] ?? '';
|
||||
$whenHindi = $whenHindiCache[(int) $whenVal] ?? '';
|
||||
} elseif (is_string($whenVal) && trim($whenVal) !== '' && trim($whenVal) !== '0') {
|
||||
$whenText = trim($whenVal);
|
||||
}
|
||||
if ($whenText !== '') {
|
||||
$upperWhen = strtoupper($whenText);
|
||||
if (isset($whenCodeDescMap[$upperWhen])) {
|
||||
$whenText = $whenCodeDescMap[$upperWhen];
|
||||
}
|
||||
if ($whenHindi === '' && isset($whenHindiMap[$upperWhen])) {
|
||||
$whenHindi = $whenHindiMap[$upperWhen];
|
||||
}
|
||||
}
|
||||
|
||||
$freqText = '';
|
||||
$freqHindi = '';
|
||||
if (is_numeric($freqVal) && (int) $freqVal > 0) {
|
||||
$freqText = $freqCache[(int) $freqVal] ?? '';
|
||||
$freqHindi = $freqHindiCache[(int) $freqVal] ?? '';
|
||||
} elseif (is_string($freqVal) && trim($freqVal) !== '' && trim($freqVal) !== '0') {
|
||||
$freqText = trim($freqVal);
|
||||
}
|
||||
if ($freqText !== '') {
|
||||
$upperFreq = strtoupper($freqText);
|
||||
if ($freqHindi === '' && isset($freqHindiMap[$upperFreq])) {
|
||||
$freqHindi = $freqHindiMap[$upperFreq];
|
||||
}
|
||||
}
|
||||
|
||||
$cleanRemark = (string) preg_replace('/^(edit\s*remove|remove\s*edit|edit|remove|delete)\s*$/i', '', trim($remark));
|
||||
$cleanRemark = trim($cleanRemark);
|
||||
$remarkHindi = '';
|
||||
if ($cleanRemark !== '') {
|
||||
$upperRemark = strtoupper($cleanRemark);
|
||||
if (isset($remarkHindiMap[$upperRemark])) {
|
||||
$remarkHindi = $remarkHindiMap[$upperRemark];
|
||||
}
|
||||
}
|
||||
|
||||
$dirParts = [];
|
||||
$dirLocalParts = [];
|
||||
|
||||
if ($whenText !== '') {
|
||||
$dirParts[] = $whenText;
|
||||
}
|
||||
if ($whenHindi !== '') {
|
||||
$dirLocalParts[] = $whenHindi;
|
||||
}
|
||||
|
||||
if ($doseText !== '') {
|
||||
$dirParts[] = $doseText;
|
||||
}
|
||||
|
||||
if ($freqText !== '') {
|
||||
$dirParts[] = $freqText;
|
||||
}
|
||||
if ($freqHindi !== '') {
|
||||
$dirLocalParts[] = $freqHindi;
|
||||
}
|
||||
|
||||
if ($cleanRemark !== '') {
|
||||
$dirParts[] = $cleanRemark;
|
||||
}
|
||||
if ($remarkHindi !== '') {
|
||||
$dirLocalParts[] = $remarkHindi;
|
||||
}
|
||||
|
||||
$daysClean = trim($days);
|
||||
if ($daysClean !== '' && $daysClean !== '0') {
|
||||
$daysNum = is_numeric($daysClean) ? (int) $daysClean : 0;
|
||||
$dirParts[] = $daysNum > 0 ? ('for ' . $daysNum . ' days') : ('for ' . $daysClean);
|
||||
if ($daysNum > 0) {
|
||||
$dirLocalParts[] = $daysNum . ' दिन';
|
||||
}
|
||||
}
|
||||
|
||||
$engText = implode(' | ', array_values(array_unique(array_filter($dirParts))));
|
||||
$hinText = implode(' | ', array_values(array_unique(array_filter($dirLocalParts))));
|
||||
|
||||
if ($engText !== '' && $hinText !== '') {
|
||||
return $engText . ' | ' . $hinText;
|
||||
}
|
||||
|
||||
return $engText !== '' ? $engText : $hinText;
|
||||
}
|
||||
|
||||
private function normalizeFhirGender(string $gender): string
|
||||
|
|
|
|||
|
|
@ -941,9 +941,34 @@ class Ipd extends BaseController
|
|||
return $permission;
|
||||
}
|
||||
|
||||
$showHeader = $this->request->getGet('header') !== '0';
|
||||
$pdfBinary = $this->generateIpdBillPdfBinary($ipdId, $mode, $showHeader);
|
||||
if ($pdfBinary === null) {
|
||||
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
||||
}
|
||||
|
||||
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
||||
$ipdCode = (string) ($panelData['ipd_info']->ipd_code ?? ('IPD-' . $ipdId));
|
||||
$fileName = 'IPD_Bill_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '_M' . $mode . '.pdf';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
||||
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->setHeader('Pragma', 'no-cache')
|
||||
->setHeader('Expires', '0')
|
||||
->setBody($pdfBinary);
|
||||
}
|
||||
|
||||
public function generateIpdBillPdfBinary(int $ipdId, int $mode = 1, bool $showHeader = true): ?string
|
||||
{
|
||||
if ($ipdId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
||||
if (empty($panelData)) {
|
||||
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Recalculate before print so PDF always reflects latest bill math.
|
||||
|
|
@ -951,10 +976,7 @@ class Ipd extends BaseController
|
|||
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
||||
|
||||
$mode = max(1, min(6, $mode));
|
||||
|
||||
// Get header parameter from query string (default: show header)
|
||||
$showHeader = $this->request->getGet('header') !== '0';
|
||||
|
||||
|
||||
$data = $this->buildBillDetailsTabData($panelData, $ipdId);
|
||||
$data['show_print_actions'] = false;
|
||||
$data['show_payment_details'] = in_array($mode, [1, 6], true);
|
||||
|
|
@ -973,7 +995,7 @@ class Ipd extends BaseController
|
|||
// When header is hidden (letterhead mode), use larger top margin
|
||||
$marginTop = $showHeader ? 8 : 50; // 50mm = ~5cm for letterhead
|
||||
$marginBottom = $showHeader ? 8 : 20; // 20mm = 2cm
|
||||
|
||||
|
||||
$mpdf = new Mpdf([
|
||||
'tempDir' => $mpdfTempDir,
|
||||
'format' => 'A4',
|
||||
|
|
@ -989,15 +1011,12 @@ class Ipd extends BaseController
|
|||
$fileName = 'IPD_Bill_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '_M' . $mode . '.pdf';
|
||||
|
||||
$pdfBinary = $mpdf->Output($fileName, 'S');
|
||||
$this->cacheAbdmIpdBillPdf($ipdId, $pdfBinary);
|
||||
if (is_string($pdfBinary) && str_starts_with($pdfBinary, '%PDF-')) {
|
||||
$this->cacheAbdmIpdBillPdf($ipdId, $pdfBinary);
|
||||
return $pdfBinary;
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
||||
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->setHeader('Pragma', 'no-cache')
|
||||
->setHeader('Expires', '0')
|
||||
->setBody($pdfBinary);
|
||||
return null;
|
||||
}
|
||||
|
||||
private function cacheAbdmIpdBillPdf(int $ipdId, string $pdfBinary): void
|
||||
|
|
|
|||
|
|
@ -8694,6 +8694,235 @@ class Ipd_discharge extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
private function buildIpdPdfDocumentsList(int $ipdId, string $dischargeRaw): array
|
||||
{
|
||||
$directory = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'abdm' . DIRECTORY_SEPARATOR . 'ipd' . DIRECTORY_SEPARATOR . $ipdId;
|
||||
$createdAt = $this->toIsoDateTimeOrNow($dischargeRaw);
|
||||
|
||||
// Ensure IPD Bill PDF is cached if missing
|
||||
$billPath = $directory . DIRECTORY_SEPARATOR . 'ipd-bill.pdf';
|
||||
if (! is_file($billPath) || filesize($billPath) === 0) {
|
||||
try {
|
||||
(new \App\Controllers\Billing\Ipd())->generateIpdBillPdfBinary($ipdId, 1, true);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Auto-generating IPD Bill PDF failed for IPD {ipd}: {msg}', [
|
||||
'ipd' => $ipdId,
|
||||
'msg' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$documents = [];
|
||||
$definitions = [
|
||||
['file' => 'discharge-summary.pdf', 'title' => 'IPD Discharge Summary', 'loinc' => '18842-5', 'snomed' => '373942005'],
|
||||
['file' => 'ipd-bill.pdf', 'title' => 'IPD Bill / Invoice', 'loinc' => '75490-3', 'snomed' => '823651000000106'],
|
||||
];
|
||||
|
||||
foreach ($definitions as $definition) {
|
||||
$path = $directory . DIRECTORY_SEPARATOR . $definition['file'];
|
||||
if (! is_file($path) || filesize($path) === 0) {
|
||||
continue;
|
||||
}
|
||||
$binary = file_get_contents($path);
|
||||
if (! is_string($binary) || ! str_starts_with($binary, '%PDF-')) {
|
||||
continue;
|
||||
}
|
||||
$documents[] = [
|
||||
'title' => $definition['title'],
|
||||
'loinc_code' => $definition['loinc'],
|
||||
'snomed_code' => $definition['snomed'],
|
||||
'content_type' => 'application/pdf',
|
||||
'data' => base64_encode($binary),
|
||||
'created_at' => $createdAt,
|
||||
'path' => $path,
|
||||
];
|
||||
}
|
||||
|
||||
return $documents;
|
||||
}
|
||||
|
||||
private function formatDischargeMedDirections($doseVal, $whenVal, $freqVal, string $days = '', string $remark = ''): string
|
||||
{
|
||||
static $whenCodeDescMap = [
|
||||
'BF' => 'BF (BEFORE FOOD)', 'AF' => 'AF (AFTER FOOD)',
|
||||
'WF' => 'WF (WITH FOOD)', 'ES' => 'ES (EMPTY STOMACH)',
|
||||
'BBF' => 'BBF (BEFORE BREAKFAST)', 'ABF' => 'ABF (AFTER BREAKFAST)',
|
||||
'BL' => 'BL (BEFORE LUNCH)', 'AL' => 'AL (AFTER LUNCH)',
|
||||
'BD' => 'BD (BEFORE DINNER)', 'AD' => 'AD (AFTER DINNER)',
|
||||
'BT' => 'BT (BED TIME)',
|
||||
];
|
||||
static $whenHindiMap = [
|
||||
'BF' => 'भोजन से पहले', 'BEFORE FOOD' => 'भोजन से पहले',
|
||||
'AF' => 'भोजन के बाद', 'AFTER FOOD' => 'भोजन के बाद',
|
||||
'WF' => 'भोजन के साथ', 'WITH FOOD' => 'भोजन के साथ',
|
||||
'ES' => 'सुबह खाली पेट', 'EMPTY STOMACH' => 'सुबह खाली पेट',
|
||||
'BBF'=> 'नाश्ते से पहले', 'BEFORE BREAKFAST' => 'नाश्ते से पहले',
|
||||
'ABF'=> 'नाश्ते के बाद', 'AFTER BREAKFAST' => 'नाश्ते के बाद',
|
||||
'BL' => 'दोपहर के भोजन से पहले', 'BEFORE LUNCH' => 'दोपहर के भोजन से पहले',
|
||||
'AL' => 'दोपहर के भोजन के बाद', 'AFTER LUNCH' => 'दोपहर के भोजन के बाद',
|
||||
'BD' => 'रात के भोजन से पहले', 'BEFORE DINNER'=> 'रात के भोजन से पहले',
|
||||
'AD' => 'रात के भोजन के बाद', 'AFTER DINNER' => 'रात के भोजन के बाद',
|
||||
'BT' => 'रात को सोते समय', 'BED TIME' => 'रात को सोते समय',
|
||||
];
|
||||
static $freqHindiMap = [
|
||||
'OD' => 'दिन में एक बार (OD)', 'BD' => 'दिन में दो बार (BD)',
|
||||
'TDS' => 'दिन में तीन बार (TDS)', 'TID' => 'दिन में तीन बार (TID)',
|
||||
'QID' => 'दिन में चार बार (QID)', 'HS' => 'रात को सोते समय (HS)',
|
||||
'SOS' => 'ज़रूरत पड़ने पर (SOS)', 'STAT'=> 'तुरंत एक बार (STAT)',
|
||||
'ALTERNATE DAY' => 'एक दिन छोड़कर',
|
||||
'DAILY' => 'प्रतिदिन',
|
||||
'WEEKLY' => 'हफ़्ते में एक बार',
|
||||
'MONTHLY' => 'महीने में एक बार',
|
||||
];
|
||||
static $remarkHindiMap = [
|
||||
'TAKE WITH MILK' => 'दूध के साथ लें',
|
||||
'TAKE WITH WARM WATER' => 'गुनगुने पानी के साथ लें',
|
||||
'AVOID SOUR FOOD AND DAIRY PRODUCTS'=> 'खट्टा और डेयरी उत्पाद न लें',
|
||||
'TAKE AFTER MEALS' => 'भोजन के बाद लें',
|
||||
'TAKE ON AN EMPTY STOMACH EARLY MORNING' => 'सुबह खाली पेट लें',
|
||||
'CHEW WELL BEFORE SWALLOWING' => 'चबाकर खाएं',
|
||||
'DISSOLVE IN HALF GLASS OF WATER' => 'आधे गिलास पानी में घोलकर लें',
|
||||
'APPLY LOCALLY TWICE DAILY' => 'दिन में दो बार लगाएं',
|
||||
'DO NOT CRUSH OR CHEW TABLET' => 'गोली को तोड़े या चबाएं नहीं',
|
||||
'AVOID ALCOHOL WHILE TAKING THIS MEDICINE' => 'शराब का सेवन न करें',
|
||||
'COMPLETE FULL COURSE OF ANTIBIOTICS' => 'एंटीबायोटिक का पूरा कोर्स लें',
|
||||
'DRINK PLENTY OF FLUIDS / WATER' => 'प्रचुर मात्रा में पानी पिएं',
|
||||
];
|
||||
|
||||
static $doseCache = null;
|
||||
static $whenCache = null;
|
||||
static $freqCache = null;
|
||||
static $whenHindiCache = null;
|
||||
static $freqHindiCache = null;
|
||||
|
||||
if ($doseCache === null && $this->db->tableExists('opd_dose_shed')) {
|
||||
$doseCache = [];
|
||||
foreach ($this->db->table('opd_dose_shed')->select('id, dose_desc')->get()->getResultArray() as $r) {
|
||||
$doseCache[(int) $r['id']] = trim((string) ($r['dose_desc'] ?? ''));
|
||||
}
|
||||
}
|
||||
if ($whenCache === null && $this->db->tableExists('opd_dose_when')) {
|
||||
$whenCache = [];
|
||||
$whenHindiCache = [];
|
||||
$fields = $this->db->getFieldNames('opd_dose_when') ?? [];
|
||||
$hasHindi = in_array('dose_sign_hindi', $fields, true);
|
||||
foreach ($this->db->table('opd_dose_when')->select('id, dose_sign' . ($hasHindi ? ', dose_sign_hindi' : ''))->get()->getResultArray() as $r) {
|
||||
$whenCache[(int) $r['id']] = trim((string) ($r['dose_sign'] ?? ''));
|
||||
if ($hasHindi) {
|
||||
$whenHindiCache[(int) $r['id']] = trim((string) ($r['dose_sign_hindi'] ?? ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($freqCache === null && $this->db->tableExists('opd_dose_frequency')) {
|
||||
$freqCache = [];
|
||||
$freqHindiCache = [];
|
||||
$fields = $this->db->getFieldNames('opd_dose_frequency') ?? [];
|
||||
$hasHindi = in_array('dose_sign_hindi', $fields, true);
|
||||
foreach ($this->db->table('opd_dose_frequency')->select('id, dose_sign' . ($hasHindi ? ', dose_sign_hindi' : ''))->get()->getResultArray() as $r) {
|
||||
$freqCache[(int) $r['id']] = trim((string) ($r['dose_sign'] ?? ''));
|
||||
if ($hasHindi) {
|
||||
$freqHindiCache[(int) $r['id']] = trim((string) ($r['dose_sign_hindi'] ?? ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$doseText = '';
|
||||
if (is_numeric($doseVal) && (int) $doseVal > 0) {
|
||||
$doseText = $doseCache[(int) $doseVal] ?? '';
|
||||
} elseif (is_string($doseVal) && trim($doseVal) !== '' && trim($doseVal) !== '0') {
|
||||
$doseText = trim($doseVal);
|
||||
}
|
||||
|
||||
$whenText = '';
|
||||
$whenHindi = '';
|
||||
if (is_numeric($whenVal) && (int) $whenVal > 0) {
|
||||
$whenText = $whenCache[(int) $whenVal] ?? '';
|
||||
$whenHindi = $whenHindiCache[(int) $whenVal] ?? '';
|
||||
} elseif (is_string($whenVal) && trim($whenVal) !== '' && trim($whenVal) !== '0') {
|
||||
$whenText = trim($whenVal);
|
||||
}
|
||||
if ($whenText !== '') {
|
||||
$upperWhen = strtoupper($whenText);
|
||||
if (isset($whenCodeDescMap[$upperWhen])) {
|
||||
$whenText = $whenCodeDescMap[$upperWhen];
|
||||
}
|
||||
if ($whenHindi === '' && isset($whenHindiMap[$upperWhen])) {
|
||||
$whenHindi = $whenHindiMap[$upperWhen];
|
||||
}
|
||||
}
|
||||
|
||||
$freqText = '';
|
||||
$freqHindi = '';
|
||||
if (is_numeric($freqVal) && (int) $freqVal > 0) {
|
||||
$freqText = $freqCache[(int) $freqVal] ?? '';
|
||||
$freqHindi = $freqHindiCache[(int) $freqVal] ?? '';
|
||||
} elseif (is_string($freqVal) && trim($freqVal) !== '' && trim($freqVal) !== '0') {
|
||||
$freqText = trim($freqVal);
|
||||
}
|
||||
if ($freqText !== '') {
|
||||
$upperFreq = strtoupper($freqText);
|
||||
if ($freqHindi === '' && isset($freqHindiMap[$upperFreq])) {
|
||||
$freqHindi = $freqHindiMap[$upperFreq];
|
||||
}
|
||||
}
|
||||
|
||||
$cleanRemark = (string) preg_replace('/^(edit\s*remove|remove\s*edit|edit|remove|delete)\s*$/i', '', trim($remark));
|
||||
$cleanRemark = trim($cleanRemark);
|
||||
$remarkHindi = '';
|
||||
if ($cleanRemark !== '') {
|
||||
$upperRemark = strtoupper($cleanRemark);
|
||||
if (isset($remarkHindiMap[$upperRemark])) {
|
||||
$remarkHindi = $remarkHindiMap[$upperRemark];
|
||||
}
|
||||
}
|
||||
|
||||
$dirParts = [];
|
||||
$dirLocalParts = [];
|
||||
|
||||
if ($whenText !== '') {
|
||||
$dirParts[] = $whenText;
|
||||
}
|
||||
if ($whenHindi !== '') {
|
||||
$dirLocalParts[] = $whenHindi;
|
||||
}
|
||||
|
||||
if ($doseText !== '') {
|
||||
$dirParts[] = $doseText;
|
||||
}
|
||||
|
||||
if ($freqText !== '') {
|
||||
$dirParts[] = $freqText;
|
||||
}
|
||||
if ($freqHindi !== '') {
|
||||
$dirLocalParts[] = $freqHindi;
|
||||
}
|
||||
|
||||
if ($cleanRemark !== '') {
|
||||
$dirParts[] = $cleanRemark;
|
||||
}
|
||||
if ($remarkHindi !== '') {
|
||||
$dirLocalParts[] = $remarkHindi;
|
||||
}
|
||||
|
||||
$daysClean = trim($days);
|
||||
if ($daysClean !== '' && $daysClean !== '0') {
|
||||
$daysNum = is_numeric($daysClean) ? (int) $daysClean : 0;
|
||||
$dirParts[] = $daysNum > 0 ? ('for ' . $daysNum . ' days') : ('for ' . $daysClean);
|
||||
if ($daysNum > 0) {
|
||||
$dirLocalParts[] = $daysNum . ' दिन';
|
||||
}
|
||||
}
|
||||
|
||||
$engText = implode(' | ', array_values(array_unique(array_filter($dirParts))));
|
||||
$hinText = implode(' | ', array_values(array_unique(array_filter($dirLocalParts))));
|
||||
|
||||
if ($engText !== '' && $hinText !== '') {
|
||||
return $engText . ' | ' . $hinText;
|
||||
}
|
||||
|
||||
return $engText !== '' ? $engText : $hinText;
|
||||
}
|
||||
|
||||
public function show_file3(int $ipdId)
|
||||
{
|
||||
return $this->show_discharge($ipdId, 3);
|
||||
|
|
@ -8870,29 +9099,22 @@ class Ipd_discharge extends BaseController
|
|||
}
|
||||
|
||||
$medicationRows = [];
|
||||
$legacyMeds = $this->byIpdRows('ipd_discharge_prescrption_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days'], 'id ASC', $ipdId);
|
||||
$legacyMeds = $this->byIpdRows('ipd_discharge_prescrption_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'remark'], 'id ASC', $ipdId);
|
||||
if (empty($legacyMeds)) {
|
||||
$legacyMeds = $this->byIpdRows('ipd_discharge_prescription_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days'], 'id ASC', $ipdId);
|
||||
$legacyMeds = $this->byIpdRows('ipd_discharge_prescription_prescribed', ['med_name', 'dosage', 'dosage_when', 'dosage_freq', 'no_of_days', 'remark'], 'id ASC', $ipdId);
|
||||
}
|
||||
foreach ($legacyMeds as $row) {
|
||||
$name = trim((string) ($row['med_name'] ?? ''));
|
||||
if (! $this->isMeaningfulDischargeValue($name)) {
|
||||
continue;
|
||||
}
|
||||
$dosageDisplay = $this->buildDosageDisplay(
|
||||
(int) ($row['dosage'] ?? 0),
|
||||
(int) ($row['dosage_when'] ?? 0),
|
||||
(int) ($row['dosage_freq'] ?? 0)
|
||||
$dose = $this->formatDischargeMedDirections(
|
||||
$row['dosage'] ?? '',
|
||||
$row['dosage_when'] ?? '',
|
||||
$row['dosage_freq'] ?? '',
|
||||
(string) ($row['no_of_days'] ?? ''),
|
||||
(string) ($row['remark'] ?? '')
|
||||
);
|
||||
$days = trim((string) ($row['no_of_days'] ?? ''));
|
||||
$doseParts = [];
|
||||
if ($this->isMeaningfulDischargeValue($dosageDisplay)) {
|
||||
$doseParts[] = $dosageDisplay;
|
||||
}
|
||||
if ($this->isMeaningfulDischargeValue($days)) {
|
||||
$doseParts[] = 'for ' . $days . ' days';
|
||||
}
|
||||
$dose = implode(' ', $doseParts);
|
||||
$medicationRows[] = ['name' => $name, 'dosage' => $dose];
|
||||
}
|
||||
if (empty($medicationRows)) {
|
||||
|
|
@ -8909,6 +9131,186 @@ class Ipd_discharge extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
$observationRows = [];
|
||||
if ($this->tableHasColumns('ipd_discharge_1_b', ['ipd_d_id'])) {
|
||||
$cols = $this->db->getFieldNames('ipd_discharge_1_b') ?? [];
|
||||
$sel = in_array('short_head', $cols, true) ? 'short_head,rdata' : '*';
|
||||
foreach ($this->db->table('ipd_discharge_1_b')->select($sel)->where('ipd_d_id', $ipdId)->get()->getResultArray() as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || ! $this->isMeaningfulDischargeValue($value)) {
|
||||
continue;
|
||||
}
|
||||
$observationRows[] = [
|
||||
'text' => $label,
|
||||
'value' => $value,
|
||||
'category' => 'Condition on Admission Time',
|
||||
'category_code' => 'vital-signs',
|
||||
'effective_at' => $admissionIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($this->tableHasColumns('ipd_discharge_1_b_final', ['ipd_d_id'])) {
|
||||
$cols = $this->db->getFieldNames('ipd_discharge_1_b_final') ?? [];
|
||||
$sel = in_array('short_head', $cols, true) ? 'short_head,rdata' : '*';
|
||||
foreach ($this->db->table('ipd_discharge_1_b_final')->select($sel)->where('ipd_d_id', $ipdId)->get()->getResultArray() as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || ! $this->isMeaningfulDischargeValue($value)) {
|
||||
continue;
|
||||
}
|
||||
$observationRows[] = [
|
||||
'text' => $label,
|
||||
'value' => $value,
|
||||
'category' => 'Condition on Discharge Time',
|
||||
'category_code' => 'vital-signs',
|
||||
'effective_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$investigationRows = [];
|
||||
if ($this->tableHasColumns('ipd_discharge_1_d', ['ipd_d_id'])) {
|
||||
$cols = $this->db->getFieldNames('ipd_discharge_1_d') ?? [];
|
||||
$sel = in_array('short_head', $cols, true) ? 'short_head,rdata' : '*';
|
||||
foreach ($this->db->table('ipd_discharge_1_d')->select($sel)->where('ipd_d_id', $ipdId)->get()->getResultArray() as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || ! $this->isMeaningfulDischargeValue($value)) {
|
||||
continue;
|
||||
}
|
||||
$investigationRows[] = [
|
||||
'text' => $label . ': ' . $value,
|
||||
'authored_on' => $admissionIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($this->tableHasColumns('ipd_discharge_1_e', ['ipd_d_id'])) {
|
||||
$cols = $this->db->getFieldNames('ipd_discharge_1_e') ?? [];
|
||||
$sel = in_array('short_head', $cols, true) ? 'short_head,rdata' : '*';
|
||||
foreach ($this->db->table('ipd_discharge_1_e')->select($sel)->where('ipd_d_id', $ipdId)->get()->getResultArray() as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || ! $this->isMeaningfulDischargeValue($value)) {
|
||||
continue;
|
||||
}
|
||||
$investigationRows[] = [
|
||||
'text' => $label . ': ' . $value,
|
||||
'authored_on' => $admissionIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$allergyRows = [];
|
||||
$carePlanRows = [];
|
||||
$instructionRows = $this->byIpdRows('ipd_discharge_instructions', ['comp_report', 'comp_remark', 'review_after'], 'id DESC', $ipdId);
|
||||
if (! empty($instructionRows)) {
|
||||
$instructionRow = $instructionRows[0];
|
||||
$instructionMeta = $this->parseInstructionMetaPayload((string) ($instructionRow['comp_report'] ?? ''));
|
||||
$nabhMeta = is_array($instructionMeta['nabh'] ?? null) ? $instructionMeta['nabh'] : [];
|
||||
|
||||
$allergyStatus = strtolower(trim((string) ($nabhMeta['drug_allergy_status'] ?? '')));
|
||||
$allergyDetails = trim((string) ($nabhMeta['drug_allergy_details'] ?? ''));
|
||||
if ($allergyStatus !== '' || $allergyDetails !== '') {
|
||||
$allergyRows[] = [
|
||||
'text' => $allergyDetails !== '' ? $allergyDetails : ('Drug allergy status: ' . $allergyStatus),
|
||||
'note' => $allergyStatus,
|
||||
'criticality' => $allergyStatus === 'yes' ? 'high' : 'low',
|
||||
'recorded_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
|
||||
$dischargeAdvice = trim((string) ($instructionRow['comp_remark'] ?? ''));
|
||||
if ($dischargeAdvice !== '') {
|
||||
$carePlanRows[] = [
|
||||
'title' => 'Discharge Advice',
|
||||
'description' => $dischargeAdvice,
|
||||
'created_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
|
||||
$foodIds = is_array($instructionMeta['food_ids'] ?? null) ? ($instructionMeta['food_ids'] ?? []) : [];
|
||||
if (empty($foodIds) && $this->tableHasColumns('ipd_discharge_drug_food_interaction', ['ipd_id', 'food_id_list'])) {
|
||||
$legacyFoodRow = $this->firstRowByIpd('ipd_discharge_drug_food_interaction', $ipdId);
|
||||
$foodIds = $this->parseFoodIdCsv((string) ($legacyFoodRow['food_id_list'] ?? ''));
|
||||
}
|
||||
if (! empty($foodIds) && $this->tableHasColumns('ipd_discharge_master_food', ['id', 'food_short', 'food_desc'])) {
|
||||
$foodRows = $this->db->table('ipd_discharge_master_food')
|
||||
->select('id,food_short,food_desc,food_desc_lang')
|
||||
->whereIn('id', array_map('intval', $foodIds))
|
||||
->get()
|
||||
->getResultArray();
|
||||
$foodMap = [];
|
||||
foreach ($foodRows as $fr) {
|
||||
$foodMap[(int) $fr['id']] = $fr;
|
||||
}
|
||||
$foodLines = [];
|
||||
$fIndex = 1;
|
||||
foreach ($foodIds as $fid) {
|
||||
$fr = $foodMap[(int) $fid] ?? null;
|
||||
if (! $fr) {
|
||||
continue;
|
||||
}
|
||||
$heading = trim((string) ($fr['food_short'] ?? ''));
|
||||
$line = trim((string) ($fr['food_desc_lang'] ?? ''));
|
||||
if ($line === '') {
|
||||
$line = trim((string) ($fr['food_desc'] ?? ''));
|
||||
}
|
||||
if ($heading === '' && $line === '') {
|
||||
continue;
|
||||
}
|
||||
$foodLines[] = $fIndex . '. ' . ($heading !== '' ? $heading . ': ' : '') . $line;
|
||||
$fIndex++;
|
||||
}
|
||||
if (! empty($foodLines)) {
|
||||
$carePlanRows[] = [
|
||||
'title' => 'Dietary Advice',
|
||||
'description' => implode("\n", $foodLines),
|
||||
'created_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$otherAdvice = trim((string) ($instructionMeta['other_text'] ?? ''));
|
||||
if ($otherAdvice !== '') {
|
||||
$carePlanRows[] = [
|
||||
'title' => 'Other Advice',
|
||||
'description' => $otherAdvice,
|
||||
'created_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
|
||||
$reviewAfter = trim((string) ($instructionRow['review_after'] ?? ''));
|
||||
if ($reviewAfter !== '') {
|
||||
$reviewDate = '';
|
||||
if ($dischargeRaw !== '' && is_numeric($reviewAfter)) {
|
||||
$reviewTs = strtotime($dischargeRaw . ' +' . (int) $reviewAfter . ' days');
|
||||
if ($reviewTs !== false) {
|
||||
$reviewDate = ' (' . date('d-m-Y', $reviewTs) . ')';
|
||||
}
|
||||
}
|
||||
$carePlanRows[] = [
|
||||
'title' => 'Follow Up',
|
||||
'description' => 'Review After: ' . $reviewAfter . ' Days' . $reviewDate . ' / as and when required',
|
||||
'created_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$courseRows = $this->byIpdRows('ipd_discharge_course_remark', ['comp_remark'], 'id DESC', $ipdId);
|
||||
if (! empty($courseRows)) {
|
||||
$courseText = trim((string) ($courseRows[0]['comp_remark'] ?? ''));
|
||||
if ($courseText !== '') {
|
||||
$carePlanRows[] = [
|
||||
'title' => 'Course In Hospital',
|
||||
'description' => $courseText,
|
||||
'created_at' => $dischargeIso,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$documentsList = $this->buildIpdPdfDocumentsList($ipdId, $dischargeIso);
|
||||
|
||||
$genderRaw = trim((string) ($patientRow['gender'] ?? ''));
|
||||
if ($genderRaw === '') {
|
||||
$genderRaw = trim((string) ($patientRow['xgender'] ?? ''));
|
||||
|
|
@ -8924,6 +9326,7 @@ class Ipd_discharge extends BaseController
|
|||
|
||||
$source = [
|
||||
'record_id' => (string) $ipdId,
|
||||
'bundle_identifier' => 'discharge-' . $ipdNo,
|
||||
'session_id' => (string) $ipdId,
|
||||
'visit_date' => $visitDate,
|
||||
'completed_at' => $dischargeIso,
|
||||
|
|
@ -8954,6 +9357,11 @@ class Ipd_discharge extends BaseController
|
|||
'conditions' => $conditionRows,
|
||||
'procedures' => $procedureRows,
|
||||
'medications' => $medicationRows,
|
||||
'observations' => $observationRows,
|
||||
'investigations' => $investigationRows,
|
||||
'allergies' => $allergyRows,
|
||||
'care_plans' => $carePlanRows,
|
||||
'documents' => $documentsList,
|
||||
'template' => $this->resolveAbdmDischargeTemplate(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -254,10 +254,10 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
continue;
|
||||
}
|
||||
|
||||
$stage = trim((string) ($obs['stage'] ?? $obs['timing'] ?? $obs['category_text'] ?? ''));
|
||||
if ($stage !== '' && stripos($text, 'Admission') === false && stripos($text, 'Discharge') === false) {
|
||||
$text = $text . ' (' . $stage . ')';
|
||||
}
|
||||
$catText = trim((string) ($obs['category'] ?? $obs['category_text'] ?? $obs['stage'] ?? $obs['timing'] ?? ''));
|
||||
$isAdm = (stripos($catText, 'Admission') !== false || stripos($text, 'Admission') !== false);
|
||||
$isDis = (stripos($catText, 'Discharge') !== false || stripos($text, 'Discharge') !== false);
|
||||
$obsCategoryDisplay = $isAdm ? 'General Examination on Admission' : ($isDis ? 'Examination on Discharge' : 'Vital Signs');
|
||||
|
||||
$coding = [];
|
||||
$loincCode = trim((string) ($obs['loinc_code'] ?? ''));
|
||||
|
|
@ -290,7 +290,7 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
'meta' => ['profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/Observation']],
|
||||
'status' => 'final',
|
||||
'category' => [[
|
||||
'text' => 'Vital Signs' . ($stage !== '' ? ' (' . $stage . ')' : ''),
|
||||
'text' => $obsCategoryDisplay,
|
||||
'coding' => [[
|
||||
'system' => 'http://terminology.hl7.org/CodeSystem/observation-category',
|
||||
'code' => 'vital-signs',
|
||||
|
|
@ -474,6 +474,16 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
continue;
|
||||
}
|
||||
|
||||
$docTitle = trim((string) ($document['title'] ?? 'Clinical document'));
|
||||
$snomedCode = trim((string) ($document['snomed_code'] ?? ''));
|
||||
if ($snomedCode === '') {
|
||||
if (stripos($docTitle, 'Bill') !== false || stripos($docTitle, 'Invoice') !== false) {
|
||||
$snomedCode = '823651000000106'; // Billing record
|
||||
} else {
|
||||
$snomedCode = '373942005'; // Discharge summary
|
||||
}
|
||||
}
|
||||
|
||||
$builder->addDocumentReference([
|
||||
'resourceType' => 'DocumentReference',
|
||||
'id' => 'discharge-document-' . $recordId . '-' . $idx,
|
||||
|
|
@ -482,9 +492,9 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
'docStatus' => 'final',
|
||||
'type' => ['coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => '373942005',
|
||||
'display' => 'Discharge summary',
|
||||
]], 'text' => 'Discharge Summary'],
|
||||
'code' => $snomedCode,
|
||||
'display' => $docTitle,
|
||||
]], 'text' => $docTitle],
|
||||
'subject' => ['reference' => $patientRef],
|
||||
'date' => (string) ($document['created_at'] ?? $timestamp),
|
||||
'author' => is_array($practitioner) ? [[
|
||||
|
|
@ -493,35 +503,57 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
'custodian' => is_array($organization) ? [
|
||||
'reference' => 'urn:uuid:' . (string) $organization['id'],
|
||||
] : null,
|
||||
'description' => (string) ($document['title'] ?? 'Clinical document'),
|
||||
'description' => $docTitle,
|
||||
'content' => [[
|
||||
'attachment' => [
|
||||
'contentType' => (string) ($document['content_type'] ?? 'application/pdf'),
|
||||
'language' => 'en-IN',
|
||||
'data' => $data,
|
||||
'title' => (string) ($document['title'] ?? 'Clinical document'),
|
||||
'title' => $docTitle,
|
||||
'creation' => (string) ($document['created_at'] ?? $timestamp),
|
||||
],
|
||||
]],
|
||||
]);
|
||||
}
|
||||
|
||||
$rawObservations = (array) ($source['observations'] ?? []);
|
||||
$admissionObsIndexed = [];
|
||||
$dischargeObsIndexed = [];
|
||||
foreach ($rawObservations as $idx => $obs) {
|
||||
$cat = trim((string) ($obs['category'] ?? $obs['category_text'] ?? $obs['stage'] ?? $obs['timing'] ?? ''));
|
||||
$txt = trim((string) ($obs['text'] ?? ''));
|
||||
if (stripos($cat, 'Admission') !== false || stripos($txt, 'Admission') !== false) {
|
||||
$admissionObsIndexed[$idx] = $obs;
|
||||
} elseif (stripos($cat, 'Discharge') !== false || stripos($txt, 'Discharge') !== false) {
|
||||
$dischargeObsIndexed[$idx] = $obs;
|
||||
}
|
||||
}
|
||||
|
||||
$sectionDefinitions = [
|
||||
['title' => 'Chief complaints', 'code' => '422843007', 'display' => 'Chief complaint section', 'prefix' => 'discharge-chief-complaint-', 'items' => $chiefComplaintsList, 'narrative' => trim((string) ($source['chief_complaint_narrative'] ?? ''))],
|
||||
['title' => 'Problems and Diagnoses', 'code' => '1003642006', 'display' => 'Past medical history section', 'prefix' => 'discharge-diagnosis-', 'items' => $diagnosesList, 'narrative' => trim((string) ($source['diagnosis_narrative'] ?? ''))],
|
||||
['title' => 'Procedures', 'code' => '1003640003', 'display' => 'History of past procedure section', 'prefix' => 'discharge-procedure-', 'items' => (array) ($source['procedures'] ?? [])],
|
||||
['title' => 'Medications', 'code' => '1003606003', 'display' => 'Medication history section', 'prefix' => 'discharge-medication-', 'items' => (array) ($source['medications'] ?? [])],
|
||||
['title' => 'Physical examination', 'code' => '425044008', 'display' => 'Physical exam section', 'prefix' => 'discharge-observation-', 'items' => (array) ($source['observations'] ?? [])],
|
||||
['title' => 'Investigations', 'code' => '721981007', 'display' => 'Diagnostic studies report', 'prefix' => 'discharge-investigation-', 'items' => (array) ($source['investigations'] ?? [])],
|
||||
['title' => 'Allergies', 'code' => '722446000', 'display' => 'Allergy record', 'prefix' => 'discharge-allergy-', 'items' => (array) ($source['allergies'] ?? [])],
|
||||
['title' => 'Care plan', 'code' => '734163000', 'display' => 'Care plan', 'prefix' => 'discharge-careplan-', 'items' => (array) ($source['care_plans'] ?? [])],
|
||||
['title' => 'Document reference', 'code' => '373942005', 'display' => 'Discharge summary', 'prefix' => 'discharge-document-', 'items' => (array) ($source['documents'] ?? [])],
|
||||
];
|
||||
|
||||
if (! empty($admissionObsIndexed) && ! empty($dischargeObsIndexed)) {
|
||||
$sectionDefinitions[] = ['title' => 'General Examination on Admission', 'code' => '425044008', 'display' => 'Physical exam section', 'prefix' => 'discharge-observation-', 'items_indexed' => $admissionObsIndexed];
|
||||
$sectionDefinitions[] = ['title' => 'Examination on Discharge', 'code' => '425044008', 'display' => 'Physical exam section', 'prefix' => 'discharge-observation-', 'items_indexed' => $dischargeObsIndexed];
|
||||
} else {
|
||||
$sectionDefinitions[] = ['title' => 'Physical examination', 'code' => '425044008', 'display' => 'Physical exam section', 'prefix' => 'discharge-observation-', 'items' => $rawObservations];
|
||||
}
|
||||
|
||||
$sectionDefinitions[] = ['title' => 'Investigations', 'code' => '721981007', 'display' => 'Diagnostic studies report', 'prefix' => 'discharge-investigation-', 'items' => (array) ($source['investigations'] ?? [])];
|
||||
$sectionDefinitions[] = ['title' => 'Allergies', 'code' => '722446000', 'display' => 'Allergy record', 'prefix' => 'discharge-allergy-', 'items' => (array) ($source['allergies'] ?? [])];
|
||||
$sectionDefinitions[] = ['title' => 'Care plan', 'code' => '734163000', 'display' => 'Care plan', 'prefix' => 'discharge-careplan-', 'items' => (array) ($source['care_plans'] ?? [])];
|
||||
$sectionDefinitions[] = ['title' => 'Document reference', 'code' => '373942005', 'display' => 'Discharge summary', 'prefix' => 'discharge-document-', 'items' => (array) ($source['documents'] ?? [])];
|
||||
|
||||
$sections = [];
|
||||
foreach ($sectionDefinitions as $definition) {
|
||||
$references = [];
|
||||
$narrativeParts = [];
|
||||
foreach ($definition['items'] as $idx => $item) {
|
||||
$items = isset($definition['items_indexed']) ? $definition['items_indexed'] : (array) ($definition['items'] ?? []);
|
||||
foreach ($items as $idx => $item) {
|
||||
$references[] = ['reference' => 'urn:uuid:' . $definition['prefix'] . $recordId . '-' . $idx];
|
||||
$narrative = $this->sectionItemNarrative((array) $item);
|
||||
if ($narrative !== '') {
|
||||
|
|
@ -734,13 +766,43 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
/** @param array<string,mixed> $item */
|
||||
private function sectionItemNarrative(array $item): string
|
||||
{
|
||||
// For medications: name + dosage
|
||||
if (isset($item['name']) && $this->isMeaningfulValue((string) $item['name'])) {
|
||||
$name = trim((string) $item['name']);
|
||||
$dosage = trim((string) ($item['dosage'] ?? ''));
|
||||
return trim($name . ($this->isMeaningfulValue($dosage) ? ' - ' . $dosage : ''));
|
||||
}
|
||||
|
||||
// For observations: text + value (+ unit)
|
||||
if (isset($item['text'], $item['value']) && $this->isMeaningfulValue((string) $item['text']) && $this->isMeaningfulValue((string) $item['value'])) {
|
||||
$text = trim((string) $item['text']);
|
||||
$val = trim((string) $item['value']);
|
||||
$unit = trim((string) ($item['unit'] ?? ''));
|
||||
$unitInfo = is_numeric($val) ? $this->getVitalSignUnitInfo($text, $unit) : ['unit' => ''];
|
||||
$displayUnit = $unit !== '' ? $unit : ($unitInfo['unit'] ?? '');
|
||||
$valStr = $val . ($displayUnit !== '' && stripos($val, $displayUnit) === false ? ' ' . $displayUnit : '');
|
||||
return trim($text . ': ' . $valStr);
|
||||
}
|
||||
|
||||
// For care plans: title + description
|
||||
if (isset($item['title'], $item['description']) && $this->isMeaningfulValue((string) $item['description'])) {
|
||||
$title = trim((string) $item['title']);
|
||||
$desc = trim((string) $item['description']);
|
||||
if ($title !== '' && stripos($desc, $title) === false) {
|
||||
return trim($title . ': ' . strip_tags($desc));
|
||||
}
|
||||
return trim(strip_tags($desc));
|
||||
}
|
||||
|
||||
// For documents
|
||||
if (isset($item['title']) && $this->isMeaningfulValue((string) $item['title'])) {
|
||||
return trim((string) $item['title']);
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
foreach (['text', 'description', 'title', 'name', 'value'] as $field) {
|
||||
$value = trim((string) ($item[$field] ?? ''));
|
||||
if ($this->isMeaningfulValue($value)) {
|
||||
if ($field === 'name') {
|
||||
$dosage = trim((string) ($item['dosage'] ?? ''));
|
||||
return trim($value . ($this->isMeaningfulValue($dosage) ? ' - ' . $dosage : ''));
|
||||
}
|
||||
return trim(strip_tags($value));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,141 @@ final class FhirGeneratorsTest extends CIUnitTestCase
|
|||
$this->assertStringNotContainsString('UNMAPPED', (string) $encoded);
|
||||
}
|
||||
|
||||
public function testDischargeRecordWithDualVitalsAdviceAndMultiplePdfs(): void
|
||||
{
|
||||
$src = [
|
||||
'record_id' => 3208,
|
||||
'bundle_identifier' => 'discharge-A26090003208',
|
||||
'session_id' => 3208,
|
||||
'visit_date' => '2026-09-04',
|
||||
'completed_at' => '2026-09-04T04:58:00+05:30',
|
||||
'patient' => [
|
||||
'id' => 15355,
|
||||
'uhid' => 'P26091015355',
|
||||
'name' => 'JANVI BISHT',
|
||||
'gender' => 'female',
|
||||
'dob' => '2013-05-10',
|
||||
'abha_id' => '91407564383062',
|
||||
'abha_address' => 'janvibisht@sbx',
|
||||
],
|
||||
'encounter' => [
|
||||
'id' => 'A26090003208',
|
||||
'class_code' => 'IMP',
|
||||
'start' => '2026-09-01T04:53:00+05:30',
|
||||
'end' => '2026-09-04T04:58:00+05:30',
|
||||
],
|
||||
'doctor' => ['id' => 9, 'name' => 'Dr. Sanjay Kumar', 'registration_number' => 'DOC12345'],
|
||||
'organization' => ['id' => 'IN0510000000', 'name' => 'DevSoft Tech'],
|
||||
'chief_complaints' => [
|
||||
['text' => 'Abdominal Pain'],
|
||||
['text' => 'Breathlessness'],
|
||||
['text' => 'Cough'],
|
||||
['text' => 'Fever'],
|
||||
],
|
||||
'diagnoses' => [['text' => 'VIRAL FEVER']],
|
||||
'procedures' => [['text' => 'Biphosphonate prophylaxis', 'performed_at' => '2026-09-03T00:00:00+05:30']],
|
||||
'medications' => [
|
||||
[
|
||||
'name' => 'TAB PANTAKOOL DSR',
|
||||
'dosage' => 'BF (BEFORE FOOD) | OD | भोजन से पहले | दिन में एक बार (OD)',
|
||||
],
|
||||
[
|
||||
'name' => 'TAB AZIK-500',
|
||||
'dosage' => 'AF (AFTER FOOD) | BD | भोजन के बाद | दिन में दो बार (BD)',
|
||||
],
|
||||
],
|
||||
'observations' => [
|
||||
['text' => 'Pulse /min', 'value' => '30/min', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'Respiration /min', 'value' => '120/min', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'BP mmHg', 'value' => '80mmHg', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'Pallor', 'value' => 'Negative', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'SPO2', 'value' => '95', 'unit' => '%', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'RBS', 'value' => '95', 'unit' => 'mg/dL', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
['text' => 'Temp F', 'value' => '99', 'unit' => 'degF', 'category' => 'Condition on Admission Time', 'effective_at' => '2026-09-01T04:53:00+05:30'],
|
||||
|
||||
['text' => 'Pulse /min', 'value' => '30/min', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'Respiration /min', 'value' => '110/min', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'BP mmHg', 'value' => '90mmHg', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'Pallor', 'value' => 'Negative', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'SPO2', 'value' => '95', 'unit' => '%', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'RBS', 'value' => '105', 'unit' => 'mg/dL', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
['text' => 'Temp F', 'value' => '97', 'unit' => 'degF', 'category' => 'Condition on Discharge Time', 'effective_at' => '2026-09-04T04:58:00+05:30'],
|
||||
],
|
||||
'investigations' => [
|
||||
['text' => 'Hb %: 11', 'loinc_code' => '718-7'],
|
||||
],
|
||||
'care_plans' => [
|
||||
['title' => 'Discharge Advice', 'description' => 'To Continue the other medication if any systmic condition like hypertension , diabetic etc, as per adivised by treating physician'],
|
||||
['title' => 'Dietary Advice', 'description' => "1. Eat Nutritious diet,Stay away from smoking and alcohol altogether.\n2. Balanced Meals\n3. Fruits and Vegetables\n4. Avoid Processed Foods"],
|
||||
['title' => 'Follow Up', 'description' => 'Review After: 5 Days (09-09-2026) / as and when required'],
|
||||
],
|
||||
'documents' => [
|
||||
[
|
||||
'title' => 'IPD Discharge Summary',
|
||||
'content_type' => 'application/pdf',
|
||||
'data' => base64_encode('%PDF-discharge-summary'),
|
||||
'snomed_code' => '373942005',
|
||||
'loinc_code' => '18842-5',
|
||||
'created_at' => '2026-09-04T04:58:00+05:30',
|
||||
],
|
||||
[
|
||||
'title' => 'IPD Bill / Invoice',
|
||||
'content_type' => 'application/pdf',
|
||||
'data' => base64_encode('%PDF-ipd-bill'),
|
||||
'snomed_code' => '823651000000106',
|
||||
'loinc_code' => '75490-3',
|
||||
'created_at' => '2026-09-04T04:58:00+05:30',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$output = $this->factory->discharge()->generate($src);
|
||||
$bundle = $output['fhir_bundle'];
|
||||
$composition = $bundle['entry'][0]['resource'];
|
||||
|
||||
// Verify sections: should contain both General Examination on Admission and Examination on Discharge
|
||||
$sectionTitles = array_column($composition['section'], 'title');
|
||||
$this->assertContains('General Examination on Admission', $sectionTitles);
|
||||
$this->assertContains('Examination on Discharge', $sectionTitles);
|
||||
$this->assertContains('Care plan', $sectionTitles);
|
||||
$this->assertContains('Document reference', $sectionTitles);
|
||||
|
||||
// Verify MedicationRequests have dosageInstruction text
|
||||
$medRequests = array_values(array_filter($bundle['entry'], static fn (array $e): bool => ($e['resource']['resourceType'] ?? '') === 'MedicationRequest'));
|
||||
$this->assertCount(2, $medRequests);
|
||||
$this->assertSame('BF (BEFORE FOOD) | OD | भोजन से पहले | दिन में एक बार (OD)', $medRequests[0]['resource']['dosageInstruction'][0]['text']);
|
||||
$this->assertSame('AF (AFTER FOOD) | BD | भोजन के बाद | दिन में दो बार (BD)', $medRequests[1]['resource']['dosageInstruction'][0]['text']);
|
||||
|
||||
// Verify CarePlans
|
||||
$carePlans = array_values(array_filter($bundle['entry'], static fn (array $e): bool => ($e['resource']['resourceType'] ?? '') === 'CarePlan'));
|
||||
$this->assertCount(3, $carePlans);
|
||||
$carePlanTitles = array_column(array_column($carePlans, 'resource'), 'title');
|
||||
$this->assertContains('Discharge Advice', $carePlanTitles);
|
||||
$this->assertContains('Dietary Advice', $carePlanTitles);
|
||||
$this->assertContains('Follow Up', $carePlanTitles);
|
||||
|
||||
// Verify DocumentReferences (both Discharge Summary and Invoice)
|
||||
$docRefs = array_values(array_filter($bundle['entry'], static fn (array $e): bool => ($e['resource']['resourceType'] ?? '') === 'DocumentReference'));
|
||||
$this->assertCount(2, $docRefs);
|
||||
$this->assertSame('IPD Discharge Summary', $docRefs[0]['resource']['description']);
|
||||
$this->assertSame('IPD Bill / Invoice', $docRefs[1]['resource']['description']);
|
||||
$this->assertSame('823651000000106', $docRefs[1]['resource']['type']['coding'][0]['code']);
|
||||
|
||||
// Verify Observation Categories on Admission vs Discharge
|
||||
$observations = array_values(array_filter($bundle['entry'], static fn (array $e): bool => ($e['resource']['resourceType'] ?? '') === 'Observation' && isset($e['resource']['category'])));
|
||||
$this->assertGreaterThanOrEqual(14, count($observations));
|
||||
$this->assertSame('General Examination on Admission', $observations[0]['resource']['category'][0]['text']);
|
||||
$this->assertSame('Examination on Discharge', $observations[7]['resource']['category'][0]['text']);
|
||||
|
||||
// Verify all references resolve
|
||||
$fullUrls = array_column($bundle['entry'], 'fullUrl');
|
||||
$encoded = json_encode($bundle, JSON_UNESCAPED_SLASHES);
|
||||
preg_match_all('/"reference":"(urn:uuid:[^"]+)"/', (string) $encoded, $matches);
|
||||
foreach ($matches[1] as $reference) {
|
||||
$this->assertContains($reference, $fullUrls, 'Unresolved reference: ' . $reference);
|
||||
}
|
||||
}
|
||||
|
||||
public function testInvoiceRecordHasNrcesProfilesAndResolvableReferences(): void
|
||||
{
|
||||
$controller = new AbdmGateway();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user