From c0f7e213e6513ca4ab9f12424b1f00787712b224 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Thu, 11 Jun 2026 09:57:04 +0530 Subject: [PATCH] Discharge templates: add status heading token and refine placeholders --- app/Config/Routes.php | 2 + app/Controllers/Ipd_discharge.php | 387 +++++++++++++++++- .../Setting/Template/discharge_templates.php | 21 +- 3 files changed, 386 insertions(+), 24 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index b68ab6c..e36ebac 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -370,6 +370,8 @@ $routes->get('Ipd_discharge/search_patient', 'Ipd_discharge::search_patient'); $routes->get('Ipd_discharge/search_patient_ajax', 'Ipd_discharge::search_patient_ajax'); $routes->get('Ipd_discharge/debug_ipd_fields/(:num)', 'Ipd_discharge::debug_ipd_fields/$1'); $routes->get('Ipd_discharge/debug_discharge_html/(:num)', 'Ipd_discharge::debug_discharge_html/$1'); +$routes->get('Ipd_discharge/placeholder_preview', 'Ipd_discharge::placeholder_preview'); +$routes->get('Ipd_discharge/placeholder_preview/(:num)', 'Ipd_discharge::placeholder_preview/$1'); $routes->match(['get', 'post'], 'Ipd_discharge/ipd_select/(:num)', 'Ipd_discharge::ipd_select/$1'); $routes->match(['get', 'post'], 'Ipd_discharge/ipd_select/(:num)/(:num)', 'Ipd_discharge::ipd_select/$1/$2'); $routes->get('Ipd_discharge/section_past_data', 'Ipd_discharge::section_past_data'); diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php index 4086f15..e3b4419 100644 --- a/app/Controllers/Ipd_discharge.php +++ b/app/Controllers/Ipd_discharge.php @@ -127,7 +127,7 @@ class Ipd_discharge extends BaseController private function defaultDischargeTemplateHtml(): string { - return '

Discharge Summary

' + return '{{DISCHARGE_STATUS}}' . '' . '' . '' @@ -160,7 +160,7 @@ class Ipd_discharge extends BaseController . '
{{CO_MORBIDITIES}}
' . '
{{DISCHARGE_MEDICATIONS}}
' . '
{{DIETARY_ADVICE}}
' - . '
{{DISCHARGE_INSTRUCTIONS}}
' + . '
{{REVIEW_AFTER}}
' . '
{{SIGNATURE_BLOCK}}
'; } @@ -184,7 +184,7 @@ class Ipd_discharge extends BaseController private function nabhDischargeTemplateHtml(): string { - return '

DISCHARGE SUMMARY

' + return '{{DISCHARGE_STATUS}}' . '
Patient: {{PATIENT_NAME}}
' . '' . '' @@ -220,7 +220,7 @@ class Ipd_discharge extends BaseController . '
{{CO_MORBIDITIES}}
' . '
{{DISCHARGE_MEDICATIONS}}
' . '
{{DIETARY_ADVICE}}
' - . '
{{DISCHARGE_INSTRUCTIONS}}
' + . '
{{REVIEW_AFTER}}
' . '

Counselling & Handover Confirmation

' . '
Patient Name: {{PATIENT_NAME}}
' . '' @@ -523,11 +523,21 @@ class Ipd_discharge extends BaseController return $this->extractDischargeSectionByMarkers($full, $starts, array_merge($allMarkers, $extraEnds)); }; - // Main clinical discharge summary from HTML editor - $clinicalSummary = $section(['Discharge Summary']); + $ipd = $panelData['ipd_info'] ?? null; + $ipdId = (int) ($ipd->id ?? 0); + + // Main clinical discharge summary: use explicit DB field first (instruction_remark). + // Fallback to section extraction for older cached content. + $instructionRow = $ipdId > 0 ? $this->firstRowByIpd('ipd_discharge_instructions', $ipdId) : []; + $clinicalSummaryRaw = $this->normalizeRichText((string) ($instructionRow['comp_remark'] ?? '')); + $clinicalSummary = $clinicalSummaryRaw !== '' + ? '
' . $this->renderRichText($clinicalSummaryRaw) . '
' + : $section(['Discharge Summary']); // Replace "Discharge Summary" header with discharge status - if ($dischargeStatus !== '' && $clinicalSummary !== '') { + if ($dischargeStatus !== '' + && strcasecmp(trim($dischargeStatus), 'Discharge Summary') !== 0 + && $clinicalSummary !== '') { $clinicalSummary = preg_replace( '/]*>\s*Discharge\s+Summary\s*<\/h[1-6]>/i', '

