Commit Graph

323 Commits

Author SHA1 Message Date
69373af48c Fix: Add CKEditor special character sanitization for PDF generation
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.
2026-06-07 13:53:03 +05:30
328de6279d Fix: Disable auto-generation of discharge templates + add manual creation button
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)
2026-06-07 11:38:18 +05:30
d923dc6c29 Fix: Template delete page refresh + add debug logging
ISSUE: Template delete returns success but record appears not deleted

ROOT CAUSE: Delete WAS working (verified in database), but page wasn't refreshing properly after success

CHANGES:

1. JavaScript fix (discharge_templates.php):
   - Replaced conditional load_form_div / location.assign logic
   - Now uses simple location.reload() after 500ms delay
   - Ensures page always refreshes after successful delete

2. Controller debug logging (Template.php):
   - Added comprehensive debug logs to trace delete flow
   - Logs: request ID, row fetch, query compilation, affected rows
   - Logs error codes and messages for troubleshooting
   - Will help diagnose future issues

VERIFIED:
- Database confirmed template ID 2 was deleted
- Only frontend refresh was failing
- Now forces reload to show updated list
2026-06-07 11:32:24 +05:30
ea07063675 Fix: Prevent duplicate medicine saves on 'Save Discharge Advice' button
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
2026-06-07 09:03:12 +05:30
783ddcbacc Fix discharge medications: add Dosage column with English+Hindi labels
- 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
2026-06-07 08:43:45 +05:30
89c150324f Add refresh parameter to force regenerate cached discharge content in PDF
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"
2026-06-07 08:24:38 +05:30
d096585df6 \Fix {{DISCHARGE_SUMMARY}} placeholder - remove extra information
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"
2026-06-07 08:12:03 +05:30
751fdfaedb Remove all inline styles and use semantic CSS classes
Problem:
- Inline styles in HTML override custom CSS defined in templates
- User's custom styles (font-family, table borders, padding) not applied
- Hard to maintain and customize styling
- Inline styles have higher specificity than class/ID selectors

Solution:
Replaced all inline styles with semantic CSS classes:

Section Headings:
- style='margin:16px 0 8px 0' → class='discharge-section-heading' (h4)
- style='text-align:center;margin:...' → class='discharge-title' (h2)

Lists:
- style='margin:0 0 10px 20px' → class='discharge-list' (ul/ol)
- style='margin:4px 0 8px 20px' → class='discharge-sublist' (ul)
- style='list-style:none' → class='no-bullet' (li)

Text:
- style='color:#475569' → class='discharge-remark' (span)

Tables:
- style='width:100%;border-collapse:...' → class='discharge-table'
- Medicine table → class='discharge-medicine-table'
- Patient info → class='discharge-info-table'
- Signature block → class='discharge-signature-table'
- Removed all inline width/align styles from th/td

Containers:
- style='margin-bottom:8px' → class='discharge-field'
- style='margin-top:6px' → class='discharge-footer'
- style='margin-bottom:6px' → class='discharge-item'
- Added class='discharge-section' for section wrappers

Separators:
- style='margin:1px;padding:0' → class='discharge-separator' (hr)

Changes Made:
1. app/Controllers/Ipd_discharge.php:
   - buildAutoDischargeContent() - All section builders
   - buildAutoDischargeSummaryTable() - Patient info table
   - addListSection() - List sections
   - buildNursingTrendSection() - Nursing trends
   - defaultDischargeTemplateHtml() - Default template
   - nabhDischargeTemplateHtml() - NABH template

2. Created DISCHARGE_CSS_CLASSES.md:
   - Complete CSS class reference
   - Suggested stylesheet with all classes
   - Customization guide
   - Example usage
   - Print optimization styles

Benefits:
✓ User's custom CSS now applies correctly
✓ No style conflicts or overrides
✓ Easy to customize via CSS
✓ Consistent styling across all sections
✓ Maintainable - change CSS once, applies everywhere
✓ Print-friendly with @media print rules

Usage:
1. Copy CSS from DISCHARGE_CSS_CLASSES.md
2. Add to discharge template 'template_css' field
3. Or add to global print CSS
4. Customize colors/spacing/fonts as needed

