Integrate Final Diagnosis with canonical disease master data and add reusable My/Master template CRUD for discharge narrative sections, including summary investigations, course, other advice, and discharge summary.
Restore surgery and procedure bindings after SPA updates, validate procedure dates without SQL errors, and prevent medicine autocomplete Enter from submitting while preserving layout and autofill.
Serialize the full repaired body instead of only the temporary wrapper contents so DOMDocument recovery does not drop later discharge sections before mPDF rendering.
- Add ?html=1 parameter to show_discharge to output HTML directly
- Shows debug info: IPD ID, template name, content lengths
- Bypass PDF generation to see rendered HTML content
- Helps diagnose PHP/rendering errors before PDF conversion
Usage: /Ipd_discharge/show_discharge/1007168/1?html=1&tpl=3
- Restore "Discharge Summary" heading for template extraction to work
- Show status as strong text below heading (not as replacement)
- Include discharge summary HTML editor content (instruction_remark)
- Section extraction requires consistent heading names
Previous fix broke section extraction by changing heading text
- Include ipd_discharge_instructions.comp_remark in discharge summary section
- Previously only showed status header, missing actual discharge narrative
- Reuse existing instructionRowForMeta query to avoid duplicate DB calls
- Apply renderRichText() to properly display HTML editor content
Fixes missing discharge summary text for IPD 1007168 and similar cases
- Enhanced normalizeRichText() to detect and preserve HTML from HTML editors
- Added renderRichText() helper to safely render HTML vs plain text content
- Updated 4 rendering locations (narrative sections, systemic exams, investigations)
- Backward compatible: plain textarea fields still work correctly
- Fixes issue where strip_tags() removed all HTML formatting from editor content
Resolves blank discharge summaries in PDF when content saved from TinyMCE/HTML editors
- Fix dose columns showing IDs instead of labels (OD, BID, TDS, etc.)
- Add Rx-Group functionality with modal, search, and bulk add
- Add substitute medicine suggestions with one-click use
- Improve medicine autocomplete with debouncing and scope=all
- Load dose master maps in controller and pass to view
- Match OPD Consult UI/UX patterns for consistency
- Clear all output buffers before sending PDF binary to prevent corruption
- Add X-Content-Type-Options: nosniff to force Chrome to treat response as PDF
- Apply fixes to main, fallback, and ultimate fallback PDF response paths
- Fixes issue where Chrome fails to open PDF while Firefox/Edge work
- Critical for PDFs with Hindi/UTF-8 content where output buffering can corrupt binary data
ISSUE 1: {{SIGNATURE_BLOCK}} placeholder not working
- Signature extraction was using wrong end markers
- Start: 'Signature of Consultant'
- End: 'Signature of Medical Officer', 'Signature of Receiver / Date'
- Problem: All three texts are in SAME table row
- Extraction cut off too early (found end marker immediately after start)
FIX: Remove end markers for signature block extraction
- Signature table is LAST element in content
- Extract from 'Signature of Consultant' to end of content
- Now captures entire signature table correctly
ISSUE 2: Other Advice not showing in PDF
- Cached content was generated BEFORE 'Other Advice:' label was added
- Database has data but cached HTML doesn't include label
FIX: Created cache clearing utility
- clear_discharge_cache.php script
- Clears cached content for specified IPD
- Forces regeneration with new labels
DEBUG TOOLS ADDED:
- debug_discharge_content.php - Check what's in cached content
- clear_discharge_cache.php - Clear cache to force regeneration
TESTING:
1. php clear_discharge_cache.php 1
2. Visit /Ipd_discharge/preview_discharge_report/1?regen=1
3. Verify PDF shows Other Advice: and Discharge Summary: labels
4. Verify {{SIGNATURE_BLOCK}} placeholder works in templates
ISSUE 1: Discharge Summary showing in PDF but without label
ISSUE 2: Need separate placeholders for Other Advice and Discharge Summary
FIX 1: Added label to instruction_remark in PDF
- Added Discharge Summary: label before instruction_remark content
FIX 2: Added new placeholders for template flexibility
- OTHER_ADVICE - Shows only Other Advice field
- INSTRUCTION_REMARK or DISCHARGE_ADVICE - Shows only Discharge Summary field
- DISCHARGE_INSTRUCTIONS - Shows full section with all advice
Now templates can control what appears where using specific placeholders.
CHANGES:
1. Line 2248 - Added Discharge Summary: label
2. Line 574-575 - Extract new tokens from content
3. Line 579-581 - Add tokens to vars array
4. Line 520-522 - Add markers to extraction list
TESTING: Go to discharge PDF and verify labels show correctly
ISSUE: instruction_remark (Discharge Summary) data appearing in instruction_other (Other Advice) field after save
ROOT CAUSE: Legacy table sync was writing wrong variable
- Save handler at line 5478 was writing \ to legacy food_text
- Should have been writing \
- When legacy fallback loads (if food_ids empty), wrong data copied to instruction_other
FIX: Changed line 5478 in Ipd_discharge.php
- Before: \['food_text'] = \;
- After: \['food_text'] = \;
FILES MODIFIED:
1. app/Controllers/Ipd_discharge.php - Fixed legacy save handler
2. app/Views/billing/ipd/discharge_create.php - Removed debug comments
TESTING STEPS:
1. Clear corrupt legacy data: php clear_legacy_food.php 1
2. Go to /Ipd_discharge/ipd_select/1
3. Clear both fields
4. Type 'Hello Other' in Other Advice
5. Type 'Hello Discharge' in Discharge Summary
6. Click 'Save Discharge Advice'
7. After reload: Other Advice should show 'Hello Other', NOT 'Hello Discharge'
SCRIPTS ADDED:
- check_instruction_db.php - Check database values
- check_legacy_food.php - Check legacy table
- clear_legacy_food.php - Clean corrupt legacy data
- list_instruction_records.php - List all records
Added sanitizeCKEditorSpecialChars() method to handle smart quotes, zero-width spaces, em/en dashes, and other special characters that can break mPDF.
Removes invisible Unicode chars, replaces special punctuation with ASCII equivalents, cleans HTML comments.
ISSUE: Templates auto-regenerate when deleted
User deletes template ID 3, page reload creates template ID 4 automatically
ROOT CAUSE: ensureDefaultDischargeTemplateSeeded() called on EVERY page load
- discharge_templates() method called it on every view
- getDischargeTemplateRows() called it on every discharge PDF generation
- When count=0, it auto-inserted default templates
- Made it impossible to delete all templates
SOLUTION: Changed to manual opt-in template creation
1. Removed auto-seeding calls:
- Setting/Template::discharge_templates() - removed ensureDefaultDischargeTemplateSeeded()
- Ipd_discharge::getDischargeTemplateRows() - removed ensureDefaultDischargeTemplateSeeded()
- Both now only call ensureDischargeTemplateTable() (structure check only)
2. Added manual template creation:
- New 'Create Default Templates' button shown when table is empty
- New endpoint: discharge_templates_seed() - creates defaults on demand
- Route: POST /setting/template/discharge_templates_seed
- Button confirms before creating, shows success message, reloads page
3. Preserved seeding functions:
- ensureDefaultDischargeTemplateSeeded() kept but not auto-called
- Can be manually triggered via new button
- Creates 'Default Discharge Template' and 'NABH Compliant Discharge Summary'
BEHAVIOR NOW:
- Users can delete ALL templates - they stay deleted
- Empty table shows helpful message with creation button
- Templates only created when user explicitly requests them
- No more surprise template regeneration
TESTING:
- Delete all templates
- Page reloads showing empty table with button
- Click 'Create Default Templates'
- 2 templates created (ID 1 and 2)
ISSUE: Medicines were being saved twice - once via Add button AJAX, and again when clicking 'Save Discharge Advice' button.
ROOT CAUSE:
1. Legacy code in save_main handler (lines 5489-5538) read 'discharge_medicine_json' POST field
2. JavaScript serializeDischargeMedicineTable() collected all medicine table rows into JSON
3. On form submit (Save Discharge Advice), JSON was sent and controller re-inserted all medicines
4. Result: duplicate entries in ipd_discharge_prescrption_prescribed table
SOLUTION:
- DISABLED controller code that processes discharge_medicine_json in save_main handler
- DISABLED JavaScript call to serializeDischargeMedicineTable() on form submit
- Medicines now saved ONLY via Add button AJAX (action='add_drug')
- Save Discharge Advice button now only saves dietary advice, instructions, review after, etc.
TESTING:
1. Add medicines via Add button (saves immediately)
2. Click Save Discharge Advice (does NOT save medicines again)
3. No duplicate entries in database
- Add getDoseMasterRows() to load dose masters (opd_dose_shed, opd_dose_when, opd_dose_frequency)
- Add buildDosageDisplay() to format dosage: 'BID (TWO TIME A DAY) / दिन में दो बार लेना'
- Update buildAutoDischargeContent() medication table query to include dosage, dosage_when, dosage_freq fields
- Change table columns from '#|Medicine|Qty|Days|Notes' to 'Medicine Name|Dosage|Qty|Day' matching old CI3 format
- Dosage column shows full text labels from master tables, not numeric IDs
- Add composition support as small text under medicine name for legacy compatibility
- Remove # serial number column and Notes column to match old HMS format
Problem:
- PDF shows old cached content with 'Date of Discharge: 2026-06-05'
- Debug page shows correct new format (status header only)
- Discharge content is cached in ipd_discharge table
- PDF generation uses cached content from database
- Fix only affects newly generated content, not cached content
Root Cause:
When discharge is first created, content is generated and saved to DB.
PDF generation flow (show_discharge method):
1. Load cached content from ipd_discharge table
2. Only regenerate if content is empty
3. Apply template and render PDF
So old discharges still have old format cached.
Solution:
Added 'refresh' parameter to show_discharge() method:
- If refresh=1, ignore cached content and regenerate
- Regenerate using buildAutoDischargeContent() (has fix)
- Save new content to database
- Then render PDF
Code Change (line 5900-5913):
Added:
\ = (int) (\->request->getGet('refresh') ?? 0) === 1;
Changed:
if (\ || trim(strip_tags(\)) === '') {
Usage:
1. Original URL (uses cache):
http://localhost:8080/Ipd_discharge/show_discharge/1/1?tpl=1
2. Force refresh (regenerates):
http://localhost:8080/Ipd_discharge/show_discharge/1/1?tpl=1&refresh=1
3. After first refresh, cache is updated with new format
Subsequent views will use new cached content
Note:
- preview_discharge_report() already has 'regen=1' parameter
- Same functionality, different parameter name
- Both methods now support force regeneration
For existing discharges:
- Add &refresh=1 to URL once to update cache
- Or recreate discharge from edit page
- Or clear ipd_discharge.content for that IPD ID"
Problem:
- {{DISCHARGE_SUMMARY}} placeholder was showing:
1. Discharge status header (Discharge Summary/LAMA/etc.)
2. Department information
3. Treating Doctor(s) information
4. Date of Admission
- User wants ONLY the status header in this placeholder
- Extra information should be removed from {{DISCHARGE_SUMMARY}}
Solution:
Modified buildAutoDischargeContent() method:
Before (lines 1679-1703):
- Built dischargeSummaryParts array with department/doctor/date
- Added all info in one section with header + div containing details
After:
- {{DISCHARGE_SUMMARY}} now contains ONLY the status header
- Removed department, doctor, and admission date from this section
- Status header comes from ipd_discharg_status table via:
ipd_master.ipd_status (or discarge_patient_status) = ipd_discharg_status.id
Returns ipd_discharg_status.status_details field
Status Values:
- ipd_status = 0 → 'Discharge Summary' (hardcoded)
- ipd_status = 1 → From DB: 'Discharge Summary'
- ipd_status = 2 → From DB: 'Leave Against Medical Advice'
- ipd_status = 3 → From DB: 'Refer To Higher Centre'
- ipd_status = 5 → From DB: 'Dead Summary'
- ipd_status = 7 → From DB: 'Discharge on Request'
Now {{DISCHARGE_SUMMARY}} outputs:
<h4 class='discharge-section-heading'>Discharge Summary</h4>
OR
<h4 class='discharge-section-heading'>Leave Against Medical Advice</h4>
That's it - no extra lines below.
If user needs department/doctor/date info, they can use individual tokens:
- {{DEPARTMENT}}
- {{DOCTOR_NAMES}}
- {{ADMIT_DATE_ONLY}}
- {{DISCHARGE_DATE_ONLY}}
- {{ADMISSION_TIME}}
- {{DISCHARGE_TIME}}
Or use {{PATIENT_INFO_TABLE}} which has all patient demographics.
Testing:
- Visit: /Ipd_discharge/debug_discharge_html/1
- Check {{DISCHARGE_SUMMARY}} section
- Should only show status header, no other info"
CRITICAL FIX - User Requirement:
- If ipd_status = 0: Show 'Discharge Summary' only
- If ipd_status != 0: Show value from ipd_discharge_status.status_details
- Remove 'Date of Discharge' line completely
Problem:
- {{DISCHARGE_SUMMARY}} still showed 'Discharge Summary' header regardless of status
- 'Date of Discharge: YYYY-MM-DD' line was appearing
- Previous fix only worked on saved content via regex, not on auto-generated content
Root Cause:
- buildAutoDischargeContent() method had hardcoded 'Discharge Summary' header
- getDischargeStatusText() was querying database but header wasn't being used
- Date of Discharge was being added to dischargeSummaryParts array
Solution:
1. Updated buildAutoDischargeContent() method (line ~1670):
- Removed 'Date of Discharge' from dischargeSummaryParts array
- Added comment: 'NOTE: Date of Discharge removed per user request'
- Call getDischargeStatusText() to get dynamic header
- Use status-based header instead of hardcoded 'Discharge Summary'
- Now generates: <h4>status_details_value</h4> instead of <h4>Discharge Summary</h4>
2. Updated getDischargeStatusText() method (line ~1047):
- Added early return for ipd_status = 0: returns 'Discharge Summary'
- For ipd_status != 0: queries ipd_discharge_status table
- Returns status_details field (primary)
- Falls back to status_desc if status_details is empty
- Final fallback: 'Discharge Summary'
3. Updated buildAutoDischargeSummaryTable() method (line ~1517):
- Used for {{PATIENT_INFO_TABLE}} token
- Now uses getDischargeStatusText() for header
- Changes <h2>Discharge Summary</h2> to <h2>status_value</h2>
- Keeps patient info table structure unchanged
Status Mapping Logic:
- ipd_status = 0 → 'Discharge Summary' (special case, no DB lookup)
- ipd_status = 1 → Query DB → 'Discharge Summary' (from status_details)
- ipd_status = 2 → Query DB → 'Leave Against Medical Advice'
- ipd_status = 3 → Query DB → 'Refer To Higher Centre'
- ipd_status = 5 → Query DB → 'Dead Summary'
- ipd_status = 7 → Query DB → 'Discharge on Request'
Content Structure Before:
<h4>Discharge Summary</h4>
<div>
<b>Department:</b> General Surgery<br>
<b>Treating Doctor(s):</b> Dr. Name<br>
<b>Date of Admission:</b> 04-06-2026<br>
<b>Date of Discharge:</b> 07-06-2026
</div>
Content Structure After:
<h4>Leave Against Medical Advice</h4>
<div>
<b>Department:</b> General Surgery<br>
<b>Treating Doctor(s):</b> Dr. Name<br>
<b>Date of Admission:</b> 04-06-2026
</div>
Testing:
1. Set ipd_master.ipd_status = 0 → regenerate → should show 'Discharge Summary'
2. Set ipd_master.ipd_status = 1 → regenerate → should show 'Discharge Summary'
3. Set ipd_master.ipd_status = 2 → regenerate → should show 'Leave Against Medical Advice'
4. Set ipd_master.ipd_status = 5 → regenerate → should show 'Dead Summary'
5. 'Date of Discharge' line should NOT appear in any case
Files Modified:
- app/Controllers/Ipd_discharge.php:
- getDischargeStatusText() - Added ipd_status = 0 check
- buildAutoDischargeContent() - Removed Date of Discharge, use dynamic header
- buildAutoDischargeSummaryTable() - Use dynamic header
IMPORTANT: User must REGENERATE discharge summary after pulling code
- Click 'Regenerate' button on discharge preview page
- Or delete existing discharge content and reload form
- Old saved content will still have old header until regenerated
Problem:
- {{DISCHARGE_SUMMARY}} always showed 'Discharge Summary' header
- User wants header to change based on patient discharge status
- 'Date of Discharge' line was unwanted
- Need to see HTML source to check spacing between placeholders
Solution:
1. Added getDischargeStatusText() method:
- Queries ipd_discharge_status table
- Uses ipd_master.ipd_status as lookup key
- Returns status_details or status_desc from table
- Fallback: 'Discharge Summary' if status not found
2. Modified buildDischargeSectionTokenVars():
- Now accepts panelData parameter (has ipd info)
- Gets discharge status text via getDischargeStatusText()
- Replaces 'Discharge Summary' heading with status text
- Removes 'Date of Discharge: YYYY-MM-DD' line
- Uses regex to handle multiple HTML tag formats (h1-h6, b, strong, p)
3. Added debug_discharge_html() endpoint:
- URL: /Ipd_discharge/debug_discharge_html/{ipdId}
- Shows complete HTML source with syntax highlighting
- Displays rendered template with all placeholders replaced
- Useful for checking spacing and HTML structure
- Auto-generates content if not saved yet
Database Schema:
- ipd_master.ipd_status: 0-7 (discharge status ID)
- ipd_discharge_status table:
- id: 0-7
- status_desc: 'Status Pending', 'Improved/Recovered', 'LAMA', 'Referred', etc.
- status_details: 'Discharge Summary', 'Leave Against Medical Advice', etc.
Status Mapping (from user's data):
- 0: Status Pending
- 1: Improved/Recovered -> 'Discharge Summary'
- 2: LAMA -> 'Leave Against Medical Advice'
- 3: Referred -> 'Refer To Higher Centre'
- 4: Satisfactory -> 'Discharge Summary'
- 5: Expired -> 'Dead Summary'
- 6: Admission Cancelled
- 7: Discharge on Request
Regex Replacements:
1. Header replacement: /<h[1-6][^>]*>\s*Discharge\s+Summary\s*<\/h[1-6]>/i
2. Bold replacement: /<(b|strong)>\s*Discharge\s+Summary\s*<\/\1>/i
3. Date removal (br): /Date\s+of\s+Discharge\s*:\s*[0-9\-\/]+\s*<br\s*\/?>/i
4. Date removal (p): /<p[^>]*>\s*Date\s+of\s+Discharge\s*:\s*[0-9\-\/]+\s*<\/p>/i
Usage:
- When ipd_status = 1: Shows 'Discharge Summary' (from status_details)
- When ipd_status = 2: Shows 'Leave Against Medical Advice'
- When ipd_status = 5: Shows 'Dead Summary'
- 'Date of Discharge: 2026-06-07' line is removed from all sections
Debug Endpoints:
1. /Ipd_discharge/debug_ipd_fields/{ipdId} - Shows database field values
2. /Ipd_discharge/debug_discharge_html/{ipdId} - Shows complete HTML source
Testing:
- Change ipd_master.ipd_status value
- Regenerate discharge summary
- Header should change based on status
- Date line should not appear
- Use debug_discharge_html to see full HTML structure
CRITICAL FIXES based on actual database schema analysis:
Problem 1: {{ADMISSION_TIME}} showing 12:00 AM
- Code was looking for: register_time, admission_time (don't exist)
- Actual field: reg_time (stores '14:44' format)
- Fix: Changed to use ipd->reg_time field
Problem 2: {{DISCHARGE_TIME}} showing 12:00 AM
- Code was looking for: discharge_date datetime field
- Actual field: discharge_time (stores '05:30' format)
- Fix: Changed to use ipd->discharge_time field directly
Problem 3: {{DEPARTMENT}} showing blank
- Code was using: hc_department.id field for lookup
- Actual field: hc_department.iId (primary key is iId, not id)
- Fix: Changed WHERE clause from 'id' to 'iId'
Problem 4: {{DOCTOR_NAMES}} showing blank
- Code was looking for: doc_list, doctor_id, doc_id (don't exist)
- Actual field: r_doc_id (referring doctor ID = 12)
- Fix: Changed primary lookup to r_doc_id field
Technical Improvements:
1. Enhanced safeTime() function:
- Added regex parsing for 'HH:MM' time format
- Validates hour (0-23) and minute (0-59) ranges
- Converts 24-hour to 12-hour format with AM/PM
- Example: '14:44' -> '02:44 PM', '05:30' -> '05:30 AM'
- Falls back to strtotime() for datetime formats
- Returns empty string for invalid times
2. Updated getDischargeDoctorNames():
- Primary: ipd->r_doc_id (referring doctor)
- Fallback: ipd->doc_list (comma-separated IDs)
- Fallback: ipd->doctor_id, doc_id, consultant_id
- Queries doctor_master table
- Gets specializations from doc_spec + med_spec
- Format: 'Dr. Name [Specialization]' or 'Title Name [Spec]'
3. Updated getDischargeDepartmentName():
- Changed: WHERE id = dept_id
- To: WHERE iId = dept_id
- Keeps fallback to ipd_department table
- Returns department vName field
4. Updated debug endpoint:
- Now checks: reg_time, reg_time_bak, discharge_time, discharge_time_bak
- Shows: dept_id, r_doc_id, r_doc_name
- Removed non-existent fields from output
Database Schema (from live data):
- ipd_master.reg_time: '14:44' (admission time)
- ipd_master.discharge_time: '05:30' (discharge time)
- ipd_master.dept_id: 2 -> hc_department.iId (vName field)
- ipd_master.r_doc_id: 12 -> doctor_master.id (p_fname field)
Testing:
- Admission Time should show: 02:44 PM (from reg_time '14:44')
- Discharge Time should show: 05:30 AM (from discharge_time '05:30')
- Department should show department name from hc_department
- Doctor Names should show doctor name with specialization
Problem:
- {{ADMISSION_TIME}} showing 12:00 AM instead of actual time
- {{DISCHARGE_TIME}} showing 12:00 AM instead of actual time
- {{DEPARTMENT}} showing blank
- {{DOCTOR_NAMES}} showing blank
Need to identify:
1. Which datetime fields exist in ipd_master table
2. Which department field exists (dept_id, department_id, or department)
3. Which doctor field exists (doc_list, doctor_id, doc_id, consultant_id)
4. What values are actually stored in these fields
Debug Endpoint Added:
- URL: /Ipd_discharge/debug_ipd_fields/{ipdId}
- Example: http://localhost:8080/Ipd_discharge/debug_ipd_fields/123
- Replace 123 with actual IPD ID from your discharge page
What it shows:
- All available field names in ipd_master table
- Values of: register_date, discharge_date, register_time, discharge_time, admission_time
- Values of: dept_id, department_id, department
- Values of: doc_list, doctor_id, doc_id, consultant_id
- Result of getDischargeDepartmentName() function
- Result of getDischargeDoctorNames() function
- Result of safeTime() for register_date and discharge_date
Usage:
1. Go to your discharge form
2. Note the IPD ID from the URL
3. Open: /Ipd_discharge/debug_ipd_fields/{ipdId} in browser
4. Check the JSON output to see which fields exist and their values
This will help identify:
- If datetime fields have time component or just date
- Which field names are actually used in your database
- Why placeholders are showing blank/wrong values
Problem:
- {{DEPARTMENT}} and {{DOCTOR_NAMES}} placeholders not working in discharge templates
- Complex GROUP_CONCAT query with LEFT JOINs causing issues
- Query might return NULL or fail silently when specializations missing
Solution - getDischargeDoctorNames():
- Removed GROUP_CONCAT to avoid SQL aggregation issues
- Split into two separate queries: doctors first, then specializations
- Changed specialization JOIN from LEFT to INNER to avoid NULL values
- Query doctors from doctor_master separately
- For each doctor, query doc_spec + med_spec to get specializations
- Build full name format: 'Dr. Name [Spec1, Spec2]' or 'Title Name [Spec1, Spec2]'
- Return comma-separated list of all doctors
Technical Changes:
- First query: SELECT d.id, d.p_fname, d.p_title FROM doctor_master
- Second query (per doctor): SELECT m.SpecName FROM doc_spec s JOIN med_spec m
- Use INNER JOIN for specializations to only include valid matches
- Handle missing specializations gracefully (show doctor name without brackets)
- Handle missing title (default to 'Dr.')
getDischargeDepartmentName() - Already Working:
- Tries dept_id from ipd_master
- Queries hc_department table first
- Falls back to ipd_department table
- Falls back to direct department field
- Returns empty string if all methods fail
Token Flow:
1. buildDischargeSummaryTokenVars() calls both functions
2. Returns array with 'DEPARTMENT' and 'DOCTOR_NAMES' keys
3. buildDischargeTemplateTokenVars() merges these tokens
4. Creates lowercase, UPPERCASE, and Ucfirst versions of all tokens
5. applyDischargeTemplateTokens() replaces {{TOKEN}} and {TOKEN} in template
Available Token Variants:
- {{DEPARTMENT}}, {{department}}, {{Department}}
- {{DOCTOR_NAMES}}, {{doctor_names}}, {{Doctor_names}}
- {{DOCTOR_NAME}} (singular alias for same value)
Example Output:
- Single doctor: 'Dr. John Smith [Cardiology]'
- Multiple doctors: 'Dr. John Smith [Cardiology], Dr. Jane Doe [Neurology]'
- No specialization: 'Dr. John Smith'
- With title: 'Prof. John Smith [Cardiology]'
Testing:
- Ensure doc_list field in ipd_master has comma-separated doctor IDs
- Falls back to doctor_id, doc_id, or consultant_id if doc_list empty
- Returns empty string if no valid doctor IDs found