' . esc($dischargeStatus) . '

', @@ -551,10 +561,21 @@ class Ipd_discharge extends BaseController $clinicalSummary ); } + + if ($clinicalSummary !== '') { + $hasSummaryHeading = preg_match( + '/]*>\s*Discharge\s+Summary\s*<\/h[1-6]>|<(?:b|strong)[^>]*>\s*Discharge\s+Summary\s*<\/(?:b|strong)>/i', + $clinicalSummary + ) === 1; + if (! $hasSummaryHeading) { + $clinicalSummary = '

DISCHARGE SUMMARY

' . $clinicalSummary; + } + } $finalDiagnosis = $section(['Final Diagnosis']); - $surgery = $section(['Surgery']); - $procedure = $section(['Procedure']); + // Use explicit section labels to avoid matching free-text words like "elective surgery". + $surgery = $section(['Surgery :', '>Surgery :', 'Surgery :']); + $procedure = $section(['Procedure :', '>Procedure :', 'Procedure :']); $personalHistory = $section(['Personal History']); $presentingComplaints = $section(['Presenting Complaints and Reason for Admission']); $painMeasurement = $section(['Pain Measurement Scale']); @@ -564,21 +585,66 @@ class Ipd_discharge extends BaseController $examOnDischarge = $section(['Examination on Discharge']); $dischargeMedications = $section(['Discharge Medications']); $dietaryAdvice = $section(['Dietary Advice']); - $dischargeInstructions = $section(['Discharge Advice/Instructions/Summary']); $drugAllergyAdr = $section(['Drug Allergy / ADR']); $coMorbidities = $section(['Co-Morbidities']); // Extract entire signature table - no end markers (it's the last section) $signatureBlock = $section(['Signature of Consultant'], []); + $reviewAfterRaw = trim((string) ($instructionRow['review_after'] ?? '')); + $reviewAfter = $reviewAfterRaw !== '' + ? '' + : ''; + $dischargeStatusHeading = '

' . esc($dischargeStatus !== '' ? $dischargeStatus : 'Discharge Summary') . '