The existing body/table/td/p styles will now work properly
No inline styles to override custom CSS anymore
2026-06-07 07:56:11 +05:30
3c430fa386 Fix discharge status header in auto-generated content and remove Date of Discharge
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
2026-06-07 07:42:38 +05:30
09570e0793 Make discharge summary header dynamic based on discharge status
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
2026-06-07 07:35:36 +05:30
e152fab496 Fix discharge time placeholders and department/doctor field mappings
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
2026-06-07 07:07:35 +05:30
87789cff7a Add debug endpoint to check IPD master fields
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
2026-06-07 06:58:59 +05:30
51aa6400c8 Fix {{DEPARTMENT}} and {{DOCTOR_NAMES}} placeholder tokens
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
2026-06-07 06:48:58 +05:30
e731ef61e9 Fix discharge medicine save, add CKEditor, and Review After quick select
Medicine Saving Fix (MAJOR):
- Add hidden field 'discharge_medicine_json' to capture medicine table data
- Add serializeDischargeMedicineTable() function to convert DOM table to JSON before form submit
- Hook serialization into form submit event (before syncEditorValues)
- Update controller to process discharge_medicine_json in save_main action
- Insert medicines into ipd_discharge_prescrption_prescribed or ipd_discharge_prescription_prescribed table
- Medicine data now persists on page reload

CKEditor Integration:
- Initialize CKEditor for 'instruction_other' (Other Advice) field
- Initialize CKEditor for 'instruction_remark' (Discharge Summary) field
- Add simplified toolbar: Bold, Italic, Underline, Lists, Undo/Redo
- Properly destroy existing instances before re-initialization
- Height: 120px for Other Advice, 150px for Discharge Summary

Review After Quick Select Buttons:
- Add getNextVisitOptions() method to Ipd_discharge controller (copied from Opd_prescription)
- Add resolveFirstField() helper method
- Pass next_visit_options array to view
- Render quick select buttons: '3 Days (09-06-2026)', '1 Week (13-06-2026)', etc.
- Add JavaScript handler: click button → populate #discharge_review_after input
- Change Review After field from col-md-4 to col-md-6 for better layout
- Add col-md-12 wrapper for button row with flex-wrap gap-2 layout

Technical Details:
- Medicine JSON structure: {med_type, med_name, dosage, dosage_when, dosage_freq, no_of_days, qty, remark}
- JSON processing includes column existence checks for flexibility
- Quick select buttons use data-value attribute for value insertion
- Falls back to hardcoded date options if opd_nextvisit table missing
- Medicine insert uses med_id=0 to mark client-added medicines

User Workflow:
1. Add medicines → shown in table (Type, Medicine, Dose labels, Duration formatted)
2. Click 'Save Discharge Advice' → medicines serialize to JSON → POST to controller
3. Controller parses JSON → inserts to database → medicines persist on reload
4. Other Advice/Discharge Summary now have rich text editing (bold, lists, etc.)
5. Review After: click '1 Week (13-06-2026)' button → auto-fills input
2026-06-06 20:53:05 +05:30
0d9ff81a97 Add DataTables search box support to patient search - now handles both initial search and live filtering 2026-06-06 14:56:02 +05:30
d82078c9ed Convert patient search to DataTables with AJAX server-side processing
- Updated Patient::search() to only render view with search query
- Created Patient::search_ajax() method for DataTables AJAX endpoint
- Implemented server-side processing with pagination, sorting, and filtering
- Results ordered by Last Visit DESC by default
- Retrieves all patient data from patient_master table
- Added proper SQL escaping to prevent injection
- Updated Patient_Search_V.php to use DataTables with AJAX remote data
- Added DataTables configuration with 25 rows per page default
- Supports dynamic length menu (10, 25, 50, 100, All)
- Added route for billing/patient/search_ajax endpoint
- Improved user experience with loading spinner and customized messages
2026-06-06 14:52:10 +05:30
550e72eca8 Add IPD Discharge search functionality with AJAX
- Added 'Discharge' button in sidebar under In-Patient & Nursing Care section
- Created search_patient() and search_patient_ajax() controller methods
- Implemented AJAX-based patient search by IPD Code, UHID, Name, or Mobile
- Added ipd_discharge/search_patient.php view with dynamic results table
- Search results show patient details, bed info, doctor assignments, and discharge status
- Added 'Create Discharge' and 'Print Discharge' action buttons for each patient
- Integrated DataTables for sorting and pagination
- Fixed database column names (patient_master.gender, age, age_in_month)
- Added routes for search page and AJAX endpoint
2026-06-06 14:43:10 +05:30
16348d12db Fix placeholder tokens: DISCHARGE_SUMMARY, DOCTOR_NAMES, DEPARTMENT
- Add 'Discharge Summary' section to buildAutoDischargeContent with department, doctors, and dates so {{DISCHARGE_SUMMARY}} token extracts properly
- Improve getDischargeDepartmentName to support dept_id, department_id fields and check both hc_department and ipd_department tables
- Improve getDischargeDoctorNames to fallback to doctor_id, doc_id, consultant_id if doc_list is empty
- Update DISCHARGE_TEMPLATE_TOKENS.md to clarify what {{DISCHARGE_SUMMARY}} contains
- Clear cached content for IPD 1 to regenerate with new logic

