From 13e058db9551bfcd981316b3e70985af6fd62251 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Sat, 7 Mar 2026 04:15:04 +0530 Subject: [PATCH] IPD discharge: align preview with legacy pathology/report output --- app/Controllers/Ipd_discharge.php | 2136 +++++++++++++++++++ app/Views/billing/ipd/discharge_create.php | 967 +++++++++ app/Views/billing/ipd/discharge_preview.php | 131 ++ app/Views/billing/ipd/discharge_print.php | 90 + 4 files changed, 3324 insertions(+) create mode 100644 app/Controllers/Ipd_discharge.php create mode 100644 app/Views/billing/ipd/discharge_create.php create mode 100644 app/Views/billing/ipd/discharge_preview.php create mode 100644 app/Views/billing/ipd/discharge_print.php diff --git a/app/Controllers/Ipd_discharge.php b/app/Controllers/Ipd_discharge.php new file mode 100644 index 0000000..cdbc0e0 --- /dev/null +++ b/app/Controllers/Ipd_discharge.php @@ -0,0 +1,2136 @@ +ipdBillingModel = new IpdBillingModel(); + helper(['form', 'age']); + } + + private function dischargeTabUrl(int $ipdId): string + { + return site_url('billing/ipd/panel/' . $ipdId . '/tab/discharge-process'); + } + + private function requireAnyPermission(array $permissions) + { + if (! function_exists('auth')) { + return null; + } + + $user = auth()->user(); + if (! $user || ! method_exists($user, 'can')) { + return $this->response->setStatusCode(403)->setBody('Access denied'); + } + + foreach ($permissions as $permission) { + if ($user->can($permission)) { + return null; + } + } + + return $this->response->setStatusCode(403)->setBody('Access denied'); + } + + private function getDischargeContent(int $ipdId): string + { + if (! $this->db->tableExists('ipd_discharge')) { + return ''; + } + + $row = $this->db->table('ipd_discharge') + ->where('ipd_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + return (string) ($row['content'] ?? ''); + } + + private function currentUserLabel(): string + { + if (! function_exists('auth')) { + return 'system'; + } + + $user = auth()->user(); + if (! $user) { + return 'system'; + } + + $name = (string) ($user->username ?? $user->email ?? ''); + if ($name !== '') { + return $name; + } + + return 'user-' . (string) ($user->id ?? 0); + } + + private function saveDischargeContent(int $ipdId, string $content): bool + { + try { + if ($this->db->tableExists('ipd_master') && $this->db->fieldExists('ipd_create', 'ipd_master')) { + $this->db->table('ipd_master') + ->where('id', $ipdId) + ->update(['ipd_create' => 1]); + } + + if (! $this->db->tableExists('ipd_discharge') || ! $this->db->fieldExists('content', 'ipd_discharge')) { + return false; + } + + $builder = $this->db->table('ipd_discharge'); + $existing = $builder + ->where('ipd_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + if (! empty($existing['id'])) { + return (bool) $builder + ->where('id', (int) $existing['id']) + ->update(['content' => $content]); + } + + $insert = [ + 'ipd_id' => $ipdId, + 'content' => $content, + ]; + + // Legacy schema has NOT NULL columns without defaults. + $userLabel = substr($this->currentUserLabel(), 0, 50); + if ($this->db->fieldExists('created_by', 'ipd_discharge')) { + $insert['created_by'] = $userLabel; + } + if ($this->db->fieldExists('checked_by', 'ipd_discharge')) { + $insert['checked_by'] = $userLabel; + } + if ($this->db->fieldExists('created_datetime', 'ipd_discharge')) { + $insert['created_datetime'] = date('Y-m-d H:i:s'); + } + if ($this->db->fieldExists('ipd_discharge_print', 'ipd_discharge')) { + $insert['ipd_discharge_print'] = 0; + } + + return (bool) $builder->insert($insert); + } catch (\Throwable $e) { + log_message('error', 'Discharge content save failed for IPD {ipd}: {msg}', [ + 'ipd' => $ipdId, + 'msg' => $e->getMessage(), + ]); + + return false; + } + } + + private function tableHasColumns(string $table, array $columns): bool + { + if (! $this->db->tableExists($table)) { + return false; + } + + foreach ($columns as $column) { + if (! $this->db->fieldExists($column, $table)) { + return false; + } + } + + return true; + } + + private function byIpdRows(string $table, array $columns = ['*'], string $orderBy = 'id ASC', int $ipdId = 0): array + { + if ($ipdId <= 0 || ! $this->tableHasColumns($table, ['ipd_id'])) { + return []; + } + + $builder = $this->db->table($table)->where('ipd_id', $ipdId); + if ($columns !== ['*']) { + $builder->select(implode(',', $columns)); + } + + if ($orderBy !== '') { + $parts = explode(' ', trim($orderBy)); + $field = $parts[0] ?? 'id'; + $dir = strtoupper($parts[1] ?? 'ASC'); + $builder->orderBy($field, $dir === 'DESC' ? 'DESC' : 'ASC'); + } + + return $builder->get()->getResultArray(); + } + + private function safeDate(?string $dateValue): string + { + $value = trim((string) $dateValue); + if ($value === '' || $value === '0000-00-00' || $value === '1901-01-01') { + return ''; + } + + $ts = strtotime($value); + if ($ts === false) { + return ''; + } + + return date('d-m-Y', $ts); + } + + private function addListSection(array &$sections, string $title, array $rows): void + { + if (empty($rows)) { + return; + } + + $html = '

' . esc($title) . '

'; + + $sections[] = $html; + } + + private function buildAutoDischargeContent(int $ipdId, array $panelData): string + { + $ipd = $panelData['ipd_info'] ?? null; + $person = $panelData['person_info'] ?? null; + + $patientName = trim((string) ($person->p_fname ?? '')); + $patientCode = trim((string) ( + $person->uhid + ?? $person->UHID + ?? $person->patient_code + ?? $person->p_code + ?? $person->reg_no + ?? '' + )); + $patientId = (int) ($person->id ?? 0); + + $sections = []; + + $sections[] = '

IPD Discharge Summary

'; + $sections[] = '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
IPD: ' . esc((string) ($ipd->ipd_code ?? $ipdId)) . 'UHID: ' . esc($patientCode) . 'Date: ' . esc(date('d-m-Y')) . '
Patient: ' . esc($patientName) . 'Admit Date: ' . esc((string) ($ipd->str_register_date ?? '')) . 'Discharge Date: ' . esc((string) ($ipd->str_discharge_date ?? '')) . '
'; + + $complaints = $this->byIpdRows('ipd_discharge_complaint', ['comp_report', 'comp_remark'], 'id ASC', $ipdId); + $this->addListSection($sections, 'Presenting Complaints and Reason for Admission', $complaints); + $complaintRemark = $this->firstRowByIpd('ipd_discharge_complaint_remark', $ipdId); + $complaintRemarkText = trim((string) ($complaintRemark['comp_remark'] ?? '')); + if ($complaintRemarkText !== '') { + $sections[] = '
' . nl2br(esc($complaintRemarkText)) . '
'; + } + + $physicalExamRows = $this->getPhysicalExamRows($ipdId); + $generalRows = array_merge( + $physicalExamRows['general_group_1'] ?? [], + $physicalExamRows['general_group_2'] ?? [] + ); + if (! empty($generalRows)) { + $html = '

General Examination on Admission

' + . ''; + + $col = 0; + $perRow = 4; + foreach ($generalRows as $row) { + if ($col % $perRow === 0) { + $html .= ''; + } + + $label = (string) ($row['label'] ?? ''); + $value = trim((string) ($row['value'] ?? '')); + $html .= ''; + + if ($col % $perRow === $perRow - 1) { + $html .= ''; + } + $col++; + } + + while ($col % $perRow !== 0) { + $html .= ''; + if ($col % $perRow === $perRow - 1) { + $html .= ''; + } + $col++; + } + + $html .= '
' . esc($label) . ': ' . esc($value) . '
 
'; + $sections[] = $html; + } + + $sysRows = $physicalExamRows['systemic'] ?? []; + $sysHtml = ''; + foreach ($sysRows as $row) { + $value = trim((string) ($row['value'] ?? '')); + if ($value === '') { + continue; + } + + $name = trim((string) ($row['name'] ?? 'Systemic Examination')); + $sysHtml .= '
' . esc($name) . ': ' . nl2br(esc($value)) . '
'; + } + if ($sysHtml !== '') { + $sections[] = '

Other / Systemic Examinations

' . $sysHtml; + } + + $personalHistory = []; + if ($patientId > 0 && $this->db->tableExists('patient_master')) { + $patientRow = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray() ?? []; + $historyMap = [ + 'is_smoking' => 'Smoking', + 'is_alcohol' => 'Alcohol', + 'is_drug_abuse' => 'Drug abuse', + 'is_tobacoo' => 'Tobacco', + 'is_hypertesion' => 'Hypertension', + 'is_niddm' => 'Type 2 diabetes mellitus (DM)', + 'is_hbsag' => 'HBsAg', + 'is_hcv' => 'HCV', + 'is_hiv_I_II' => 'HIV I & II', + 'Others' => 'Others', + ]; + + foreach ($historyMap as $field => $label) { + if ((int) ($patientRow[$field] ?? 0) === 1) { + $personalHistory[] = $label; + } + } + } + + if (! empty($personalHistory)) { + $sections[] = '

Personal History

' . esc(implode(', ', $personalHistory)) . '
'; + } + + $admitDate = $this->normalizeDateValue((string) ($ipd->register_date ?? '')) ?? ''; + $dischargeDate = $this->normalizeDateValue((string) ($ipd->discharge_date ?? '')) ?? date('Y-m-d'); + $savedClinicalDates = $this->getSavedClinicalLabDates($ipdId); + $clinicalLabRows = $this->getClinicalInvestigationLabRows($patientId, $admitDate, $dischargeDate, $savedClinicalDates); + + $effectiveClinicalDates = $savedClinicalDates; + if (empty($effectiveClinicalDates)) { + foreach ($clinicalLabRows as $row) { + $dt = $this->normalizeDateValue((string) ($row['inv_date'] ?? '')); + if ($dt !== null) { + $effectiveClinicalDates[$dt] = $dt; + } + } + $effectiveClinicalDates = array_values($effectiveClinicalDates); + } + + $pathologyMatrix = $this->getClinicalPathologyMatrixRows($patientId, $effectiveClinicalDates); + + $selectedLabRows = []; + foreach ($clinicalLabRows as $row) { + if (! empty($savedClinicalDates)) { + if (! empty($row['checked'])) { + $selectedLabRows[] = $row; + } + continue; + } + + // No saved selection yet: include all available pathology dates as fallback. + $selectedLabRows[] = $row; + } + + $otherExamRow = []; + if ($this->tableHasColumns('ipd_discharge_2', ['ipd_d_id'])) { + $otherExamRow = $this->db->table('ipd_discharge_2') + ->where('ipd_d_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray() ?? []; + } + $otherExamParsed = $this->parseClinicalOtherExamPayload((string) ($otherExamRow['rdata'] ?? '')); + $nonPathRows = $this->getClinicalNonPathReportRows($patientId, $admitDate, $dischargeDate, $otherExamParsed['non_path_ids'] ?? []); + $selectedNonPathRows = []; + foreach ($nonPathRows as $row) { + if (! empty($row['checked'])) { + $selectedNonPathRows[] = $row; + } + } + + if (! empty($pathologyMatrix['rows']) || ! empty($selectedLabRows) || ! empty($selectedNonPathRows) || trim((string) ($otherExamParsed['text'] ?? '')) !== '') { + $html = '

Clinical Investigation Reports

'; + + if (! empty($pathologyMatrix['rows'])) { + $html .= '
In-Hospital Lab:
' + . '' + . '' + . '' + . ''; + + foreach (($pathologyMatrix['dates'] ?? []) as $dt) { + $html .= ''; + } + + $html .= ''; + + foreach (($pathologyMatrix['rows'] ?? []) as $row) { + $html .= '' + . '' + . ''; + + foreach (($pathologyMatrix['dates'] ?? []) as $dt) { + $html .= ''; + } + + $html .= ''; + } + + $html .= '
TestFixed Normals' . esc((string) ($pathologyMatrix['date_labels'][$dt] ?? $dt)) . '
' . esc((string) ($row['test_name'] ?? '')) . '' . esc((string) ($row['fixed_normals'] ?? '')) . '' . esc((string) ($row['values'][$dt] ?? '')) . '
'; + } elseif (! empty($selectedLabRows)) { + // Fallback when value-matrix cannot be built from current schema/data. + $html .= '
In-Hospital Lab:
'; + } + + if (! empty($selectedNonPathRows)) { + $html .= '
X-Ray / ECG / Sonography / CT / MRI:
'; + } + + $otherExamText = trim((string) ($otherExamParsed['text'] ?? '')); + if ($otherExamText !== '') { + $html .= '
Other Examinations / Provisional Diagnosis:
' . nl2br(esc($otherExamText)) . '
'; + } + + $sections[] = $html; + } + + $diagnosis = $this->byIpdRows('ipd_discharge_diagnosis', ['comp_report', 'comp_remark'], 'id ASC', $ipdId); + $this->addListSection($sections, 'Final Diagnosis', $diagnosis); + $diagnosisRemark = $this->firstRowByIpd('ipd_discharge_diagnosis_remark', $ipdId); + $diagnosisRemarkText = trim((string) ($diagnosisRemark['comp_remark'] ?? '')); + if ($diagnosisRemarkText !== '') { + $sections[] = '
' . nl2br(esc($diagnosisRemarkText)) . '
'; + } + + $inhosRow = $this->firstRowByIpd('ipd_discharge_investigtions_inhos', $ipdId); + $inhosRemark = trim((string) ($inhosRow['comp_remark'] ?? '')); + if ($inhosRemark !== '') { + $sections[] = '

Summary of key investigations during Hospitalization

' + . nl2br(esc($inhosRemark)) + . '
'; + } + + $course = $this->byIpdRows('ipd_discharge_course', ['comp_report', 'comp_remark'], 'id ASC', $ipdId); + $this->addListSection($sections, 'Course in the Hospital', $course); + $courseRemark = $this->firstRowByIpd('ipd_discharge_course_remark', $ipdId); + $courseRemarkText = trim((string) ($courseRemark['comp_remark'] ?? '')); + if ($courseRemarkText !== '') { + $sections[] = '
' . nl2br(esc($courseRemarkText)) . '
'; + } + + $dischargeExamRows = $this->getMappedColRows('ipd_discharge_general_exam_col', 'ipd_discharge_1_b_final', $ipdId, 'Discharge Condition', null); + if (! empty($dischargeExamRows)) { + $html = '

Examination on Discharge

' + . ''; + + $col = 0; + $perRow = 4; + foreach ($dischargeExamRows as $row) { + if ($col % $perRow === 0) { + $html .= ''; + } + + $label = (string) ($row['label'] ?? ''); + $value = trim((string) ($row['value'] ?? '')); + $html .= ''; + + if ($col % $perRow === $perRow - 1) { + $html .= ''; + } + $col++; + } + + while ($col % $perRow !== 0) { + $html .= ''; + if ($col % $perRow === $perRow - 1) { + $html .= ''; + } + $col++; + } + + $html .= '
' . esc($label) . ': ' . esc($value) . '
 
'; + $sections[] = $html; + } + + $surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['surgery_name', 'surgery_date'], 'id ASC', $ipdId); + if (! empty($surgeryRows)) { + $html = '

Surgery

'; + $sections[] = $html; + } + + $procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['procedure_name', 'procedure_date'], 'id ASC', $ipdId); + if (! empty($procedureRows)) { + $html = '

Procedure

'; + $sections[] = $html; + } + + $drugRows = $this->byIpdRows('ipd_discharge_drug', ['drug_name', 'drug_dose', 'drug_day'], 'id ASC', $ipdId); + if (! empty($drugRows)) { + $html = '

Discharge Medications

    '; + foreach ($drugRows as $row) { + $drugName = trim((string) ($row['drug_name'] ?? '')); + if ($drugName === '') { + continue; + } + + $line = esc($drugName); + $dose = trim((string) ($row['drug_dose'] ?? '')); + $days = trim((string) ($row['drug_day'] ?? '')); + + if ($dose !== '') { + $line .= ' ' . esc($dose) . ''; + } + if ($days !== '') { + $line .= ' [' . esc($days) . ' days]'; + } + + $html .= '
  1. ' . $line . '
  2. '; + } + $html .= '
'; + $sections[] = $html; + } else { + $medRows = $this->byIpdRows('ipd_discharge_prescrption_prescribed', ['med_name', 'med_type', 'qty', 'no_of_days', 'remark'], 'id ASC', $ipdId); + if (! empty($medRows)) { + $html = '

Discharge Medications

' + . '' + . ''; + $sr = 1; + foreach ($medRows as $row) { + $medName = trim((string) ($row['med_name'] ?? '')); + if ($medName === '') { + continue; + } + $medType = trim((string) ($row['med_type'] ?? '')); + $label = trim($medType . ' ' . $medName); + $html .= '' + . '' + . '' + . '' + . '' + . '' + . ''; + $sr++; + } + $html .= '
#MedicineQtyDaysNotes
' . $sr . '' . esc($label) . '' . esc((string) ($row['qty'] ?? '')) . '' . esc((string) ($row['no_of_days'] ?? '')) . '' . esc((string) ($row['remark'] ?? '')) . '
'; + $sections[] = $html; + } + } + + $instructions = $this->byIpdRows('ipd_discharge_instructions', ['comp_remark', 'review_after', 'footer_text'], 'id DESC', $ipdId); + if (! empty($instructions)) { + $first = $instructions[0]; + $html = '

Discharge Advice/Instructions/Summary

'; + + $remark = trim((string) ($first['comp_remark'] ?? '')); + if ($remark !== '') { + $html .= '
' . nl2br(esc($remark)) . '
'; + } + + $reviewAfter = trim((string) ($first['review_after'] ?? '')); + if ($reviewAfter !== '') { + $html .= '
Review after ' . esc($reviewAfter) . ' days / as and when required.
'; + } + + $footerText = trim((string) ($first['footer_text'] ?? '')); + if ($footerText !== '') { + $html .= '
' . nl2br(esc($footerText)) . '
'; + } + + $sections[] = $html; + } + + $sections[] = '' + . '' + . '' + . '' + . '' + . '' + . '
____________________
Signature of Consultant
____________________
Signature of Medical Officer
____________________
Signature of Receiver / Date
'; + + return trim(implode("\n", $sections)); + } + + private function firstRowByIpd(string $table, int $ipdId): array + { + if ($ipdId <= 0 || ! $this->tableHasColumns($table, ['ipd_id'])) { + return []; + } + + $row = $this->db->table($table) + ->where('ipd_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + return is_array($row) ? $row : []; + } + + private function upsertByIpd(string $table, int $ipdId, array $data): bool + { + if ($ipdId <= 0 || ! $this->tableHasColumns($table, ['ipd_id'])) { + return false; + } + + try { + $builder = $this->db->table($table); + $existing = $this->firstRowByIpd($table, $ipdId); + + if (! empty($existing['id'])) { + return (bool) $builder->where('id', (int) $existing['id'])->update($data); + } + + $insert = array_merge(['ipd_id' => $ipdId], $data); + + return (bool) $builder->insert($insert); + } catch (\Throwable $e) { + log_message('error', 'Discharge upsert failed in {table} for IPD {ipd}: {msg}', [ + 'table' => $table, + 'ipd' => $ipdId, + 'msg' => $e->getMessage(), + ]); + + return false; + } + } + + private function upsertByIpdField(string $table, string $ipdField, int $ipdId, array $data): bool + { + if ($ipdId <= 0 || ! $this->tableHasColumns($table, [$ipdField])) { + return false; + } + + try { + $builder = $this->db->table($table); + $existing = $builder + ->where($ipdField, $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + if (! empty($existing['id'])) { + return (bool) $builder->where('id', (int) $existing['id'])->update($data); + } + + $insert = array_merge([$ipdField => $ipdId], $data); + + return (bool) $builder->insert($insert); + } catch (\Throwable $e) { + log_message('error', 'Discharge upsertByIpdField failed in {table}: {msg}', [ + 'table' => $table, + 'msg' => $e->getMessage(), + ]); + + return false; + } + } + + private function toDbDate(string $input): ?string + { + $value = trim($input); + if ($value === '') { + return null; + } + + $ts = strtotime($value); + if ($ts === false) { + return null; + } + + return date('Y-m-d', $ts); + } + + private function upsertByComposite( + string $table, + string $ipdField, + int $ipdId, + string $keyField, + int $keyValue, + array $data + ): bool { + if ($ipdId <= 0 || $keyValue <= 0 || ! $this->tableHasColumns($table, [$ipdField, $keyField])) { + return false; + } + + try { + $builder = $this->db->table($table); + $existing = $builder + ->where($ipdField, $ipdId) + ->where($keyField, $keyValue) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + if (! empty($existing['id'])) { + return (bool) $builder->where('id', (int) $existing['id'])->update($data); + } + + $insert = array_merge($data, [ + $ipdField => $ipdId, + $keyField => $keyValue, + ]); + + return (bool) $builder->insert($insert); + } catch (\Throwable $e) { + log_message('error', 'Discharge composite upsert failed in {table}: {msg}', [ + 'table' => $table, + 'msg' => $e->getMessage(), + ]); + + return false; + } + } + + private function getPhysicalExamRows(int $ipdId): array + { + $group1 = []; + $group2 = []; + $sysRows = []; + + if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_description'])) { + $generalCols = $this->db->table('ipd_discharge_general_exam_col') + ->select('id,col_description,col_name,col_pre_value,cat_group') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + $rdataMap = []; + if ($this->tableHasColumns('ipd_discharge_1_b', ['ipd_d_id', 'col_id', 'rdata'])) { + $rRows = $this->db->table('ipd_discharge_1_b') + ->select('col_id,rdata') + ->where('ipd_d_id', $ipdId) + ->get() + ->getResultArray(); + foreach ($rRows as $r) { + $rdataMap[(int) ($r['col_id'] ?? 0)] = (string) ($r['rdata'] ?? ''); + } + } + + foreach ($generalCols as $row) { + $colId = (int) ($row['id'] ?? 0); + if ($colId <= 0) { + continue; + } + $item = [ + 'id' => $colId, + 'label' => (string) ($row['col_description'] ?? $row['col_name'] ?? ('Exam ' . $colId)), + 'value' => (string) ($rdataMap[$colId] ?? (string) ($row['col_pre_value'] ?? '')), + ]; + + if ((int) ($row['cat_group'] ?? 1) === 2) { + $group2[] = $item; + } else { + $group1[] = $item; + } + } + } + + if ($this->tableHasColumns('ipd_discharge_sys_exam', ['id', 'sys_exam_name'])) { + $sysMaster = $this->db->table('ipd_discharge_sys_exam') + ->select('id,sys_exam_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + $sysValueMap = []; + if ($this->tableHasColumns('ipd_discharge_1_a', ['ipd_d_id', 'head_id', 'rdata'])) { + $sysSaved = $this->db->table('ipd_discharge_1_a') + ->select('head_id,rdata') + ->where('ipd_d_id', $ipdId) + ->get() + ->getResultArray(); + foreach ($sysSaved as $row) { + $sysValueMap[(int) ($row['head_id'] ?? 0)] = (string) ($row['rdata'] ?? ''); + } + } + + foreach ($sysMaster as $row) { + $sid = (int) ($row['id'] ?? 0); + if ($sid <= 0) { + continue; + } + $sysRows[] = [ + 'id' => $sid, + 'name' => (string) ($row['sys_exam_name'] ?? ('Systemic Exam ' . $sid)), + 'value' => (string) ($sysValueMap[$sid] ?? ''), + ]; + } + } + + return [ + 'general_group_1' => $group1, + 'general_group_2' => $group2, + 'systemic' => $sysRows, + ]; + } + + private function getMappedColRows( + string $masterTable, + string $valueTable, + int $ipdId, + string $prefix, + ?int $catGroup = null + ): array { + if (! $this->tableHasColumns($masterTable, ['id', 'col_description'])) { + return []; + } + + $builder = $this->db->table($masterTable) + ->select('id,col_name,col_description,col_pre_value'); + + if ($catGroup !== null && $this->db->fieldExists('cat_group', $masterTable)) { + $builder->where('cat_group', $catGroup); + } + + $masterRows = $builder->orderBy('id', 'ASC')->get()->getResultArray(); + + $valueMap = []; + if ($this->tableHasColumns($valueTable, ['ipd_d_id', 'col_id', 'rdata'])) { + $savedRows = $this->db->table($valueTable) + ->select('col_id,rdata') + ->where('ipd_d_id', $ipdId) + ->get() + ->getResultArray(); + + foreach ($savedRows as $row) { + $valueMap[(int) ($row['col_id'] ?? 0)] = (string) ($row['rdata'] ?? ''); + } + } + + $rows = []; + foreach ($masterRows as $row) { + $colId = (int) ($row['id'] ?? 0); + if ($colId <= 0) { + continue; + } + + $rows[] = [ + 'id' => $colId, + 'name' => (string) ($row['col_name'] ?? ''), + 'label' => (string) ($row['col_description'] ?? $row['col_name'] ?? ($prefix . ' ' . $colId)), + 'value' => (string) ($valueMap[$colId] ?? (string) ($row['col_pre_value'] ?? '')), + ]; + } + + return $rows; + } + + private function normalizeDateValue(string $raw): ?string + { + $raw = trim($raw); + if ($raw === '') { + return null; + } + + $raw = trim($raw, "'\""); + if ($raw === '') { + return null; + } + + $ts = strtotime($raw); + if ($ts === false) { + return null; + } + + return date('Y-m-d', $ts); + } + + private function normalizeCsvDateList(string $raw): array + { + $out = []; + foreach (explode(',', $raw) as $part) { + $dt = $this->normalizeDateValue($part); + if ($dt !== null) { + $out[$dt] = $dt; + } + } + + return array_values($out); + } + + private function normalizeCsvIdList(string $raw): array + { + $out = []; + foreach (explode(',', $raw) as $part) { + $id = (int) trim((string) $part); + if ($id > 0) { + $out[$id] = $id; + } + } + + return array_values($out); + } + + private function parseClinicalOtherExamPayload(string $raw): array + { + $raw = (string) $raw; + $metaIds = []; + + if (preg_match('/\[\[CLINICAL_META\]\](.*?)\[\[\/CLINICAL_META\]\]\s*$/s', $raw, $m)) { + $json = trim((string) ($m[1] ?? '')); + $decoded = json_decode($json, true); + if (is_array($decoded) && isset($decoded['non_path_ids']) && is_array($decoded['non_path_ids'])) { + foreach ($decoded['non_path_ids'] as $id) { + $id = (int) $id; + if ($id > 0) { + $metaIds[$id] = $id; + } + } + } + + $raw = preg_replace('/\s*\[\[CLINICAL_META\]\].*?\[\[\/CLINICAL_META\]\]\s*$/s', '', $raw) ?? $raw; + } + + return [ + 'text' => trim($raw), + 'non_path_ids' => array_values($metaIds), + ]; + } + + private function buildClinicalOtherExamPayload(string $text, array $nonPathIds): string + { + $text = trim($text); + $normIds = []; + foreach ($nonPathIds as $id) { + $id = (int) $id; + if ($id > 0) { + $normIds[$id] = $id; + } + } + + if (empty($normIds)) { + return $text; + } + + $metaJson = json_encode([ + 'non_path_ids' => array_values($normIds), + ], JSON_UNESCAPED_SLASHES); + + if (! is_string($metaJson) || $metaJson === '') { + return $text; + } + + return trim($text . "\n\n[[CLINICAL_META]]" . $metaJson . '[[/CLINICAL_META]]'); + } + + private function plainTextFromHtml(string $raw): string + { + $raw = trim($raw); + if ($raw === '') { + return ''; + } + + // Preserve basic line breaks from common block/line tags before stripping HTML. + $raw = preg_replace('/<\s*br\s*\/?\s*>/i', "\n", $raw) ?? $raw; + $raw = preg_replace('/<\s*\/\s*(p|div|li|h[1-6]|tr)\s*>/i', "\n", $raw) ?? $raw; + + $text = strip_tags($raw); + $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $text = preg_replace('/[ \t]+/', ' ', $text) ?? $text; + $text = preg_replace('/\n{2,}/', "\n", $text) ?? $text; + + return trim($text); + } + + private function getSavedClinicalLabDates(int $ipdId): array + { + if (! $this->tableHasColumns('ipd_discharge_2', ['ipd_d_id', 'lab_investigation_list'])) { + return []; + } + + $row = $this->db->table('ipd_discharge_2') + ->select('lab_investigation_list') + ->where('ipd_d_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + + $raw = (string) ($row['lab_investigation_list'] ?? ''); + + return $this->normalizeCsvDateList($raw); + } + + private function getClinicalInvestigationLabRows(int $patientId, string $admitDate, string $dischargeDate, array $selectedDates): array + { + if ($patientId <= 0) { + return []; + } + + if (! $this->tableHasColumns('invoice_master', ['id', 'attach_id', 'inv_date']) + || ! $this->tableHasColumns('lab_request', ['id', 'charge_id']) + || ! $this->tableHasColumns('lab_request_item', ['lab_request_id', 'lab_test_id']) + || ! $this->tableHasColumns('lab_tests', ['mstTestKey', 'Test', 'TestID']) + || ! $this->tableHasColumns('ipd_discharge_investigation_template', ['test_code'])) { + return []; + } + + $builder = $this->db->table('invoice_master m') + ->select('DATE(m.inv_date) AS inv_date', false) + ->select("DATE_FORMAT(m.inv_date,'%d-%m-%Y') AS inv_date_label", false) + ->select('GROUP_CONCAT(DISTINCT t.Test ORDER BY t.Test SEPARATOR ",") AS test_list', false) + ->join('lab_request l', 'm.id = l.charge_id', 'inner') + ->join('lab_request_item i', 'l.id = i.lab_request_id', 'inner') + ->join('lab_tests t', 'i.lab_test_id = t.mstTestKey', 'inner') + ->join('ipd_discharge_investigation_template d', 't.TestID = d.test_code', 'inner') + ->where('m.attach_id', $patientId) + ->groupBy('DATE(m.inv_date)', false) + ->orderBy('DATE(m.inv_date)', 'ASC', false); + + if ($admitDate !== '' && $dischargeDate !== '') { + $builder->where('DATE(m.inv_date) >=', $admitDate) + ->where('DATE(m.inv_date) <=', $dischargeDate); + } + + $rows = $builder->get()->getResultArray(); + + $selectedMap = []; + foreach ($selectedDates as $dt) { + $selectedMap[$dt] = true; + } + + foreach ($rows as &$row) { + $dateValue = (string) ($row['inv_date'] ?? ''); + $row['inv_date'] = $dateValue; + $row['inv_date_label'] = (string) ($row['inv_date_label'] ?? $dateValue); + $row['test_list'] = (string) ($row['test_list'] ?? ''); + $row['checked'] = isset($selectedMap[$dateValue]); + } + unset($row); + + return $rows; + } + + private function getClinicalPathologyMatrixRows(int $patientId, array $selectedDates): array + { + if ($patientId <= 0 || empty($selectedDates)) { + return [ + 'dates' => [], + 'date_labels' => [], + 'rows' => [], + ]; + } + + if (! $this->tableHasColumns('invoice_master', ['id', 'attach_id', 'inv_date']) + || ! $this->tableHasColumns('lab_request', ['id', 'charge_id']) + || ! $this->tableHasColumns('lab_request_item', ['lab_request_id', 'lab_test_id', 'lab_test_value']) + || ! $this->tableHasColumns('lab_tests', ['mstTestKey', 'Test', 'TestID']) + || ! $this->tableHasColumns('ipd_discharge_investigation_template', ['test_code'])) { + return [ + 'dates' => [], + 'date_labels' => [], + 'rows' => [], + ]; + } + + $dateMap = []; + foreach ($selectedDates as $dt) { + $norm = $this->normalizeDateValue((string) $dt); + if ($norm !== null) { + $dateMap[$norm] = $norm; + } + } + + $dates = array_values($dateMap); + sort($dates); + if (empty($dates)) { + return [ + 'dates' => [], + 'date_labels' => [], + 'rows' => [], + ]; + } + + $builder = $this->db->table('invoice_master m') + ->select('DATE(m.inv_date) AS inv_date', false) + ->select('t.TestID AS test_id, t.Test AS test_name, t.mstTestKey AS test_sort', false) + ->select('i.lab_test_value', false) + ->join('lab_request l', 'm.id = l.charge_id', 'inner') + ->join('lab_request_item i', 'l.id = i.lab_request_id', 'inner') + ->join('lab_tests t', 'i.lab_test_id = t.mstTestKey', 'inner') + ->join('ipd_discharge_investigation_template d', 't.TestID = d.test_code', 'inner') + ->where('m.attach_id', $patientId) + ->orderBy('DATE(m.inv_date)', 'ASC', false) + ->orderBy('t.mstTestKey', 'ASC'); + + if ($this->db->fieldExists('FixedNormals', 'lab_tests')) { + $builder->select('t.FixedNormals AS fixed_normals', false); + } else { + $builder->select("'' AS fixed_normals", false); + } + + if ($this->tableHasColumns('ipd_discharge_investigation_template', ['group_test']) + && $this->tableHasColumns('lab_rgroups', ['mstRGrpKey', 'sort_order'])) { + $builder->join('lab_rgroups g', 'd.group_test = g.mstRGrpKey', 'left') + ->select('COALESCE(g.sort_order, 9999) AS grp_sort', false) + ->orderBy('grp_sort', 'ASC', false); + } else { + $builder->select('9999 AS grp_sort', false); + } + + $escapedDates = []; + foreach ($dates as $dt) { + $escapedDates[] = $this->db->escape($dt); + } + $builder->where('DATE(m.inv_date) IN (' . implode(',', $escapedDates) . ')', null, false); + + $rawRows = $builder->get()->getResultArray(); + + $rowsByTest = []; + $orderByTest = []; + foreach ($rawRows as $row) { + $testId = trim((string) ($row['test_id'] ?? '')); + if ($testId === '') { + continue; + } + + $date = (string) ($row['inv_date'] ?? ''); + if ($date === '') { + continue; + } + + if (! isset($rowsByTest[$testId])) { + $rowsByTest[$testId] = [ + 'test_name' => (string) ($row['test_name'] ?? ''), + 'fixed_normals' => (string) ($row['fixed_normals'] ?? ''), + 'values' => [], + ]; + + $orderByTest[$testId] = [ + 'grp_sort' => (int) ($row['grp_sort'] ?? 9999), + 'test_sort' => (int) ($row['test_sort'] ?? 0), + 'test_name' => (string) ($row['test_name'] ?? ''), + ]; + } + + $value = trim((string) ($row['lab_test_value'] ?? '')); + if (! isset($rowsByTest[$testId]['values'][$date])) { + $rowsByTest[$testId]['values'][$date] = []; + } + if ($value !== '' && ! in_array($value, $rowsByTest[$testId]['values'][$date], true)) { + $rowsByTest[$testId]['values'][$date][] = $value; + } + } + + $orderedTestIds = array_keys($rowsByTest); + usort($orderedTestIds, function ($a, $b) use ($orderByTest) { + $oa = $orderByTest[$a] ?? ['grp_sort' => 9999, 'test_sort' => 0, 'test_name' => '']; + $ob = $orderByTest[$b] ?? ['grp_sort' => 9999, 'test_sort' => 0, 'test_name' => '']; + + if ((int) $oa['grp_sort'] !== (int) $ob['grp_sort']) { + return (int) $oa['grp_sort'] <=> (int) $ob['grp_sort']; + } + if ((int) $oa['test_sort'] !== (int) $ob['test_sort']) { + return (int) $oa['test_sort'] <=> (int) $ob['test_sort']; + } + + return strcmp((string) $oa['test_name'], (string) $ob['test_name']); + }); + + $matrixRows = []; + foreach ($orderedTestIds as $testId) { + $testRow = $rowsByTest[$testId]; + $values = []; + foreach ($dates as $dt) { + $vals = $testRow['values'][$dt] ?? []; + $values[$dt] = empty($vals) ? '' : implode(', ', $vals); + } + + $matrixRows[] = [ + 'test_id' => $testId, + 'test_name' => (string) ($testRow['test_name'] ?? ''), + 'fixed_normals' => (string) ($testRow['fixed_normals'] ?? ''), + 'values' => $values, + ]; + } + + $dateLabels = []; + foreach ($dates as $dt) { + $ts = strtotime($dt); + $dateLabels[$dt] = $ts === false ? $dt : date('d-m-Y', $ts); + } + + return [ + 'dates' => $dates, + 'date_labels' => $dateLabels, + 'rows' => $matrixRows, + ]; + } + + private function resolveClinicalNonPathModality(int $labType, string $reportName, string $impression): ?string + { + $byType = [ + 1 => 'Sonography', + 2 => 'MRI', + 3 => 'X-Ray', + 4 => 'CT-Scan', + 6 => 'ECG', + ]; + + if (isset($byType[$labType])) { + return $byType[$labType]; + } + + $haystack = strtolower($reportName . ' ' . $impression); + if (strpos($haystack, 'x-ray') !== false || strpos($haystack, 'xray') !== false || strpos($haystack, 'radiology') !== false) { + return 'X-Ray'; + } + if (strpos($haystack, 'ecg') !== false || strpos($haystack, 'ekg') !== false || strpos($haystack, 'echo') !== false) { + return 'ECG'; + } + if (strpos($haystack, 'sonography') !== false || strpos($haystack, 'ultrasound') !== false || strpos($haystack, 'usg') !== false) { + return 'Sonography'; + } + if (strpos($haystack, 'ct') !== false || strpos($haystack, 'ct scan') !== false || strpos($haystack, 'ct-scan') !== false) { + return 'CT-Scan'; + } + if (strpos($haystack, 'mri') !== false) { + return 'MRI'; + } + + return null; + } + + private function getClinicalNonPathReportRows(int $patientId, string $admitDate, string $dischargeDate, array $selectedIds): array + { + if ($patientId <= 0) { + return []; + } + + if (! $this->tableHasColumns('invoice_master', ['id', 'attach_id', 'inv_date']) + || ! $this->tableHasColumns('lab_request', ['id', 'charge_id', 'lab_type', 'Request_Date', 'report_name', 'report_data_Impression'])) { + return []; + } + + $rows = $this->db->table('invoice_master m') + ->select('l.id AS lab_request_id, l.lab_type, l.report_name, l.report_data_Impression, l.Request_Date, m.inv_date', false) + ->join('lab_request l', 'm.id = l.charge_id', 'inner') + ->where('m.attach_id', $patientId) + ->where('l.report_data_Impression IS NOT NULL', null, false) + ->where('TRIM(l.report_data_Impression) <>', '') + ->orderBy('m.inv_date', 'ASC') + ->orderBy('l.id', 'ASC') + ->get() + ->getResultArray(); + + $selectedMap = []; + foreach ($selectedIds as $id) { + $id = (int) $id; + if ($id > 0) { + $selectedMap[$id] = true; + } + } + + $result = []; + $seen = []; + + foreach ($rows as $row) { + $requestId = (int) ($row['lab_request_id'] ?? 0); + if ($requestId <= 0 || isset($seen[$requestId])) { + continue; + } + + $impression = $this->plainTextFromHtml((string) ($row['report_data_Impression'] ?? '')); + if ($impression === '') { + continue; + } + + $requestDate = $this->normalizeDateValue((string) ($row['Request_Date'] ?? '')) + ?? $this->normalizeDateValue((string) ($row['inv_date'] ?? '')); + + if ($requestDate === null) { + continue; + } + + if ($admitDate !== '' && strcmp($requestDate, $admitDate) < 0) { + continue; + } + if ($dischargeDate !== '' && strcmp($requestDate, $dischargeDate) > 0) { + continue; + } + + $reportName = $this->plainTextFromHtml((string) ($row['report_name'] ?? '')); + $modality = $this->resolveClinicalNonPathModality((int) ($row['lab_type'] ?? 0), $reportName, $impression); + if ($modality === null) { + continue; + } + + if ($reportName === '') { + $reportName = $modality . ' Report'; + } + + $result[] = [ + 'lab_request_id' => $requestId, + 'report_date' => $requestDate, + 'report_date_label' => date('d-m-Y', strtotime($requestDate)), + 'modality' => $modality, + 'report_name' => $reportName, + 'impression' => $impression, + 'checked' => isset($selectedMap[$requestId]), + ]; + $seen[$requestId] = true; + } + + return $result; + } + + private function parseInputDateToDb(string $rawDate): ?string + { + $rawDate = trim($rawDate); + if ($rawDate === '') { + return null; + } + + $formats = ['Y-m-d', 'd/m/Y', 'd-m-Y']; + foreach ($formats as $format) { + $dt = \DateTime::createFromFormat($format, $rawDate); + if ($dt instanceof \DateTime) { + return $dt->format('Y-m-d'); + } + } + + $ts = strtotime($rawDate); + + return $ts === false ? null : date('Y-m-d', $ts); + } + + public function ipd_select(int $ipdId, int $reCreate = 0) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'billing.ipd.current-admission', + ]); + if ($permission) { + return $permission; + } + + if ($ipdId <= 0) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Invalid IPD id'); + } + + $panelData = $this->ipdBillingModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $ipdMasterRow = $this->db->table('ipd_master')->where('id', $ipdId)->get(1)->getRowArray() ?? []; + $patientId = (int) ($ipdMasterRow['p_id'] ?? ($panelData['person_info']->id ?? 0)); + + $notice = ''; + $noticeType = 'success'; + $userLabel = substr($this->currentUserLabel(), 0, 50); + + if (strtolower($this->request->getMethod()) === 'post') { + $action = (string) ($this->request->getPost('action') ?? 'save_main'); + $savedAny = false; + + if ($action === 'add_complaint') { + $complaintName = trim((string) ($this->request->getPost('new_complaint_name') ?? '')); + $complaintRemarkRow = trim((string) ($this->request->getPost('new_complaint_remark') ?? '')); + + if ($complaintName !== '' && $this->tableHasColumns('ipd_discharge_complaint', ['ipd_id'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'comp_code' => 0, + 'comp_report' => $complaintName, + 'comp_remark' => $complaintRemarkRow, + 'update_by' => $userLabel, + ]; + + if ($this->db->fieldExists('order_id', 'ipd_discharge_complaint')) { + $insert['order_id'] = 0; + } + + $savedAny = (bool) $this->db->table('ipd_discharge_complaint')->insert($insert); + $notice = $savedAny ? 'Complaint row added.' : 'Unable to add complaint row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Enter complaint name before adding.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_complaint') { + $removeId = (int) ($this->request->getPost('complaint_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_complaint', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_complaint') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Complaint row removed.' : 'Unable to remove complaint row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } + } elseif ($action === 'add_surgery') { + $name = trim((string) ($this->request->getPost('new_surgery_name') ?? '')); + $date = $this->parseInputDateToDb((string) ($this->request->getPost('new_surgery_date') ?? '')); + $remark = trim((string) ($this->request->getPost('new_surgery_remark') ?? '')); + if ($name !== '' && $this->tableHasColumns('ipd_discharge_surgery', ['ipd_id', 'surgery_name'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'surgery_name' => $name, + 'surgery_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('surgery_date', 'ipd_discharge_surgery')) { + $insert['surgery_date'] = $date; + } + if ($this->db->fieldExists('surgery_id', 'ipd_discharge_surgery')) { + $insert['surgery_id'] = 0; + } + if ($this->db->fieldExists('surgery_by_doc_id', 'ipd_discharge_surgery')) { + $insert['surgery_by_doc_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_surgery')->insert($insert); + $notice = $savedAny ? 'Surgery row added.' : 'Unable to add surgery row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = $name === '' + ? 'Enter surgery name before adding.' + : 'Surgery table/columns are missing in database.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_surgery') { + $removeId = (int) ($this->request->getPost('surgery_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_surgery', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_surgery') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Surgery row removed.' : 'Unable to remove surgery row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Select a valid surgery row to remove.'; + $noticeType = 'warning'; + } + } elseif ($action === 'add_procedure') { + $name = trim((string) ($this->request->getPost('new_procedure_name') ?? '')); + $date = $this->parseInputDateToDb((string) ($this->request->getPost('new_procedure_date') ?? '')); + $remark = trim((string) ($this->request->getPost('new_procedure_remark') ?? '')); + if ($name !== '' && $this->tableHasColumns('ipd_discharge_procedure', ['ipd_id', 'procedure_name'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'procedure_name' => $name, + 'procedure_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('procedure_date', 'ipd_discharge_procedure')) { + $insert['procedure_date'] = $date; + } + if ($this->db->fieldExists('procedure_id', 'ipd_discharge_procedure')) { + $insert['procedure_id'] = 0; + } + if ($this->db->fieldExists('procedure_by_doc_id', 'ipd_discharge_procedure')) { + $insert['procedure_by_doc_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_procedure')->insert($insert); + $notice = $savedAny ? 'Procedure row added.' : 'Unable to add procedure row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = $name === '' + ? 'Enter procedure name before adding.' + : 'Procedure table/columns are missing in database.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_procedure') { + $removeId = (int) ($this->request->getPost('procedure_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_procedure', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_procedure') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Procedure row removed.' : 'Unable to remove procedure row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Select a valid procedure row to remove.'; + $noticeType = 'warning'; + } + } elseif ($action === 'add_diagnosis') { + $name = trim((string) ($this->request->getPost('new_diagnosis_name') ?? '')); + $remark = trim((string) ($this->request->getPost('new_diagnosis_remark') ?? '')); + if ($name !== '' && $this->tableHasColumns('ipd_discharge_diagnosis', ['ipd_id', 'comp_report'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'comp_code' => 0, + 'comp_report' => $name, + 'comp_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('order_id', 'ipd_discharge_diagnosis')) { + $insert['order_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_diagnosis')->insert($insert); + $notice = $savedAny ? 'Diagnosis row added.' : 'Unable to add diagnosis row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = $name === '' + ? 'Enter diagnosis before adding.' + : 'Diagnosis table/columns are missing in database.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_diagnosis') { + $removeId = (int) ($this->request->getPost('diagnosis_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_diagnosis', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_diagnosis') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Diagnosis row removed.' : 'Unable to remove diagnosis row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Select a valid diagnosis row to remove.'; + $noticeType = 'warning'; + } + } elseif ($action === 'add_course') { + $name = trim((string) ($this->request->getPost('new_course_name') ?? '')); + $remark = trim((string) ($this->request->getPost('new_course_remark') ?? '')); + if ($name !== '' && $this->tableHasColumns('ipd_discharge_course', ['ipd_id', 'comp_report'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'comp_code' => 0, + 'comp_report' => $name, + 'comp_remark' => $remark, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('order_id', 'ipd_discharge_course')) { + $insert['order_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_course')->insert($insert); + $notice = $savedAny ? 'Course row added.' : 'Unable to add course row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = $name === '' + ? 'Enter course/treatment text before adding.' + : 'Course table/columns are missing in database.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_course') { + $removeId = (int) ($this->request->getPost('course_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_course', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_course') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Course row removed.' : 'Unable to remove course row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Select a valid course row to remove.'; + $noticeType = 'warning'; + } + } elseif ($action === 'add_drug') { + $name = trim((string) ($this->request->getPost('new_drug_name') ?? '')); + $dose = trim((string) ($this->request->getPost('new_drug_dose') ?? '')); + $day = trim((string) ($this->request->getPost('new_drug_day') ?? '')); + if ($name !== '' && $this->tableHasColumns('ipd_discharge_drug', ['ipd_id', 'drug_name'])) { + $insert = [ + 'ipd_id' => $ipdId, + 'drug_code' => 0, + 'drug_name' => $name, + 'drug_dose' => $dose, + 'drug_day' => $day, + 'update_by' => $userLabel, + ]; + if ($this->db->fieldExists('order_id', 'ipd_discharge_drug')) { + $insert['order_id'] = 0; + } + $savedAny = (bool) $this->db->table('ipd_discharge_drug')->insert($insert); + $notice = $savedAny ? 'Drug row added.' : 'Unable to add drug row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = $name === '' + ? 'Enter drug name before adding.' + : 'Drug table/columns are missing in database.'; + $noticeType = 'warning'; + } + } elseif ($action === 'remove_drug') { + $removeId = (int) ($this->request->getPost('drug_remove_id') ?? 0); + if ($removeId > 0 && $this->tableHasColumns('ipd_discharge_drug', ['id', 'ipd_id'])) { + $savedAny = (bool) $this->db->table('ipd_discharge_drug') + ->where('id', $removeId) + ->where('ipd_id', $ipdId) + ->delete(); + $notice = $savedAny ? 'Drug row removed.' : 'Unable to remove drug row.'; + $noticeType = $savedAny ? 'success' : 'warning'; + } else { + $notice = 'Select a valid drug row to remove.'; + $noticeType = 'warning'; + } + } else { + + if ($this->db->tableExists('ipd_master')) { + $masterUpdate = []; + + if ($this->db->fieldExists('discarge_patient_status', 'ipd_master')) { + $masterUpdate['discarge_patient_status'] = (int) ($this->request->getPost('discarge_patient_status') ?? 0); + } + if ($this->db->fieldExists('discharge_date', 'ipd_master')) { + $masterUpdate['discharge_date'] = $this->toDbDate((string) ($this->request->getPost('discharge_date') ?? '')); + } + if ($this->db->fieldExists('discharge_time', 'ipd_master')) { + $masterUpdate['discharge_time'] = (string) ($this->request->getPost('discharge_time') ?? ''); + } + if ($this->db->fieldExists('dept_id', 'ipd_master')) { + $masterUpdate['dept_id'] = (int) ($this->request->getPost('dept_id') ?? 0); + } + + if (! empty($masterUpdate)) { + $savedAny = (bool) $this->db->table('ipd_master')->where('id', $ipdId)->update($masterUpdate) || $savedAny; + } + } + + $complaintRemark = trim((string) ($this->request->getPost('complaint_remark') ?? '')); + $diagnosisRemark = trim((string) ($this->request->getPost('diagnosis_remark') ?? '')); + $courseRemark = trim((string) ($this->request->getPost('course_remark') ?? '')); + $instructionRemark = trim((string) ($this->request->getPost('instruction_remark') ?? '')); + $reviewAfter = trim((string) ($this->request->getPost('review_after') ?? '')); + + if ($complaintRemark !== '' || $this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id'])) { + $savedAny = $this->upsertByIpd('ipd_discharge_complaint_remark', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $complaintRemark, + 'update_by' => $userLabel, + ]) || $savedAny; + } + + if ($diagnosisRemark !== '' || $this->tableHasColumns('ipd_discharge_diagnosis_remark', ['ipd_id'])) { + $savedAny = $this->upsertByIpd('ipd_discharge_diagnosis_remark', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $diagnosisRemark, + 'update_by' => $userLabel, + ]) || $savedAny; + } + + if ($courseRemark !== '' || $this->tableHasColumns('ipd_discharge_course_remark', ['ipd_id'])) { + $savedAny = $this->upsertByIpd('ipd_discharge_course_remark', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $courseRemark, + 'update_by' => $userLabel, + ]) || $savedAny; + } + + if ($instructionRemark !== '' || $reviewAfter !== '' || $this->tableHasColumns('ipd_discharge_instructions', ['ipd_id'])) { + $savedAny = $this->upsertByIpd('ipd_discharge_instructions', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $instructionRemark, + 'review_after' => $reviewAfter, + 'footer_text' => '', + 'footer_banner' => '0', + 'update_by' => $userLabel, + ]) || $savedAny; + } + + // Examination on Admission (General Examination values). + if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_name']) + && $this->tableHasColumns('ipd_discharge_1_b', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) { + $generalCols = $this->db->table('ipd_discharge_general_exam_col') + ->select('id,col_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + foreach ($generalCols as $col) { + $colId = (int) ($col['id'] ?? 0); + if ($colId <= 0) { + continue; + } + $posted = $this->request->getPost('gen_exam_' . $colId); + if ($posted === null) { + continue; + } + $savedAny = $this->upsertByComposite( + 'ipd_discharge_1_b', + 'ipd_d_id', + $ipdId, + 'col_id', + $colId, + [ + 'short_head' => (string) ($col['col_name'] ?? ('Exam ' . $colId)), + 'rdata' => trim((string) $posted), + ] + ) || $savedAny; + } + } + + // Other/Systemic Examinations. + if ($this->tableHasColumns('ipd_discharge_sys_exam', ['id', 'sys_exam_name']) + && $this->tableHasColumns('ipd_discharge_1_a', ['ipd_d_id', 'head_id', 'short_head', 'rdata'])) { + $sysMaster = $this->db->table('ipd_discharge_sys_exam') + ->select('id,sys_exam_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + foreach ($sysMaster as $sys) { + $sid = (int) ($sys['id'] ?? 0); + if ($sid <= 0) { + continue; + } + $posted = $this->request->getPost('sys_exam_' . $sid); + if ($posted === null) { + continue; + } + $savedAny = $this->upsertByComposite( + 'ipd_discharge_1_a', + 'ipd_d_id', + $ipdId, + 'head_id', + $sid, + [ + 'short_head' => (string) ($sys['sys_exam_name'] ?? ('Systemic Exam ' . $sid)), + 'rdata' => trim((string) $posted), + ] + ) || $savedAny; + } + } + + // Investigation done during admit (manual entry): ipd_discharge_1_d + if ($this->tableHasColumns('ipd_discharge_investigation_during_admit', ['id', 'col_name']) + && $this->tableHasColumns('ipd_discharge_1_d', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) { + $manualCols = $this->db->table('ipd_discharge_investigation_during_admit') + ->select('id,col_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + foreach ($manualCols as $col) { + $colId = (int) ($col['id'] ?? 0); + if ($colId <= 0) { + continue; + } + $posted = $this->request->getPost('manual_exam_' . $colId); + if ($posted === null) { + continue; + } + $savedAny = $this->upsertByComposite( + 'ipd_discharge_1_d', + 'ipd_d_id', + $ipdId, + 'col_id', + $colId, + [ + 'short_head' => (string) ($col['col_name'] ?? ('Investigation ' . $colId)), + 'rdata' => trim((string) $posted), + ] + ) || $savedAny; + } + } + + // Special/manual radiology investigation: ipd_discharge_1_e + if ($this->tableHasColumns('ipd_discharge_special_investigation', ['id', 'col_name']) + && $this->tableHasColumns('ipd_discharge_1_e', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) { + $specialCols = $this->db->table('ipd_discharge_special_investigation') + ->select('id,col_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + foreach ($specialCols as $col) { + $colId = (int) ($col['id'] ?? 0); + if ($colId <= 0) { + continue; + } + $posted = $this->request->getPost('special_exam_' . $colId); + if ($posted === null) { + continue; + } + $savedAny = $this->upsertByComposite( + 'ipd_discharge_1_e', + 'ipd_d_id', + $ipdId, + 'col_id', + $colId, + [ + 'short_head' => (string) ($col['col_name'] ?? ('Special Investigation ' . $colId)), + 'rdata' => trim((string) $posted), + ] + ) || $savedAny; + } + } + + // Condition at discharge values: ipd_discharge_1_b_final + if ($this->tableHasColumns('ipd_discharge_general_exam_col', ['id', 'col_name']) + && $this->tableHasColumns('ipd_discharge_1_b_final', ['ipd_d_id', 'col_id', 'short_head', 'rdata'])) { + $disCols = $this->db->table('ipd_discharge_general_exam_col') + ->select('id,col_name') + ->orderBy('id', 'ASC') + ->get() + ->getResultArray(); + + foreach ($disCols as $col) { + $colId = (int) ($col['id'] ?? 0); + if ($colId <= 0) { + continue; + } + $posted = $this->request->getPost('dis_exam_' . $colId); + if ($posted === null) { + continue; + } + $savedAny = $this->upsertByComposite( + 'ipd_discharge_1_b_final', + 'ipd_d_id', + $ipdId, + 'col_id', + $colId, + [ + 'short_head' => (string) ($col['col_name'] ?? ('Discharge Exam ' . $colId)), + 'rdata' => trim((string) $posted), + ] + ) || $savedAny; + } + } + + // Summary of key investigations during hospitalization. + $inhosRemark = trim((string) ($this->request->getPost('inhos_remark') ?? '')); + if ($inhosRemark !== '' || $this->tableHasColumns('ipd_discharge_investigtions_inhos', ['ipd_id'])) { + $savedAny = $this->upsertByIpd('ipd_discharge_investigtions_inhos', $ipdId, [ + 'comp_report' => '', + 'comp_remark' => $inhosRemark, + 'update_by' => $userLabel, + ]) || $savedAny; + } + + // Other examinations narrative (legacy ipd_discharge_2.rdata). + $otherExamText = trim((string) ($this->request->getPost('other_exam_text') ?? '')); + $postedClinicalDates = $this->request->getPost('lab_investigation_dates'); + $postedNonPathIds = $this->request->getPost('non_path_investigation_ids'); + $normalizedClinicalDates = []; + $normalizedNonPathIds = []; + if (is_array($postedClinicalDates)) { + foreach ($postedClinicalDates as $dt) { + $parsed = $this->normalizeDateValue((string) $dt); + if ($parsed !== null) { + $normalizedClinicalDates[$parsed] = $parsed; + } + } + } else { + $listFromText = (string) ($this->request->getPost('lab_investigation_list') ?? ''); + foreach ($this->normalizeCsvDateList($listFromText) as $dt) { + $normalizedClinicalDates[$dt] = $dt; + } + } + + if (is_array($postedNonPathIds)) { + foreach ($postedNonPathIds as $id) { + $id = (int) $id; + if ($id > 0) { + $normalizedNonPathIds[$id] = $id; + } + } + } else { + $nonPathFromText = (string) ($this->request->getPost('non_path_investigation_list') ?? ''); + foreach ($this->normalizeCsvIdList($nonPathFromText) as $id) { + $normalizedNonPathIds[$id] = $id; + } + } + + $labInvestigationList = implode(',', array_values($normalizedClinicalDates)); + $nonPathIds = array_values($normalizedNonPathIds); + $otherExamPayload = $this->buildClinicalOtherExamPayload($otherExamText, $nonPathIds); + if ($otherExamText !== '' || $this->tableHasColumns('ipd_discharge_2', ['ipd_d_id'])) { + $savedAny = $this->upsertByIpdField( + 'ipd_discharge_2', + 'ipd_d_id', + $ipdId, + [ + 'lab_investigation_list' => $labInvestigationList, + 'short_head' => 'Other Examination', + 'rdata' => $otherExamPayload, + ] + ) || $savedAny; + } + + // Legacy first-tab personal history checkboxes are stored in patient_master. + if ($patientId > 0 && $this->db->tableExists('patient_master')) { + $patientUpdate = []; + $historyFields = [ + 'is_smoking', + 'is_alcohol', + 'is_drug_abuse', + 'is_tobacoo', + 'is_hypertesion', + 'is_niddm', + 'is_hbsag', + 'is_hcv', + 'is_hiv_I_II', + 'Others', + ]; + + foreach ($historyFields as $field) { + if ($this->db->fieldExists($field, 'patient_master')) { + $patientUpdate[$field] = $this->request->getPost($field) ? 1 : 0; + } + } + + if (! empty($patientUpdate)) { + $savedAny = (bool) $this->db->table('patient_master')->where('id', $patientId)->update($patientUpdate) || $savedAny; + } + } + + if ($savedAny) { + $notice = 'Discharge form data saved. You can now preview or regenerate summary.'; + } else { + $notice = 'No data could be saved. Please verify discharge tables exist in this database.'; + $noticeType = 'warning'; + } + } + + if ($action !== 'save_main' && $notice === '') { + $notice = 'Requested action could not be completed. Please verify database table/columns for this section.'; + $noticeType = 'warning'; + } + } + + if ($reCreate > 0) { + return redirect()->to(site_url('Ipd_discharge/preview_discharge_report/' . $ipdId . '?regen=1')); + } + + $ipdMasterRow = $this->db->table('ipd_master')->where('id', $ipdId)->get(1)->getRowArray() ?? []; + $statusRows = $this->db->tableExists('ipd_discharg_status') + ? $this->db->table('ipd_discharg_status')->orderBy('id', 'ASC')->get()->getResultArray() + : []; + $departmentRows = $this->db->tableExists('hc_department') + ? $this->db->table('hc_department')->orderBy('vName', 'ASC')->get()->getResultArray() + : []; + + $complaintRemarkRow = $this->firstRowByIpd('ipd_discharge_complaint_remark', $ipdId); + $complaintRows = $this->byIpdRows('ipd_discharge_complaint', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); + $surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['id', 'surgery_name', 'surgery_date', 'surgery_remark'], 'id ASC', $ipdId); + $procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['id', 'procedure_name', 'procedure_date', 'procedure_remark'], 'id ASC', $ipdId); + $diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); + $courseRows = $this->byIpdRows('ipd_discharge_course', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId); + $drugRows = $this->byIpdRows('ipd_discharge_drug', ['id', 'drug_name', 'drug_dose', 'drug_day'], 'id ASC', $ipdId); + $diagnosisRemarkRow = $this->firstRowByIpd('ipd_discharge_diagnosis_remark', $ipdId); + $courseRemarkRow = $this->firstRowByIpd('ipd_discharge_course_remark', $ipdId); + $instructionRow = $this->firstRowByIpd('ipd_discharge_instructions', $ipdId); + $inhosRow = $this->firstRowByIpd('ipd_discharge_investigtions_inhos', $ipdId); + $otherExamRow = []; + if ($this->tableHasColumns('ipd_discharge_2', ['ipd_d_id'])) { + $otherExamRow = $this->db->table('ipd_discharge_2') + ->where('ipd_d_id', $ipdId) + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray() ?? []; + } + $otherExamParsed = $this->parseClinicalOtherExamPayload((string) ($otherExamRow['rdata'] ?? '')); + $savedNonPathIds = $otherExamParsed['non_path_ids'] ?? []; + $patientHistoryRow = $patientId > 0 && $this->db->tableExists('patient_master') + ? ($this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray() ?? []) + : []; + $physicalExamRows = $this->getPhysicalExamRows($ipdId); + $manualInvestRows = $this->getMappedColRows('ipd_discharge_investigation_during_admit', 'ipd_discharge_1_d', $ipdId, 'Manual Exam', 1); + $specialInvestRows = $this->getMappedColRows('ipd_discharge_special_investigation', 'ipd_discharge_1_e', $ipdId, 'Special Exam', 1); + $dischargeConditionRows = $this->getMappedColRows('ipd_discharge_general_exam_col', 'ipd_discharge_1_b_final', $ipdId, 'Discharge Condition', null); + + $ipdInfo = $panelData['ipd_info'] ?? null; + $admitDate = $this->normalizeDateValue((string) ($ipdInfo->register_date ?? '')) ?? ''; + $dischargeDate = $this->normalizeDateValue((string) ($ipdInfo->discharge_date ?? '')) + ?? $this->normalizeDateValue((string) ($ipdMasterRow['discharge_date'] ?? '')) + ?? date('Y-m-d'); + $savedClinicalDates = $this->getSavedClinicalLabDates($ipdId); + $clinicalLabRows = $this->getClinicalInvestigationLabRows($patientId, $admitDate, $dischargeDate, $savedClinicalDates); + $clinicalNonPathRows = $this->getClinicalNonPathReportRows($patientId, $admitDate, $dischargeDate, $savedNonPathIds); + $labInvestigationList = implode(',', $savedClinicalDates); + $nonPathInvestigationList = implode(',', $savedNonPathIds); + + return view('billing/ipd/discharge_create', [ + 'ipd_id' => $ipdId, + 'ipd_info' => $panelData['ipd_info'] ?? null, + 'person_info' => $panelData['person_info'] ?? null, + 'notice' => $notice, + 'notice_type' => $noticeType, + 'status_rows' => $statusRows, + 'department_rows' => $departmentRows, + 'ipd_master_row' => $ipdMasterRow, + 'complaint_rows' => $complaintRows, + 'surgery_rows' => $surgeryRows, + 'procedure_rows' => $procedureRows, + 'diagnosis_rows' => $diagnosisRows, + 'course_rows' => $courseRows, + 'drug_rows' => $drugRows, + 'patient_history_row' => $patientHistoryRow, + 'physical_exam_rows' => $physicalExamRows, + 'manual_invest_rows' => $manualInvestRows, + 'special_invest_rows' => $specialInvestRows, + 'clinical_lab_rows' => $clinicalLabRows, + 'clinical_non_path_rows' => $clinicalNonPathRows, + 'lab_investigation_list' => $labInvestigationList, + 'non_path_investigation_list' => $nonPathInvestigationList, + 'discharge_condition_rows' => $dischargeConditionRows, + 'complaint_remark' => (string) ($complaintRemarkRow['comp_remark'] ?? ''), + 'diagnosis_remark' => (string) ($diagnosisRemarkRow['comp_remark'] ?? ''), + 'course_remark' => (string) ($courseRemarkRow['comp_remark'] ?? ''), + 'instruction_remark' => (string) ($instructionRow['comp_remark'] ?? ''), + 'review_after' => (string) ($instructionRow['review_after'] ?? ''), + 'inhos_remark' => (string) ($inhosRow['comp_remark'] ?? ''), + 'other_exam_text' => (string) ($otherExamParsed['text'] ?? ''), + ]); + } + + public function preview_discharge_report(int $ipdId) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'billing.ipd.current-admission', + ]); + if ($permission) { + return $permission; + } + + if ($ipdId <= 0) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Invalid IPD id'); + } + + $panelData = $this->ipdBillingModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $notice = ''; + $noticeType = 'success'; + $content = $this->getDischargeContent($ipdId); + $shouldRegenerate = (int) ($this->request->getGet('regen') ?? 0) === 1; + + if (strtolower($this->request->getMethod()) === 'post') { + $content = (string) ($this->request->getPost('editor_Discharge_Preview') ?? ''); + if ($this->saveDischargeContent($ipdId, $content)) { + $notice = 'Discharge summary saved.'; + $noticeType = 'success'; + } else { + $notice = 'Unable to save discharge summary in ipd_discharge table. Please check schema/permissions.'; + $noticeType = 'danger'; + } + } elseif ($shouldRegenerate || trim(strip_tags($content)) === '') { + $generated = $this->buildAutoDischargeContent($ipdId, $panelData); + if (trim(strip_tags($generated)) !== '') { + $content = $generated; + if ($this->saveDischargeContent($ipdId, $content)) { + $notice = 'Discharge summary auto-generated from IPD discharge data.'; + $noticeType = 'success'; + } else { + $notice = 'Discharge summary generated in editor, but database save failed. Please click Save again after checking DB schema.'; + $noticeType = 'warning'; + } + } + } + + return view('billing/ipd/discharge_preview', [ + 'ipd_id' => $ipdId, + 'ipd_info' => $panelData['ipd_info'] ?? null, + 'person_info' => $panelData['person_info'] ?? null, + 'content' => $content, + 'notice' => $notice, + 'notice_type' => $noticeType, + ]); + } + + public function show_discharge(int $ipdId, int $printType = 1) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + 'billing.ipd.current-admission', + ]); + if ($permission) { + return $permission; + } + + if ($ipdId <= 0) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Invalid IPD id'); + } + + $panelData = $this->ipdBillingModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $content = $this->getDischargeContent($ipdId); + if (trim(strip_tags($content)) === '') { + $content = $this->buildAutoDischargeContent($ipdId, $panelData); + if (trim(strip_tags($content)) !== '') { + $this->saveDischargeContent($ipdId, $content); + } + } + + return view('billing/ipd/discharge_print', [ + 'ipd_id' => $ipdId, + 'ipd_info' => $panelData['ipd_info'] ?? null, + 'person_info' => $panelData['person_info'] ?? null, + 'content' => $content, + 'print_type' => $printType, + 'print_mode' => 'standard', + ]); + } + + public function show_file3(int $ipdId) + { + return $this->show_discharge($ipdId, 3); + } +} diff --git a/app/Views/billing/ipd/discharge_create.php b/app/Views/billing/ipd/discharge_create.php new file mode 100644 index 0000000..c6eb96b --- /dev/null +++ b/app/Views/billing/ipd/discharge_create.php @@ -0,0 +1,967 @@ +p_fname ?? '')); + +$patientCode = ''; +if ($person) { + $patientCode = trim((string) ( + $person->uhid + ?? $person->UHID + ?? $person->patient_code + ?? $person->p_code + ?? $person->reg_no + ?? '' + )); +} + +$age = ''; +if ($person) { + $age = get_age_1($person->dob ?? null, $person->age ?? '', $person->age_in_month ?? '', $person->estimate_dob ?? ''); +} + +$dischargeDateValue = ''; +if (! empty($master['discharge_date'])) { + $ts = strtotime((string) $master['discharge_date']); + if ($ts !== false) { + $dischargeDateValue = date('Y-m-d', $ts); + } +} + +$dischargeTimeValue = (string) ($master['discharge_time'] ?? ''); +if (preg_match('/^\d{2}:\d{2}:\d{2}$/', $dischargeTimeValue)) { + $dischargeTimeValue = substr($dischargeTimeValue, 0, 5); +} +$complaintRows = $complaint_rows ?? []; +$patientHistory = $patient_history_row ?? []; +$physicalExamRows = $physical_exam_rows ?? []; +$generalExamGroup1 = $physicalExamRows['general_group_1'] ?? []; +$generalExamGroup2 = $physicalExamRows['general_group_2'] ?? []; +$systemicExamRows = $physicalExamRows['systemic'] ?? []; +$manualInvestRows = $manual_invest_rows ?? []; +$specialInvestRows = $special_invest_rows ?? []; +$clinicalLabRows = $clinical_lab_rows ?? []; +$clinicalNonPathRows = $clinical_non_path_rows ?? []; +$labInvestigationList = (string) ($lab_investigation_list ?? ''); +$nonPathInvestigationList = (string) ($non_path_investigation_list ?? ''); +$dischargeConditionRows = $discharge_condition_rows ?? []; +$surgeryRows = $surgery_rows ?? []; +$procedureRows = $procedure_rows ?? []; +$diagnosisRows = $diagnosis_rows ?? []; +$courseRows = $course_rows ?? []; +$drugRows = $drug_rows ?? []; +$inhosRemark = (string) ($inhos_remark ?? ''); +$otherExamText = (string) ($other_exam_text ?? ''); + + $historyFields = [ + 'is_smoking' => 'Smoking', + 'is_alcohol' => 'Alcohol', + 'is_tobacoo' => 'Tobacco', + 'is_drug_abuse' => 'Drug abuse', + 'is_hypertesion' => 'Hypertension', + 'is_niddm' => 'Type 2 diabetes mellitus (DM)', + 'is_hbsag' => 'HBsAg', + 'is_hcv' => 'HCV', + 'is_hiv_I_II' => 'HIV I & II', + ]; +?> + + + +
+
+ Name : + / Gender : xgender ?? '')) ?> + / Age : + / UHID / Patient Code : + / IPD ID : ipd_code ?? '')) ?> +
+ +
+
+ + + + +
+ + +
+
+ + +
+
Personal History
+
+
+ $label): ?> +
+
+ > + +
+
+ +
+
+
+ +
+
Complaints with Duration and Reason for Admission
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Complaint NameRemarksAction
No complaint rows yet.
+ +
+
+ + + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ + +
+
+
+ +
+
Examination on Admission
+
+
General Examination
+
+ +
+ + +
+ + +
No general examination master rows found (cat_group=1).
+ +
+ +
+ +
+ +
+ + +
+ + +
No general examination master rows found (cat_group=2).
+ +
+ +
+ +
+
+
+ +
+
Other / Systemic Examinations
+
+ +
No systemic examination master rows found.
+ + +
+ + +
+ + +
+
+ +
+
Clinical Investigation Reports
+
+
+
+ Clinical Investigation (In-Hospital Lab) + Blood Hb, Blood Sugar, Renal Function, Serum Bilirubin, Urine Test +
+
+ +
No pathology tests found between admission and discharge dates.
+ + +
+ > + +
+ + +
+
+ +
+
+ Clinical Investigation (X-Ray / ECG / Sonography / CT / MRI) + Select finalized impression-based reports during this admission +
+
+ +
No non-pathology impression reports found between admission and discharge dates.
+ + +
+ > + +
+ Impression: +
+
+ + +
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ + + + +
+ +
+
+
+ +
+
Surgery / Procedure / delivery if any
+
+
Surgery
+ + + + + + + + + + + + + + +
NameDateRemarkAction
No surgery rows.
+ +
+
+
+
+
+
+ +
Procedure
+ + + + + + + + + + + + + + +
NameDateRemarkAction
No procedure rows.
+ +
+
+
+
+
+
+
+
+ +
+
Final Diagnosis
+
+ + + + + + + + + + + + + +
DiagnosisRemarkAction
No diagnosis rows.
+ +
+
+
+
+
+
+
+ +
+
Summary of key investigation during Hospitalization
+
+ +
+
+ +
+
Course / Treatment in the hospital
+
+ + + + + + + + + + + + + +
CourseRemarkAction
No course rows.
+ +
+
+
+
+
+
+
+ +
+
Condition at the time of Discharge
+
+ +
No discharge condition master rows found.
+ +
+ + +
+ +
+
+ +
+
Discharge Medicine Prescribed
+
+ + + + + + + + + + + + + + +
DrugDoseDayAction
No discharge drug rows.
+ +
+
+
+
+
+
+
+
+ +
+
Admission / Discharge Information
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
Summary Blocks
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + + +
+
+
+
+
+
+ + +
diff --git a/app/Views/billing/ipd/discharge_preview.php b/app/Views/billing/ipd/discharge_preview.php new file mode 100644 index 0000000..6227017 --- /dev/null +++ b/app/Views/billing/ipd/discharge_preview.php @@ -0,0 +1,131 @@ +p_fname ?? '')); +$patientCode = trim((string) ( + $person->uhid + ?? $person->UHID + ?? $person->patient_code + ?? $person->p_code + ?? $person->reg_no + ?? '' +)); + +$age = ''; +if ($person) { + $age = get_age_1($person->dob ?? null, $person->age ?? '', $person->age_in_month ?? '', $person->estimate_dob ?? ''); +} +?> + +
+
+
+
Discharge Preview
+
+ IPD: ipd_code ?? $ipdId) ?> + + | Patient: + +
+
+ +
+ + + + +
+
UHID:
+
Age/Gender: xgender ?? ''))) ?>
+
Admit Date: str_register_date ?? '') ?>
+
Discharge Date: str_discharge_date ?? '') ?>
+
+ +
+ +
+ +
+ +
+ + + + + + +
+
+
+
+
+ + diff --git a/app/Views/billing/ipd/discharge_print.php b/app/Views/billing/ipd/discharge_print.php new file mode 100644 index 0000000..62c257b --- /dev/null +++ b/app/Views/billing/ipd/discharge_print.php @@ -0,0 +1,90 @@ +p_fname ?? '') . ' ' . ($person->p_lname ?? ''))); +if ($patientName === '') { + $patientName = (string) ($person->p_fname ?? ''); +} + +$title = 'IPD Discharge Preview'; +if ($printType === 0) { + $title = 'IPD Discharge Preview (Plain Paper)'; +} elseif ($printType === 3) { + $title = 'IPD Discharge Preview (New Print)'; +} +?> + + + + + + <?= esc($title) ?> + + + + + + + +