'; // Extract individual instruction fields for template flexibility $otherAdvice = $section(['Other Advice:']); $followUpInstructions = $section(['Discharge Summary:']); + // Ignore heading-only extraction (no real follow-up narrative text). + $followUpHeadingOnly = trim(strip_tags((string) $followUpInstructions)); + if (preg_match('/^Discharge\s+Summary\s*:?$/i', $followUpHeadingOnly) === 1) { + $followUpInstructions = ''; + } + + // Avoid duplicate output when templates contain both summary/advice placeholders + // and both resolve to the same narrative. + $normalizeText = static function (string $html): string { + $text = trim(strip_tags($html)); + return (string) preg_replace('/\s+/u', ' ', $text); + }; + if ($followUpInstructions !== '' + && $clinicalSummary !== '' + && $normalizeText((string) $followUpInstructions) !== '' + && strcasecmp($normalizeText((string) $followUpInstructions), $normalizeText((string) $clinicalSummary)) === 0) { + $followUpInstructions = ''; + } + + // Fallback for templates/data where clinical summary body is present under + // "Discharge Summary:" subsection instead of the main summary block. + $clinicalSummaryText = trim(strip_tags((string) $clinicalSummary)); + $followUpText = trim(strip_tags((string) $followUpInstructions)); + if (($clinicalSummaryText === '' || strcasecmp($clinicalSummaryText, 'Discharge Summary') === 0) && $followUpText !== '') { + $clinicalSummary = $followUpInstructions; + } + + // Fallback to master section tables when cached HTML extraction is empty. + $surgeryText = trim(strip_tags((string) $surgery)); + if ($surgeryText === '') { + $surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['surgery_name', 'surgery_date'], 'id ASC', $ipdId); + $surgery = $this->buildNamedDateSection('Surgery', $surgeryRows, 'surgery_name', 'surgery_date', 'Date of Surgery'); + } + + $procedureText = trim(strip_tags((string) $procedure)); + if ($procedureText === '') { + $procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['procedure_name', 'procedure_date'], 'id ASC', $ipdId); + $procedure = $this->buildNamedDateSection('Procedure', $procedureRows, 'procedure_name', 'procedure_date', 'Date of Procedure'); + } + $vars = [ + 'DISCHARGE_STATUS' => $dischargeStatusHeading, // Main discharge summary from HTML editor (instruction_remark field) - 'CLINICAL_SUMMARY' => $clinicalSummary, - // Legacy alias for backward compatibility 'DISCHARGE_SUMMARY' => $clinicalSummary, + // Legacy alias for backward compatibility + 'CLINICAL_SUMMARY' => $clinicalSummary, // Clinical sections 'FINAL_DIAGNOSIS' => $finalDiagnosis, @@ -594,9 +660,13 @@ class Ipd_discharge extends BaseController // Discharge instructions and advice 'DISCHARGE_MEDICATIONS' => $dischargeMedications, 'DIETARY_ADVICE' => $dietaryAdvice, - 'DISCHARGE_INSTRUCTIONS' => $dischargeInstructions, + 'DISCHARGE_INSTRUCTIONS' => '', + 'REVIEW_AFTER' => $reviewAfter, 'OTHER_ADVICE' => $otherAdvice, 'FOLLOW_UP_INSTRUCTIONS' => $followUpInstructions, + // Keep older placeholders mapped to follow-up section for backward compatibility. + 'DISCHARGE_ADVICE' => $followUpInstructions, + 'INSTRUCTION_REMARK' => $followUpInstructions, // Other sections 'PAIN_MEASUREMENT_SCALE' => $painMeasurement, @@ -615,7 +685,8 @@ class Ipd_discharge extends BaseController 'discharge_exam_on_discharge' => $examOnDischarge, 'Discharge_Medications' => $dischargeMedications, 'diet_advice' => $dietaryAdvice, - 'Discharge_Instructions' => $dischargeInstructions, + 'Discharge_Instructions' => '', + 'Review_After' => $reviewAfter, 'Pain_Measurement_Scale' => $painMeasurement, 'Drug_Allergy_ADR' => $drugAllergyAdr, 'Co_Morbidities' => $coMorbidities, @@ -704,6 +775,7 @@ class Ipd_discharge extends BaseController } $templateHtml = $this->normalizeLegacyDischargeTemplate($templateHtml); + $templateHtml = $this->normalizeDischargeTemplatePlaceholders($templateHtml); $replace = []; foreach ($vars as $key => $value) { @@ -714,6 +786,50 @@ class Ipd_discharge extends BaseController return strtr($templateHtml, $replace); } + private function normalizeDischargeTemplatePlaceholders(string $templateHtml): string + { + $out = $templateHtml; + + // Canonicalize duplicate aliases so templates use one preferred token. + $aliasToCanonical = [ + 'H_Name' => 'hospital_name', + 'H_phone_No' => 'hospital_phone', + 'H_Email' => 'hospital_email', + 'ADMIT_DATE_ONLY' => 'ADMIT_DATE', + 'DISCHARGE_DATE_ONLY' => 'DISCHARGE_DATE', + 'ADMIT_TIME' => 'ADMISSION_TIME', + 'CLINICAL_SUMMARY' => 'DISCHARGE_SUMMARY', + 'DISCHARGE_INSTRUCTIONS' => 'REVIEW_AFTER', + ]; + foreach ($aliasToCanonical as $alias => $canonical) { + $out = (string) preg_replace('/\{\{?\s*' . preg_quote($alias, '/') . '\s*\}?\}/i', '{{' . $canonical . '}}', $out); + } + + // If summary placeholder is wrapped inside heading tags, convert to block container. + $out = (string) preg_replace( + '/]*>\s*(\{\{?\s*(?:CLINICAL_SUMMARY|DISCHARGE_SUMMARY)\s*\}?\})\s*<\/h[1-6]>/i', + '
$1
', + $out + ); + + // Keep only one main summary placeholder; extra occurrences cause duplicate output. + $seenSummary = false; + $out = (string) preg_replace_callback( + '/\{\{?\s*(CLINICAL_SUMMARY|DISCHARGE_SUMMARY)\s*\}?\}/i', + static function (array $m) use (&$seenSummary): string { + if ($seenSummary) { + return ''; + } + + $seenSummary = true; + return '{{DISCHARGE_SUMMARY}}'; + }, + $out + ); + + return $out; + } + private function applyDischargeTemplate(string $content, array $panelData, ?int $requestedTemplateId = null): array { $templates = $this->getDischargeTemplateRows(); @@ -746,7 +862,19 @@ class Ipd_discharge extends BaseController $templateHtml = '{{CONTENT}}'; } + // Normalize template syntax early so downstream placeholder checks operate + // on a consistent token format (legacy PHP placeholders, casing, spacing). + $templateHtml = $this->normalizeLegacyDischargeTemplate($templateHtml); + $templateHtml = $this->normalizeDischargeTemplatePlaceholders($templateHtml); + $tokenVars = $this->buildDischargeTemplateTokenVars($panelData, $content); + + $templateHasMainSummaryToken = preg_match('/\{\{\s*DISCHARGE_SUMMARY\s*\}\}|\{\s*DISCHARGE_SUMMARY\s*\}/i', $templateHtml) === 1; + if ($templateHasMainSummaryToken) { + $tokenVars['DISCHARGE_ADVICE'] = ''; + $tokenVars['INSTRUCTION_REMARK'] = ''; + } + if (! $this->templateHasKnownDischargeTokens($templateHtml, $tokenVars)) { $templateHtml .= "\n{{CONTENT}}"; } @@ -758,6 +886,11 @@ class Ipd_discharge extends BaseController $templateHtml = '{{CONTENT}}'; } + // If template is section-driven, ignore raw CONTENT token to avoid duplicate blocks. + if ($this->templateHasDischargeSectionPlaceholders($templateHtml)) { + $templateHtml = (string) preg_replace('/\{\{?\s*content\s*\}?\}/i', '', $templateHtml); + } + $content = $this->stripLegacyTopSummaryFromContent($content, $templateHtml, $patientName, $patientCode, $ipdCode); $templateSettings = array_merge($this->defaultDischargeTemplateSettings(), [ @@ -781,6 +914,7 @@ class Ipd_discharge extends BaseController } $rendered = $this->applyDischargeTemplateTokens($templateHtml, $tokenVars); + $rendered = $this->dedupeRepeatedClinicalSummary($rendered, (string) ($tokenVars['DISCHARGE_SUMMARY'] ?? '')); return [ 'rendered_html' => $rendered, @@ -791,6 +925,33 @@ class Ipd_discharge extends BaseController ]; } + private function dedupeRepeatedClinicalSummary(string $renderedHtml, string $clinicalSummaryHtml): string + { + $renderedHtml = (string) $renderedHtml; + $clinicalSummaryHtml = trim((string) $clinicalSummaryHtml); + if ($renderedHtml === '' || $clinicalSummaryHtml === '') { + return $renderedHtml; + } + + $firstPos = strpos($renderedHtml, $clinicalSummaryHtml); + if ($firstPos === false) { + return $renderedHtml; + } + + $offset = $firstPos + strlen($clinicalSummaryHtml); + while (true) { + $nextPos = strpos($renderedHtml, $clinicalSummaryHtml, $offset); + if ($nextPos === false) { + break; + } + + $renderedHtml = substr_replace($renderedHtml, '', $nextPos, strlen($clinicalSummaryHtml)); + $offset = $nextPos; + } + + return $renderedHtml; + } + private function templateHasKnownDischargeTokens(string $templateHtml, array $tokenVars): bool { if (trim($templateHtml) === '') { @@ -853,7 +1014,9 @@ class Ipd_discharge extends BaseController } $sectionTokens = [ + 'CLINICAL_SUMMARY', 'DISCHARGE_SUMMARY', + 'FOLLOW_UP_INSTRUCTIONS', 'FINAL_DIAGNOSIS', 'SURGERY', 'PROCEDURE', @@ -868,6 +1031,8 @@ class Ipd_discharge extends BaseController 'CO_MORBIDITIES', 'DISCHARGE_MEDICATIONS', 'DIETARY_ADVICE', + 'OTHER_ADVICE', + 'REVIEW_AFTER', 'DISCHARGE_INSTRUCTIONS', 'SIGNATURE_BLOCK', ]; @@ -1073,19 +1238,25 @@ class Ipd_discharge extends BaseController return 'Discharge Summary'; } - $ipdStatus = (int) ($ipd->ipd_status ?? 0); + $ipdStatus = (int) ($ipd->discarge_patient_status ?? ($ipd->discharge_status ?? ($ipd->ipd_status ?? 0))); // If status is 0 (Status Pending), show "Discharge Summary" only if ($ipdStatus === 0) { return 'Discharge Summary'; } - // Check if ipd_discharge_status table exists - if (! $this->db->tableExists('ipd_discharge_status')) { + $statusTable = ''; + if ($this->db->tableExists('ipd_discharg_status')) { + $statusTable = 'ipd_discharg_status'; + } elseif ($this->db->tableExists('ipd_discharge_status')) { + $statusTable = 'ipd_discharge_status'; + } + + if ($statusTable === '') { return 'Discharge Summary'; } - $row = $this->db->table('ipd_discharge_status') + $row = $this->db->table($statusTable) ->select('status_desc, status_details') ->where('id', $ipdStatus) ->get(1) @@ -4902,6 +5073,172 @@ class Ipd_discharge extends BaseController ); } + /** + * Placeholder preview page: shows each placeholder and resolved content for an IPD. + * Access via: /Ipd_discharge/placeholder_preview/{ipdId}?tpl={templateId} + */ + public function placeholder_preview(int $ipdId = 0) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'billing.ipd.current-admission', + 'template.discharge', + ]); + if ($permission) { + return $permission; + } + + if ($ipdId <= 0) { + $ipdId = (int) ($this->request->getGet('ipd_id') ?? 0); + } + $requestedTemplateId = (int) ($this->request->getGet('tpl') ?? 0); + $forceRegenerate = (int) ($this->request->getGet('refresh') ?? 0) === 1; + + $placeholderInfo = [ + 'H_address_1' => 'Hospital Address Line 1', + 'H_address_2' => 'Hospital Address Line 2', + 'H_logo' => 'Hospital Logo File Name', + 'H_logo_abs' => 'Hospital Logo Absolute Path', + 'hospital_name' => 'Hospital Name', + 'hospital_address' => 'Hospital Full Address', + 'hospital_phone' => 'Hospital Phone', + 'hospital_email' => 'Hospital Email', + 'PATIENT_NAME' => 'Patient Name', + 'UHID' => 'Patient UHID', + 'IPD_CODE' => 'IPD Number/Code', + 'AGE_GENDER' => 'Age / Gender', + 'GUARDIAN' => 'Guardian Combined Text', + 'GUARDIAN_RELATION' => 'Guardian Relation', + 'GUARDIAN_NAME' => 'Guardian Name', + 'PATIENT_ADDRESS' => 'Patient Address', + 'PATIENT_PHONE' => 'Patient Phone', + 'DEPARTMENT' => 'Department Name', + 'ADMIT_DATE' => 'Admission Date', + 'DISCHARGE_DATE' => 'Discharge Date', + 'ADMISSION_TIME' => 'Admission Time', + 'DISCHARGE_TIME' => 'Discharge Time', + 'ISDELIVERY' => 'Delivery Flag', + 'INSURANCE_COMPANY' => 'Insurance Company', + 'DOCTOR_NAMES' => 'Consultant/Doctor Names', + 'DOCTOR_NAME' => 'Doctor Name (alias)', + 'CURRENT_DATE' => 'Current Date', + 'PRINT_TIME' => 'Print Date/Time', + 'CONTENT' => 'Full Auto-Generated Discharge Content', + 'DISCHARGE_STATUS' => 'Document Heading from Discharge Status', + 'PATIENT_INFO_TABLE' => 'Pre-built Patient Information Table', + 'DISCHARGE_SUMMARY' => 'Main Discharge Summary Content', + 'FINAL_DIAGNOSIS' => 'Final Diagnosis Section', + 'SURGERY' => 'Surgery Section', + 'PROCEDURE' => 'Procedure Section', + 'PERSONAL_HISTORY' => 'Personal History Section', + 'PRESENTING_COMPLAINTS' => 'Presenting Complaints Section', + 'PAIN_MEASUREMENT_SCALE' => 'Pain Measurement Section', + 'GENERAL_EXAM_ADMISSION' => 'General Examination on Admission', + 'CLINICAL_INVESTIGATION_REPORTS' => 'Clinical Investigation Reports', + 'COURSE_IN_HOSPITAL' => 'Course in Hospital Section', + 'EXAMINATION_ON_DISCHARGE' => 'Examination on Discharge Section', + 'DRUG_ALLERGY_ADR' => 'Drug Allergy/ADR Section', + 'CO_MORBIDITIES' => 'Co-Morbidities Section', + 'DISCHARGE_MEDICATIONS' => 'Discharge Medications Section', + 'DIETARY_ADVICE' => 'Dietary Advice Section', + 'OTHER_ADVICE' => 'Other Advice Section', + 'REVIEW_AFTER' => 'Review After (days/text)', + 'FOLLOW_UP_INSTRUCTIONS' => 'Follow-up Instructions Section', + 'DISCHARGE_ADVICE' => 'Follow-up Advice (legacy alias)', + 'INSTRUCTION_REMARK' => 'Instruction Remark (legacy alias)', + 'SIGNATURE_BLOCK' => 'Signature Block', + ]; + + if ($ipdId <= 0) { + $urlBase = site_url('Ipd_discharge/placeholder_preview'); + return $this->response + ->setContentType('text/html') + ->setBody( + 'Discharge Placeholder Preview' . + '' . + '