Fixes issue where {{DISCHARGE_SUMMARY}}, {{DOCTOR_NAMES}}, {{DEPARTMENT}} tokens were not working (replaced with empty values)
2026-06-06 13:00:52 +05:30
3ad0025c24 Add new discharge template tokens: PATIENT_PHONE, INSURANCE_COMPANY, DOCTOR_NAMES
New placeholders added:

1. PATIENT_PHONE - Patient's phone number from patient_master.mphone1 or ipd_master.P_mobile1
   Example: 9876543210

2. INSURANCE_COMPANY - Insurance company name from ipd_master.ins_short_name or ins_company_name
   Example: Star Health Insurance
   Default: 'Direct' if no insurance

3. DOCTOR_NAMES / DOCTOR_NAME - Comma-separated list of treating doctors with specialties
   Example: Dr. Rajesh Kumar [General Medicine], Dr. Priya Sharma [Cardiology]
   Reads from ipd_master.doc_list field and joins with doctor_master and med_spec tables

Changes:
- Added getDischargeDoctorNames() helper function
- Updated buildDischargeSummaryTokenVars() to include three new tokens
- Updated DISCHARGE_TEMPLATE_GUIDE.md with new token documentation
- Added example template showing usage of new tokens
- Updated CI3 to CI4 migration mapping table

These tokens match the data shown in the auto-generated patient summary table
and allow templates to display this information in custom layouts.
2026-06-06 12:21:37 +05:30
6d72a5ec04 Correct fix: Remove patient table from auto-generated content, add as token
OLD CI3 BEHAVIOR (correct):
- Patient demographic table was IN the template file
- Content variables contained ONLY clinical sections
- Template controlled entire layout

NEW CI4 FIX (matching CI3):
- Patient table removed from buildAutoDischargeContent completely
- Added {{PATIENT_INFO_TABLE}} token for templates to use if needed
- Auto-generated content now contains ONLY clinical sections
- Blank template = blank output (no patient table forced)
- Templates can add patient table using token or individual tokens

Changes:
- Removed includeSummaryTable parameter from buildAutoDischargeContent
- Removed templateHasDemographicTokens helper (no longer needed)
- Added PATIENT_INFO_TABLE token in buildDischargeTemplateTokenVars
- Simplified show_discharge and preview_discharge_report (no template detection)
- Added comment explaining CI3 compatibility

This matches the old CI3 chamunda_discharge_template_html.php pattern where:
- Template had hardcoded patient table structure
- Variables like \, \ were injected
- Content did NOT include patient demographics by default
2026-06-06 12:09:57 +05:30
224ac5613c Fix: Skip auto-generated patient info table when template has demographic tokens
- Modified buildAutoDischargeContent to accept includeSummaryTable parameter
- Added templateHasDemographicTokens helper to detect if template has patient info tokens
- Updated show_discharge and preview_discharge_report to check template before auto-generation
- When template has {{PATIENT_NAME}}, {{UHID}}, etc., skip auto-summary table
- Added clear_discharge_content.php script to clear cached content for regeneration
- Solves issue where blank templates still showed patient information table
2026-06-06 11:49:52 +05:30
aa812354a6 Change mPDF default font from thin dejavusans to medium-weight freeserif for better Hindi/Devanagari rendering
- Updated all controllers: Opd, Diagnosis, Billing\Charges, Billing\Ipd, Storestock, AbdmGateway
- freeserif provides better Unicode coverage and more readable font weight
- Ipd_discharge and DoctorDocument already used good fonts (freeserif/freesans)
- Added comprehensive font configuration documentation
2026-06-06 11:19:35 +05:30
38e06ed1d7 Add 'Save in Master' quick-add for Course/Treatment section
FEATURE: Quick-add functionality for Course/Treatment master terms

NEW COMPONENTS:
1. Course master table: ipd_discharge_course_master
   - term_name, term_code, icd_code, is_active
   - Auto-creates on first use
2. Controller endpoints in Ipd_discharge.php:
   - course_master_lookup() - AJAX autocomplete search
   - course_master_list() - Full list with filters
   - course_master_save() - Create/update records
   - course_master_delete() - Remove records
3. Routes added to Config/Routes.php:
   - Ipd_discharge/course_master_lookup (GET)
   - Ipd_discharge/course_master_list (GET)
   - Ipd_discharge/course_master_save (POST)
   - Ipd_discharge/course_master_delete (POST)

VIEW CHANGES (discharge_create.php):
- Course input now uses input-group layout
- Green 'Save in Master' button added
- Custom dropdown for autocomplete with code badges
- Hidden field: new_course_master_id
- JavaScript: initCourseAutocomplete() function
- Integrated with existing quickAddTermModal

QUICK-ADD MODAL ENHANCEMENT:
- Now supports 3 types: surgery, procedure, course
- Dynamic endpoint selection based on type
- Course type uses course_master_save endpoint
- Auto-populates course input and master_id on save

AUTOCOMPLETE FEATURES:
- Type 2+ characters → searches course_master table
- Dropdown shows: Name + SNOMED/ICD code badges
- Arrow key navigation (Up/Down/Enter/Escape)
- Click or Enter to select
- Hover highlighting with visual feedback
- 300ms debounce for performance

WORKFLOW:
1. Type course/treatment name
2. See dropdown with existing terms (if any)
3. Click 'Save in Master' to add new term
4. Modal opens with name pre-filled
5. Enter SNOMED/ICD codes (optional)
6. Save → Auto-populates input + master_id
7. Click '+ADD Row' to add to discharge

BENEFITS:
- Consistent UI across Surgery/Procedure/Course sections
- Fast term entry without leaving form
- Master library builds during workflow
- ABDM compliance with code support
- Reusable terms across discharges
2026-06-06 06:53:27 +05:30
5aad2b0950 Fix database error in complaint field update - remove non-existent updated_at column
BUGFIX: Database error when saving editable complaint fields

PROBLEM: After adding X-Requested-With header, the request reached
the controller but threw 'Database error' when trying to update the
complaint field.

ROOT CAUSE: The ipd_discharge_complaint table does not have an
'updated_at' column. The update query was trying to set:
  - comp_report or comp_remark (correct)
  - updated_at (does not exist) ← DATABASE ERROR

INVESTIGATION: Checked how complaint rows are inserted elsewhere in
the codebase (line 4117-4130). The table has 'update_by' column for
tracking changes, not 'updated_at'.

SOLUTION:
1. Removed hardcoded 'updated_at' => date(...) from update data
2. Only update the target field (comp_report or comp_remark)
3. Conditionally set 'update_by' if column exists (audit trail)
4. Use session full_name for user tracking

Now editable complaint fields save successfully without database errors.
2026-06-06 05:33:18 +05:30
ddec3cec69 Fix editable complaint fields - attach handlers after section reload
BUGFIX: Event handlers were only attached on initial page load

PROBLEM: After implementing editable complaint fields, the inputs
wouldn't become editable when adding new complaints via the Smart
Complaints Picker. The event handlers were only attached once on
page load, not after the section was dynamically reloaded.

ROOT CAUSE: When clicking Add or AI Draft buttons, the form submits
and the section-complaints area is reloaded from server. The new
complaint rows had the input fields but no event handlers attached.

SOLUTION:
1. Moved event handler attachment logic into initComplaintTools()
   function as new attachComplaintFieldHandlers() helper
2. This function is automatically called after section reload
3. Clone-and-replace pattern to prevent duplicate event handlers
4. Fixed table name: ipd_discharge_complaint (not ipd_discharge_comp)