Discharge Placeholder Preview

' . + '
' . + ' ' . + ' ' . + ' ' . + '

Use this page to see which placeholder resolves to what content for a given IPD.

' . + '
' + ); + } + + $panelData = $this->ipdBillingModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $content = $this->getDischargeContent($ipdId); + if ($forceRegenerate || trim(strip_tags($content)) === '') { + $content = $this->buildAutoDischargeContent($ipdId, $panelData); + if (trim(strip_tags($content)) !== '') { + $this->saveDischargeContent($ipdId, $content); + } + } + + $templatePack = $this->applyDischargeTemplate($content, $panelData, $requestedTemplateId > 0 ? $requestedTemplateId : null); + $tokenVars = $this->buildDischargeTemplateTokenVars($panelData, $content); + $selectedTemplateId = (int) ($templatePack['selected_template_id'] ?? 0); + $selectedTemplateName = (string) ($templatePack['selected_template_name'] ?? ''); + $showLegacy = (int) ($this->request->getGet('show_legacy') ?? 0) === 1; + $legacyTokens = [ + 'FOLLOW_UP_INSTRUCTIONS', + 'DISCHARGE_ADVICE', + 'INSTRUCTION_REMARK', + ]; + $legacyTokenSet = array_fill_keys($legacyTokens, true); + + $rowsHtml = ''; + foreach ($placeholderInfo as $token => $description) { + $value = (string) ($tokenVars[$token] ?? ''); + $plain = trim(strip_tags($value)); + $status = $plain === '' ? 'Empty' : 'Filled'; + $statusColor = $plain === '' ? '#b45309' : '#166534'; + $display = $value === '' ? '(empty)' : '
' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '
'; + $isLegacyToken = isset($legacyTokenSet[$token]); + $rowClassAttr = $isLegacyToken ? ' class="legacy-row"' : ''; + $rowStyleAttr = ($isLegacyToken && !$showLegacy) ? ' style="display:none;"' : ''; + + $rowsHtml .= '' + . '
' + . '' + . '' + . '' + . ''; + } + + $previewUrl = site_url('Ipd_discharge/preview_discharge_report/' . $ipdId) . ($selectedTemplateId > 0 ? ('?tpl=' . $selectedTemplateId) : ''); + $pdfUrl = site_url('Ipd_discharge/show_discharge/' . $ipdId . '/1') . ($selectedTemplateId > 0 ? ('?tpl=' . $selectedTemplateId) : ''); + + return $this->response + ->setContentType('text/html') + ->setBody( + 'Discharge Placeholder Map - IPD ' . $ipdId . '' . + '' . + '' . + '
' . + '