FLOW:
- Add complaint → section reloads → initComplaintTools() runs →
  attachComplaintFieldHandlers() attaches blur/keydown handlers →
  editable fields work

Now clicking on complaint name/remark inputs works for both:
- Existing complaints (on page load)
- Newly added complaints (after section reload)
2026-06-06 05:18:35 +05:30
2e6bff35a0 Make IPD discharge complaint names and remarks editable like OPD form
FEATURE: Inline editing for discharge complaint table

PROBLEM: In IPD discharge form, complaint names and remarks were
read-only after adding. Unlike OPD consult form where you can edit
complaint names directly in the table.

SOLUTION:
1. VIEW Changes (discharge_create.php):
   - Changed complaint name and remark columns from static <td> to editable <input>
   - Added data attributes for complaint ID and original values
   - Added CSS classes: discharge-complaint-name-input, discharge-complaint-remark-input

2. JAVASCRIPT Changes:
   - Added saveComplaintField() function for AJAX updates
   - Event handlers on blur to save changes
   - Enter key support to blur and save
   - Visual feedback: green border on success, red on error
   - Auto-updates original value after successful save

3. CONTROLLER Changes (Ipd_discharge.php):
   - Added update_complaint_field() method for AJAX endpoint
   - Updates ipd_discharge_comp table (comp_report or comp_remark)
   - Returns JSON success/error response

BENEFITS:
- Same user experience as OPD consult form
- Edit complaints/remarks without page reload
- Immediate feedback on save success/failure
- Type correction and updates without removing and re-adding rows
2026-06-06 05:12:23 +05:30
a76f0dd9e7 Make pain scale more compact for prescription layout
Reduced sizes:
- Table width: 100% to 60%
- Debug text: 10px to 8px
- Triangle: 18px to 12px
- Number boxes: 14px to 10px with 3px padding
- Border: 3px to 2px for selected
- Labels: uniform 7px font
- Reduced all margins and spacing

Still maintains clear B&W visibility with triangle, gray background, and bold border.
2026-06-06 04:21:56 +05:30
6259001aec Fix emoji rendering issue - use PDF-safe symbols instead
PROBLEM: Emojis don't render in PDF (showing as empty boxes)

SOLUTION:
- Use down-pointing triangle (▼) using HTML entity &#9660;
- This Unicode character renders properly in mPDF/PDF generators
- Darker gray background (#cccccc) for better B&W visibility
- Bold 3px border on selected box
- Larger font and padding for selected number

Visual indicators that work in B&W printing:
- Triangle (▼) above selected level
- Dark gray shading on selected box
- Bold border on selected box
- Bold text for selected label
2026-06-06 04:19:20 +05:30
a047955c7c Add emojis to pain scale for clear black & white printing
PROBLEM: Color coding not visible in black & white printers

SOLUTION:
- Added emoji indicators: 😊 😐 😣 😖 😭 for pain levels 0-4
- Changed colors to black/white/gray for B&W compatibility
- Emoji shows above selected pain level (larger size)
- Bold 3px border on selected box (visible in B&W)
- Light gray background on selected box

Now pain scale is clear even in monochrome printing!
2026-06-06 04:14:12 +05:30
cad5730182 Disable static PNG pain scale images, use dynamic HTML instead
PROBLEM: Static pains_scale_0-4.png files all look identical (just gradient)
so users can't tell which pain level is selected.

SOLUTION: Comment out PNG file checking code to force use of dynamic HTML
with arrow indicator, colored backgrounds, and bold borders.

Now {{painscale_img}} will show:
- Debug line: 'Pain Level: X - Label'
- Down arrow (▼) above selected level
- Colored background (green to red) on selected box
- Bold 3px border on selected box
- Different pain values will have visibly different displays
2026-06-06 04:07:16 +05:30
e0c44e79b1 Add debug info to pain scale image showing actual value
- Add 'Pain Level: X - Label' text above pain scale for debugging
- Helps identify if pain_value is being read correctly from database
- Fix comparison to use strict equality check for  ===
- User can now see which pain level is actually being rendered
2026-06-06 03:59:14 +05:30
a14011e11c Improve pain scale visual indicator for better clarity
- Change checkmark to down arrow (▼) for better visibility in PDFs
- Increase arrow size to 20px and make it bold
- Use white background for non-selected boxes for stronger contrast
- Add thick black border (3px) on selected level for emphasis
- Selected box shows colored background with white text
- Increase selected label text size and make it bold black
- Clear visual distinction between selected (value 2 vs 4) levels now
2026-06-06 03:54:51 +05:30
5e929c858e Fix H_logo placeholder not being replaced in OPD print
- Add fallback to 'logo.png' when H_logo is not set in database or constants
- Prevents {{H_logo}} placeholder from appearing as literal text in print/PDF
- Resolves 400 Bad Request error when browser tries to load unreplaced placeholder
- Ensures prescription templates always have a valid logo path
2026-06-06 03:43:31 +05:30
a5ccd71714 Enhance pain scale image with visual tick indicator
- Add checkmark (✓) above the selected pain intensity level
- Use color gradient from green (No Pain) to red (Worst) for better visual clarity
- Highlight selected level with colored background and white text
- Thicker border on selected level for better emphasis
- Bold label text for selected intensity
- Makes pain scale selection immediately visible in printed/PDF prescriptions
2026-06-06 03:11:25 +05:30
8ef46cddb1 Add permission for Admin to edit patient name anytime
- Add billing.patient.edit-name-anytime permission to AuthGroups
- Update patient edit time restriction from 12 hours to 24 hours
- Modify Person_Edit_V to check for edit-name-anytime permission
- Admin can now edit patient name field (input_name) even after 24-hour window
2026-06-05 17:33:31 +05:30
6314bafa29 Combine textarea notes and test list into single {{investigation}} placeholder
Modified {{investigation}} to show both:
1. Textarea content (advise_investigation_notes) if present
2. Comma-separated test list from opd_prescription_investigation table

Both sources combined with newline separator when both exist.
{{Tadvise_test_list}} still available separately for backward compatibility.

Example combined output:
USG WHOLE ABDOMEN
CECT ABDOMEN
PAC
COMPLETE CARDIAC CHECK UP
PAP SMEAR
CBC (Complete Blood Count), ECG (Electrocardiogram), HBA1C, HBSAG, HCV, HIV I & II (Elisa), Liver Function Test (LFT), PT/INR, Renal Function Test (RFT), Urine Routine & Microscopy, X-Ray Chest (PA View)
2026-06-05 16:04:01 +05:30
a1fbf50f3f Change {{Tadvise_test_list}} to comma-separated format (not bullet list)
Changed placeholder behavior to match user requirements:
- {{investigation}} = textarea content (advise_investigation_notes) only
- {{Tadvise_test_list}} = comma-separated test names from table

Removed HTML bullet list formatting. Now outputs plain comma-separated text.

Example output:
CBC (Complete Blood Count), ECG (Electrocardiogram), HBA1C, HBSAG, HCV,
HIV I & II (Elisa), Liver Function Test (LFT), PT/INR, Renal Function Test (RFT),
Urine Routine & Microscopy, X-Ray Chest (PA View)

Both placeholders are independent and can show content simultaneously.
2026-06-05 15:59:16 +05:30
0b865f41cd Add textarea fallback for {{Tadvise_test_list}} placeholder
When opd_prescription_investigation table is empty but investigation
textarea has content, parse textarea content (split by newlines) to
populate the test list.

This handles cases where tests are entered as free-text in the
"Advise Investigation" textarea instead of selecting from master list.

Parsing logic:
- Try rx_investigations table first
- If empty, split investigation textarea by newlines
- Filter out empty lines and 'null' values
- Build HTML bullet list from either source

Example textarea content:
USG WHOLE ABDOMEN
CECT ABDOMEN
PAC
COMPLETE CARDIAC CHECK UP
PAP SMEAR

Will render as:
• USG WHOLE ABDOMEN
• CECT ABDOMEN
• PAC
• COMPLETE CARDIAC CHECK UP
• PAP SMEAR
2026-06-05 15:55:34 +05:30
1ca2dbce4c Add error handling and debug mode for OPD PDF rendering
Added try-catch around mPDF WriteHTML to catch rendering errors.
Changed debug_html mode to output as text/html for better preview.
Logs mPDF errors and returns user-friendly error message.

This will help diagnose blank page issues with {{Tadvise_test_list}} placeholder.
2026-06-05 15:38:26 +05:30
a540581bf9 Fix {{Tadvise_test_list}} to always populate from rx_investigations
Previous implementation only built test list when investigation textarea
was empty, preventing {{Tadvise_test_list}} from showing when textarea
had content.

Changes:
- Separated test list building from textarea population logic
- {{Tadvise_test_list}} now always populates when rx_investigations exists
- {{investigation}} still gets comma-separated fallback when textarea empty
- Both placeholders now work independently

Test list HTML output format:
<ul style="margin:3px 0 0 16px;padding:0;list-style-type:disc;">
  <li>CBC (Complete Blood Count)</li>
  <li>ECG (Electrocardiogram)</li>
  <li>HBA1C</li>
</ul>
2026-06-05 15:28:01 +05:30
6f6286f427 Add {{Tadvise_test_list}} placeholder for investigation test names
Created new OPD template placeholder to display investigation test names
as a formatted HTML list, separate from the textarea {{investigation}}.

Changes:
- app/Controllers/Opd.php:
  * Added $rxInvestigationTestList variable in buildLegacyDashboardTemplateVars()
  * Builds HTML <ul> list from rx_investigations test names
  * Added 'Tadvise_test_list' token to return array

- app/Views/billing/opd_template_builder.php:
  * Added 'Tadvise_test_list' to $opdConsultSet placeholder list
  * Now appears in "OPD Consult" group in template builder

Usage in OPD templates:
- {{investigation}} = comma-separated test names (for textarea)
- {{Tadvise_test_list}} = formatted HTML bullet list of test names (for print/display)

Example output:
<ul style="margin:3px 0 0 16px;padding:0;list-style-type:disc;">
  <li>CBC (Complete Blood Count)</li>
  <li>USG WHOLE ABDOMEN</li>
  <li>ECG (Electrocardiogram)</li>
</ul>
2026-06-05 15:15:15 +05:30
a572bbabae Simplify org-case queries to match CI3 pattern
Changed from complex string comparisons to simple integer comparisons matching legacy CI3 code:

OPD query (CI3 pattern):
- OLD: o.insurance_case_id = cast(c.id as char) OR o.insurance_case_id = c.case_id_code
- NEW: o.insurance_case_id = c.id (integer comparison)

Invoice query (CI3 pattern):
- OLD: i.insurance_case_id = c.id OR i.insurance_case_id = c.case_id_code (with NULL/0 checks)
- NEW: i.insurance_case_id = c.id (integer comparison)

Schema notes:
- opd_master.insurance_case_id: int(11)
- invoice_master.insurance_case_id: varchar(50) (but stores numeric IDs)

MySQL varchar→int conversion handles this correctly:
- '4997' → 4997 (matches)
- '0' → 0 (doesn't match 4997)
- 'C26060004997' → 0 (doesn't match 4997)

Applied to:
- getOrganizationCaseInvoiceRows() union query
- updateOrgTotals() aggregate queries
- search() case list query
2026-06-05 14:14:22 +05:30
ba9d17d5e6 Fix org-case invoice query: exclude empty insurance_case_id
Root cause: 50k+ invoices had insurance_case_id='0', and MySQL string comparison '0'='C26060004997' matched (both convert to numeric 0).

Fix: Exclude NULL/empty/'0' insurance_case_id before comparison in:
- getOrganizationCaseInvoiceRows() union query
- updateOrgTotals() aggregate queries

Result: Case C26060004997 now returns 9 rows instead of 56,786 (memory crash fixed)
2026-06-05 14:09:26 +05:30
4c5e0748bb Revert patient ID fallback - use insurance_case_id only 2026-06-05 13:55:50 +05:30
e80e18600d Fix org-case invoice scoping 2026-06-05 13:49:58 +05:30
6e3a355e03 Fix org case invoice and discharge PDF rendering 2026-06-05 13:43:19 +05:30
9c295c55c8 fix(discharge): enforce pdf response and sanitize template html 2026-06-05 12:03:47 +05:30
499a9bea31 fix(orgcase): fallback invoice linkage by patient and case 2026-06-05 11:45:01 +05:30
bd820a7939 fix(orgcase): use insurance amount1 in union invoice query 2026-06-05 11:37:23 +05:30
e7d6dd88d2 fix(orgcase): avoid missing discount column in union query 2026-06-05 11:34:52 +05:30