Discharge Placeholder Mapping

' . + '
IPD ID: ' . $ipdId . '   Template: ' . esc($selectedTemplateName) . ' (ID: ' . $selectedTemplateId . ')
' . + '' . + '
(' . count($legacyTokens) . ' hidden by default)
' . + '
This table shows each placeholder and resolved value from current discharge data.
' . + '
' . + '
Medication explained to patient/attendantRemarks:
{{' . esc($token) . '}}' . esc($description) . '' . $status . '' . $display . '
' + . $rowsHtml . + '
PlaceholderContent SourceStatusResolved Content
' . + '' . + '' + ); + } + public function ipd_select(int $ipdId, int $reCreate = 0) { $permission = $this->requireAnyPermission([ @@ -5904,6 +6241,20 @@ class Ipd_discharge extends BaseController } if ($savedAny) { + // Keep ipd_discharge.content in sync with latest form data so preview/PDF + // immediately reflects edits without requiring manual regen links. + try { + $freshPanelData = $this->ipdBillingModel->getIpdPanelInfo($ipdId); + if (! empty($freshPanelData)) { + $freshContent = $this->buildAutoDischargeContent($ipdId, $freshPanelData); + if (trim((string) $freshContent) !== '') { + $this->saveDischargeContent($ipdId, (string) $freshContent); + } + } + } catch (\Throwable $e) { + // Fail-open: do not block UI save if regeneration fails. + } + $this->enqueueIpdDischargeSync($ipdId, $patientId, $action); } diff --git a/app/Views/Setting/Template/discharge_templates.php b/app/Views/Setting/Template/discharge_templates.php index 7aef3bc..96fc402 100644 --- a/app/Views/Setting/Template/discharge_templates.php +++ b/app/Views/Setting/Template/discharge_templates.php @@ -36,13 +36,13 @@ $status = (int) ($edit['status'] ?? 1);
Available placeholders:
- 1. Hospital Information: {{H_Name}}, {{H_address_1}}, {{H_address_2}}, {{H_phone_No}}, {{H_Email}}, {{H_logo}}, {{H_logo_abs}}, {{hospital_name}}, {{hospital_address}}, {{hospital_phone}}, {{hospital_email}}
+ 1. Hospital Information: {{hospital_name}}, {{H_address_1}}, {{H_address_2}}, {{hospital_phone}}, {{hospital_email}}, {{H_logo}}, {{H_logo_abs}}, {{hospital_address}}
2. Patient Information: {{PATIENT_NAME}}, {{UHID}}, {{IPD_CODE}}, {{AGE_GENDER}}, {{GUARDIAN}}, {{GUARDIAN_RELATION}}, {{GUARDIAN_NAME}}, {{PATIENT_ADDRESS}}, {{PATIENT_PHONE}} ✨
- 3. IPD Information: {{DEPARTMENT}}, {{ADMIT_DATE}}, {{DISCHARGE_DATE}}, {{ADMIT_DATE_ONLY}}, {{DISCHARGE_DATE_ONLY}}, {{ADMISSION_TIME}}, {{DISCHARGE_TIME}}, {{ADMIT_TIME}}, {{ISDELIVERY}}, {{INSURANCE_COMPANY}} ✨, {{DOCTOR_NAMES}} ✨, {{DOCTOR_NAME}} ✨
+ 3. IPD Information: {{DEPARTMENT}}, {{ADMIT_DATE}}, {{DISCHARGE_DATE}}, {{ADMISSION_TIME}}, {{DISCHARGE_TIME}}, {{ISDELIVERY}}, {{INSURANCE_COMPANY}} ✨, {{DOCTOR_NAMES}} ✨, {{DOCTOR_NAME}} ✨
4. IPD Discharge Content: {{CONTENT}} (all sections), {{PATIENT_INFO_TABLE}} (pre-built patient table)
- Section placeholders for custom layout/order: {{DISCHARGE_SUMMARY}}, {{FINAL_DIAGNOSIS}}, {{SURGERY}}, {{PROCEDURE}}, {{PERSONAL_HISTORY}}, {{PRESENTING_COMPLAINTS}}, {{PAIN_MEASUREMENT_SCALE}}, {{GENERAL_EXAM_ADMISSION}}, {{CLINICAL_INVESTIGATION_REPORTS}}, {{COURSE_IN_HOSPITAL}}, {{EXAMINATION_ON_DISCHARGE}}, {{DRUG_ALLERGY_ADR}}, {{CO_MORBIDITIES}}, {{DISCHARGE_MEDICATIONS}}, {{DIETARY_ADVICE}}, {{DISCHARGE_INSTRUCTIONS}}, {{SIGNATURE_BLOCK}}
- 5. Common/Meta: {{CURRENT_DATE}}, {{PRINT_TIME}}
- ✨ = Newly added tokens | All tokens are case-insensitive | Empty values auto-hide + Section placeholders for custom layout/order: {{DISCHARGE_SUMMARY}}, {{FINAL_DIAGNOSIS}}, {{SURGERY}}, {{PROCEDURE}}, {{PERSONAL_HISTORY}}, {{PRESENTING_COMPLAINTS}}, {{PAIN_MEASUREMENT_SCALE}}, {{GENERAL_EXAM_ADMISSION}}, {{CLINICAL_INVESTIGATION_REPORTS}}, {{COURSE_IN_HOSPITAL}}, {{EXAMINATION_ON_DISCHARGE}}, {{DRUG_ALLERGY_ADR}}, {{CO_MORBIDITIES}}, {{DISCHARGE_MEDICATIONS}}, {{DIETARY_ADVICE}}, {{OTHER_ADVICE}}, {{REVIEW_AFTER}}, {{FOLLOW_UP_INSTRUCTIONS}}, {{SIGNATURE_BLOCK}}
+ 5. Common/Meta: {{DISCHARGE_STATUS}}, {{CURRENT_DATE}}, {{PRINT_TIME}}
+ ✨ = Newly added tokens | All tokens are case-insensitive | Empty values auto-hide | Legacy aliases auto-normalized to preferred placeholders
@@ -64,10 +64,11 @@ $status = (int) ($edit['status'] ?? 1);
+
- Uses the live discharge routes: /Ipd_discharge/preview_discharge_report/{ipdId} and /Ipd_discharge/show_discharge/{ipdId}/1. + Uses the live discharge routes: /Ipd_discharge/preview_discharge_report/{ipdId}, /Ipd_discharge/show_discharge/{ipdId}/1, and /Ipd_discharge/placeholder_preview/{ipdId}.
@@ -224,6 +225,7 @@ $status = (int) ($edit['status'] ?? 1); var previewIpdInput = document.getElementById('discharge_preview_ipd_id'); var previewBtn = document.getElementById('btn_discharge_preview'); var pdfBtn = document.getElementById('btn_discharge_pdf'); + var placeholderPreviewBtn = document.getElementById('btn_discharge_placeholder_preview'); var editButtons = document.querySelectorAll('.discharge-template-edit'); var deleteButtons = document.querySelectorAll('.discharge-template-delete'); @@ -674,6 +676,13 @@ $status = (int) ($edit['status'] ?? 1); }); } + if (placeholderPreviewBtn) { + placeholderPreviewBtn.addEventListener('click', function () { + var ipdId = parseInt((previewIpdInput && previewIpdInput.value) ? previewIpdInput.value : '0', 10); + openDischargeUrl('', ipdId); + }); + } + // Handle "Create Default Templates" button var createDefaultsBtn = document.getElementById('btn_create_defaults'); if (createDefaultsBtn) {