diff --git a/app/Config/AuthGroups.php b/app/Config/AuthGroups.php index d71dddb..7222cc0 100644 --- a/app/Config/AuthGroups.php +++ b/app/Config/AuthGroups.php @@ -95,6 +95,9 @@ class AuthGroups extends ShieldAuthGroups 'billing.ipd.invoice' => 'Can view IPD invoice list', 'billing.ipd.cash-balance' => 'Can view IPD cash balance report', 'billing.ipd.export' => 'Can export IPD cash balance report', + 'billing.ipd.admission.edit' => 'Can edit IPD admission details (date, mode, department, refer, doctors, contact, clinical notes)', + 'billing.ipd.bill.print' => 'Can print IPD bill variants', + 'billing.ipd.discharge.edit' => 'Can edit IPD discharge process details (discount, extra charges, status, date, time)', 'finance.access' => 'Can access finance module', 'finance.vendor.manage' => 'Can manage finance vendors', 'finance.po.manage' => 'Can manage purchase orders', diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 763f701..cb0438c 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -315,6 +315,12 @@ $routes->get('Ipd_discharge/show_discharge/(:num)', 'Ipd_discharge::show_dischar $routes->get('Ipd_discharge/show_discharge/(:num)/(:num)', 'Ipd_discharge::show_discharge/$1/$2'); $routes->get('Ipd_discharge/show_file3/(:num)', 'Ipd_discharge::show_file3/$1'); +// Legacy CI3 compatibility links for IPD bill print modes. +$routes->get('ipdNew/ipd_complete_invoice/(:num)', 'Billing\\Ipd::billPrint/$1/1'); +$routes->get('ipdNew/ipd_complete_invoice/(:num)/(:num)', 'Billing\\Ipd::billPrint/$1/$2'); +$routes->get('IpdNew/ipd_complete_invoice/(:num)', 'Billing\\Ipd::billPrint/$1/1'); +$routes->get('IpdNew/ipd_complete_invoice/(:num)/(:num)', 'Billing\\Ipd::billPrint/$1/$2'); + $routes->get('setup/db-tools', 'Setup::dbTools'); $routes->post('setup/db-tools/generate', 'Setup::generateMigrations'); $routes->post('setup/db-tools/export-master-schema', 'Setup::exportMasterSchema'); @@ -334,6 +340,12 @@ $routes->group('billing', function($routes) { $routes->post('ipd/list', 'Billing\\Ipd::getIpdTable'); $routes->get('ipd/panel/(:num)', 'Billing\\Ipd::panel/$1'); $routes->get('ipd/panel/(:num)/tab/(:segment)', 'Billing\\Ipd::panelTab/$1/$2'); + $routes->post('ipd/panel/(:num)/admission', 'Billing\Ipd::updateAdmission/$1'); + $routes->post('ipd/panel/(:num)/discharge/update', 'Billing\\Ipd::updateDischargeProcess/$1'); + $routes->post('ipd/panel/(:num)/discharge/discount/(:num)', 'Billing\\Ipd::updateDischargeDiscount/$1/$2'); + $routes->post('ipd/panel/(:num)/discharge/charge/(:num)', 'Billing\\Ipd::updateDischargeCharge/$1/$2'); + $routes->get('ipd/bill-print/(:num)', 'Billing\\Ipd::billPrint/$1'); + $routes->get('ipd/bill-print/(:num)/(:num)', 'Billing\\Ipd::billPrint/$1/$2'); $routes->post('ipd/charge/add/(:num)', 'Billing\\Ipd::addIpdCharge/$1'); $routes->post('ipd/charge/update/(:num)', 'Billing\\Ipd::updateIpdCharge/$1'); $routes->post('ipd/charge/delete/(:num)', 'Billing\\Ipd::deleteIpdCharge/$1'); diff --git a/app/Controllers/Billing/Ipd.php b/app/Controllers/Billing/Ipd.php index 4d5c239..5010093 100644 --- a/app/Controllers/Billing/Ipd.php +++ b/app/Controllers/Billing/Ipd.php @@ -9,6 +9,7 @@ use App\Models\IpdBillingModel; use App\Models\IpdModel; use App\Models\NursingBedsideItemModel; use App\Models\PaymentModel; +use Mpdf\Mpdf; class Ipd extends BaseController { @@ -385,7 +386,7 @@ class Ipd extends BaseController } if ($tab === 'admission') { - return view('billing/ipd/panel_admission', $panelData); + return view('billing/ipd/panel_admission', $this->buildAdmissionTabData($panelData, $ipdId)); } if ($tab === 'bed-assign') { @@ -462,54 +463,13 @@ class Ipd extends BaseController } if ($tab === 'bill-details') { - $panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId); - $panelData['ipd_invoice_items'] = $this->ipdModel->getIpdInvoiceItems( - $ipdId, - ! empty($panelData['ipd_packages']) - ); - $panelData['showinvoice'] = $this->ipdModel->getIpdInvoiceCharges($ipdId); - $panelData['inv_med_list'] = $this->ipdModel->getIpdMedicalCredits($ipdId); - $panelData['ipd_payment'] = $this->ipdModel->getPayments($ipdId); - $panelData['insurance'] = $this->ipdModel->getIpdInsurance($ipdId); - - $grossTotal = 0.0; - foreach ($panelData['ipd_packages'] as $row) { - $grossTotal += (float) ($row->package_Amount ?? 0); - } - foreach ($panelData['ipd_invoice_items'] as $row) { - $grossTotal += (float) ($row->item_amount ?? 0); - } - foreach ($panelData['showinvoice'] as $row) { - $grossTotal += (float) ($row->amount ?? 0); - } - foreach ($panelData['inv_med_list'] as $row) { - $grossTotal += (float) ($row->net_amount ?? 0); - } - - $ipd = $panelData['ipd_info'] ?? null; - $discountTotal = (float) ($ipd->Discount ?? 0) + (float) ($ipd->Discount2 ?? 0) + (float) ($ipd->Discount3 ?? 0); - $extraCharge = (float) ($ipd->chargeamount1 ?? 0) + (float) ($ipd->chargeamount2 ?? 0); - $netTotal = ($grossTotal + $extraCharge) - $discountTotal; - - $panelData['bill_totals'] = [ - 'gross' => $grossTotal, - 'net' => $netTotal, - ]; - - $totalPaid = 0.0; - foreach ($panelData['ipd_payment'] as $row) { - $amount = (float) ($row->amount ?? 0); - $creditDebit = (int) ($row->credit_debit ?? 0); - $totalPaid += $creditDebit === 0 ? $amount : $amount * -1; - } - $panelData['bill_totals']['paid'] = $totalPaid; - $panelData['bill_totals']['balance'] = $netTotal - $totalPaid; - - return view('billing/ipd/panel_bill_details', $panelData); + return view('billing/ipd/panel_bill_details', $this->buildBillDetailsTabData($panelData, $ipdId)); } if ($tab === 'discharge-process') { $panelData['discharge_info'] = $this->ipdModel->getDischargeInfo($ipdId); + $user = auth()->user(); + $panelData['can_edit_discharge'] = $user !== null && $user->can('billing.ipd.discharge.edit'); return view('billing/ipd/panel_discharge', $panelData); } @@ -519,6 +479,319 @@ class Ipd extends BaseController ]); } + public function updateAdmission(int $ipdId) + { + $permission = $this->requireAnyPermission([ + 'billing.ipd.admission.edit', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']); + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'message' => 'IPD not found']); + } + + $registerDate = $this->normalizeAdmissionDate((string) ($this->request->getPost('register_date') ?? '')); + if ($registerDate === null) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid admission date']); + } + + $regTime = $this->normalizeAdmissionTime((string) ($this->request->getPost('reg_time') ?? '')); + if ($regTime === null) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid admission time']); + } + + $doctorIds = $this->request->getPost('doc_id'); + $doctorIds = is_array($doctorIds) + ? array_values(array_unique(array_filter(array_map('intval', $doctorIds), static fn ($id) => $id > 0))) + : []; + + $ipdFields = $this->db->getFieldNames('ipd_master') ?? []; + $fieldMap = array_flip($ipdFields); + + $primaryDoctorId = $doctorIds[0] ?? 0; + $primaryDoctorName = $primaryDoctorId > 0 ? $this->ipdModel->getDoctorNameById($primaryDoctorId) : ''; + + $masterUpdate = []; + if (isset($fieldMap['register_date'])) { + $masterUpdate['register_date'] = $registerDate; + } + if (isset($fieldMap['reg_time'])) { + $masterUpdate['reg_time'] = $regTime; + } + if (isset($fieldMap['reg_time_bak'])) { + $masterUpdate['reg_time_bak'] = $regTime; + } + if (isset($fieldMap['case_type'])) { + $masterUpdate['case_type'] = (int) ($this->request->getPost('case_type') ?? 0) === 1 ? 1 : 0; + } + if (isset($fieldMap['dept_id'])) { + $masterUpdate['dept_id'] = (int) ($this->request->getPost('dept_id') ?? 0); + } + if (isset($fieldMap['refer_by'])) { + $masterUpdate['refer_by'] = (int) ($this->request->getPost('refer_by') ?? 0); + } + if (isset($fieldMap['problem'])) { + $masterUpdate['problem'] = substr((string) ($this->request->getPost('problem') ?? ''), 0, 200); + } + if (isset($fieldMap['remark'])) { + $masterUpdate['remark'] = (string) ($this->request->getPost('remark') ?? ''); + } + if (isset($fieldMap['contact_person_Name'])) { + $masterUpdate['contact_person_Name'] = substr((string) ($this->request->getPost('contact_person_Name') ?? ''), 0, 50); + } + if (isset($fieldMap['P_mobile1'])) { + $masterUpdate['P_mobile1'] = substr((string) ($this->request->getPost('P_mobile1') ?? ''), 0, 50); + } + if (isset($fieldMap['P_mobile2'])) { + $masterUpdate['P_mobile2'] = substr((string) ($this->request->getPost('P_mobile2') ?? ''), 0, 50); + } + if (isset($fieldMap['relation'])) { + $masterUpdate['relation'] = substr((string) ($this->request->getPost('relation') ?? ''), 0, 50); + } + if (isset($fieldMap['r_doc_id'])) { + $masterUpdate['r_doc_id'] = $primaryDoctorId; + } + if (isset($fieldMap['r_doc_name'])) { + $masterUpdate['r_doc_name'] = $primaryDoctorName; + } + + $this->db->transStart(); + if (! empty($masterUpdate)) { + $this->ipdEditModel->updateIpd($masterUpdate, $ipdId); + } + $this->ipdEditModel->replaceIpdDoctors($ipdId, $doctorIds); + $this->db->transComplete(); + + if (! $this->db->transStatus()) { + return $this->response->setStatusCode(500)->setJSON(['update' => 0, 'message' => 'Unable to update admission details']); + } + + $freshPanelData = $this->ipdModel->getIpdPanelInfo($ipdId); + $admissionData = $this->buildAdmissionTabData($freshPanelData, $ipdId); + + return $this->response->setJSON([ + 'update' => 1, + 'message' => 'Admission details updated.', + 'html' => view('billing/ipd/panel_admission', $admissionData), + 'header' => $this->buildAdmissionHeaderPayload($freshPanelData), + ]); + } + + public function updateDischargeDiscount(int $ipdId, int $slot) + { + $permission = $this->requireAnyPermission([ + 'billing.ipd.discharge.edit', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']); + } + + if (! in_array($slot, [1, 2, 3], true)) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Invalid discount slot']); + } + + $fieldMap = $this->getIpdFieldMap(); + $amount = max(0, (float) ($this->request->getPost('amount') ?? 0)); + $remark = trim((string) ($this->request->getPost('remark') ?? '')); + + $amountKey = $slot === 1 ? 'Discount' : 'Discount' . $slot; + $remarkKey = $slot === 1 ? 'Discount_Remark' : 'Discount_Remark' . $slot; + + $update = []; + if (isset($fieldMap[$amountKey])) { + $update[$amountKey] = $amount; + } + if (isset($fieldMap[$remarkKey])) { + $update[$remarkKey] = substr($remark, 0, 200); + } + + if (empty($update)) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Discount fields are not available in schema']); + } + + $this->ipdEditModel->updateIpd($update, $ipdId); + $this->ipdEditModel->calculateIPD($ipdId); + + return $this->buildDischargeProcessResponse($ipdId, 'Discount updated.'); + } + + public function updateDischargeCharge(int $ipdId, int $slot) + { + $permission = $this->requireAnyPermission([ + 'billing.ipd.discharge.edit', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']); + } + + if (! in_array($slot, [1, 2], true)) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Invalid additional charge slot']); + } + + $fieldMap = $this->getIpdFieldMap(); + $amount = max(0, (float) ($this->request->getPost('amount') ?? 0)); + $remark = trim((string) ($this->request->getPost('remark') ?? '')); + + $remarkKey = 'charge' . $slot; + $amountKey = 'chargeamount' . $slot; + $byKey = 'charge_by' . $slot; + + $update = []; + if (isset($fieldMap[$remarkKey])) { + $update[$remarkKey] = substr($remark, 0, 200); + } + if (isset($fieldMap[$amountKey])) { + $update[$amountKey] = $amount; + } + if (isset($fieldMap[$byKey])) { + $user = auth()->user(); + $update[$byKey] = (string) ($user->username ?? $user->email ?? 'system'); + } + + if (empty($update)) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Charge fields are not available in schema']); + } + + $this->ipdEditModel->updateIpd($update, $ipdId); + $this->ipdEditModel->calculateIPD($ipdId); + + return $this->buildDischargeProcessResponse($ipdId, 'Additional charge updated.'); + } + + public function updateDischargeProcess(int $ipdId) + { + $permission = $this->requireAnyPermission([ + 'billing.ipd.discharge.edit', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']); + } + + $fieldMap = $this->getIpdFieldMap(); + + $dischargeDate = $this->normalizeAdmissionDate((string) ($this->request->getPost('discharge_date') ?? '')); + if ($dischargeDate === null) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid discharge date']); + } + + $dischargeTime = $this->normalizeAdmissionTime((string) ($this->request->getPost('discharge_time') ?? '')); + if ($dischargeTime === null) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid discharge time']); + } + + $status = (int) ($this->request->getPost('discharge_status') ?? 0); + if ($status < 1 || $status > 7) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Select valid discharge status']); + } + + $update = []; + if (isset($fieldMap['discarge_patient_status'])) { + $update['discarge_patient_status'] = $status; + } + if (isset($fieldMap['discharge_date'])) { + $update['discharge_date'] = $dischargeDate; + } + if (isset($fieldMap['discharge_time'])) { + $update['discharge_time'] = $dischargeTime; + } + if (isset($fieldMap['discharge_time_bak'])) { + $update['discharge_time_bak'] = $dischargeTime; + } + if (isset($fieldMap['discharge_remark'])) { + $update['discharge_remark'] = (string) ($this->request->getPost('discharge_remark') ?? ''); + } + if (isset($fieldMap['discharge_balance_user'])) { + $update['discharge_balance_user'] = substr((string) ($this->request->getPost('balance_user') ?? ''), 0, 200); + } + if (isset($fieldMap['discharge_balance_remark'])) { + $update['discharge_balance_remark'] = substr((string) ($this->request->getPost('balance_remark') ?? ''), 0, 200); + } + if (isset($fieldMap['discharge_by'])) { + $user = auth()->user(); + $update['discharge_by'] = (string) ($user->username ?? $user->email ?? 'system'); + } + if (isset($fieldMap['ipd_status'])) { + $update['ipd_status'] = 1; + } + + if (empty($update)) { + return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Discharge fields are not available in schema']); + } + + $this->ipdEditModel->updateIpd($update, $ipdId); + $this->ipdEditModel->calculateIPD($ipdId); + + return $this->buildDischargeProcessResponse($ipdId, 'Discharge status updated.'); + } + + public function billPrint(int $ipdId, int $mode = 1) + { + $permission = $this->requireAnyPermission([ + 'billing.ipd.bill.print', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $mode = max(1, min(6, $mode)); + $data = $this->buildBillDetailsTabData($panelData, $ipdId); + $data['show_print_actions'] = false; + $data['show_payment_details'] = in_array($mode, [1, 6], true); + $data['letterhead_mode'] = in_array($mode, [5, 6], true); + $data['print_mode'] = $mode; + + $html = view('billing/ipd/bill_print', $data); + + $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; + if (! is_dir($mpdfTempDir)) { + mkdir($mpdfTempDir, 0755, true); + } + + $mpdf = new Mpdf([ + 'tempDir' => $mpdfTempDir, + 'format' => 'A4', + 'margin_top' => 8, + 'margin_bottom' => 8, + 'margin_left' => 8, + 'margin_right' => 8, + ]); + + $mpdf->WriteHTML($html); + + $ipdCode = (string) ($data['ipd_info']->ipd_code ?? ('IPD-' . $ipdId)); + $fileName = 'IPD_Bill_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '_M' . $mode . '.pdf'; + + return $this->response + ->setHeader('Content-Type', 'application/pdf') + ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') + ->setBody($mpdf->Output($fileName, 'S')); + } + public function addIpdCharge(int $ipdId) { $permission = $this->requireAnyPermission([ @@ -1518,6 +1791,219 @@ class Ipd extends BaseController ]; } + private function buildBillDetailsTabData(array $panelData, int $ipdId): array + { + $panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId); + $panelData['ipd_invoice_items'] = $this->ipdModel->getIpdInvoiceItems( + $ipdId, + ! empty($panelData['ipd_packages']) + ); + $panelData['showinvoice'] = $this->ipdModel->getIpdInvoiceCharges($ipdId); + $panelData['inv_med_list'] = $this->ipdModel->getIpdMedicalCredits($ipdId); + $panelData['ipd_payment'] = $this->ipdModel->getPayments($ipdId); + $panelData['insurance'] = $this->ipdModel->getIpdInsurance($ipdId); + + $grossTotal = 0.0; + foreach ($panelData['ipd_packages'] as $row) { + $grossTotal += (float) ($row->package_Amount ?? 0); + } + foreach ($panelData['ipd_invoice_items'] as $row) { + $grossTotal += (float) ($row->item_amount ?? 0); + } + foreach ($panelData['showinvoice'] as $row) { + $grossTotal += (float) ($row->amount ?? 0); + } + foreach ($panelData['inv_med_list'] as $row) { + $grossTotal += (float) ($row->net_amount ?? 0); + } + + $ipd = $panelData['ipd_info'] ?? null; + $discountTotal = (float) ($ipd->Discount ?? 0) + (float) ($ipd->Discount2 ?? 0) + (float) ($ipd->Discount3 ?? 0); + $extraCharge = (float) ($ipd->chargeamount1 ?? 0) + (float) ($ipd->chargeamount2 ?? 0); + $netTotal = ($grossTotal + $extraCharge) - $discountTotal; + + $panelData['bill_totals'] = [ + 'gross' => $grossTotal, + 'net' => $netTotal, + ]; + + $totalPaid = 0.0; + foreach ($panelData['ipd_payment'] as $row) { + $amount = (float) ($row->amount ?? 0); + $creditDebit = (int) ($row->credit_debit ?? 0); + $totalPaid += $creditDebit === 0 ? $amount : $amount * -1; + } + $panelData['bill_totals']['paid'] = $totalPaid; + $panelData['bill_totals']['balance'] = $netTotal - $totalPaid; + + $user = auth()->user(); + $panelData['can_print_bill'] = $user !== null && $user->can('billing.ipd.bill.print'); + $panelData['show_print_actions'] = true; + $panelData['show_payment_details'] = true; + + return $panelData; + } + + private function buildDischargeProcessResponse(int $ipdId, string $message) + { + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'message' => 'IPD not found']); + } + + $panelData['discharge_info'] = $this->ipdModel->getDischargeInfo($ipdId); + $user = auth()->user(); + $panelData['can_edit_discharge'] = $user !== null && $user->can('billing.ipd.discharge.edit'); + + return $this->response->setJSON([ + 'update' => 1, + 'message' => $message, + 'html' => view('billing/ipd/panel_discharge', $panelData), + ]); + } + + private function getIpdFieldMap(): array + { + $ipdFields = $this->db->getFieldNames('ipd_master') ?? []; + + return array_flip($ipdFields); + } + + private function buildAdmissionTabData(array $panelData, int $ipdId): array + { + $ipd = $panelData['ipd_info'] ?? null; + $panelData['admission_doctors'] = $this->getAdmissionDoctors($ipdId); + $panelData['selected_doctor_ids'] = array_values(array_map( + static fn ($doctor) => (int) ($doctor->id ?? 0), + $panelData['admission_doctors'] + )); + $panelData['available_doctors'] = $this->ipdModel->getIpdDoctorList(); + $panelData['departments'] = $this->getAdmissionDepartments(); + $panelData['refer_master'] = $this->getAdmissionReferMasters(); + $panelData['register_date_input'] = $this->formatHtmlDateInput((string) ($ipd->register_date ?? '')); + $panelData['reg_time_input'] = $this->formatHtmlTimeInput((string) ($ipd->reg_time ?? '')); + $user = auth()->user(); + $panelData['can_edit_admission'] = $user !== null && $user->can('billing.ipd.admission.edit'); + + return $panelData; + } + + private function getAdmissionDoctors(int $ipdId): array + { + if (! $this->db->tableExists('ipd_master_doc_list') || ! $this->db->tableExists('doctor_master')) { + return []; + } + + return $this->db->table('ipd_master_doc_list l') + ->select("d.id, concat_ws(' ', 'Dr.', d.p_fname, d.p_mname, d.p_lname) as doctor_name", false) + ->join('doctor_master d', 'd.id = l.doc_id', 'inner') + ->where('l.ipd_id', $ipdId) + ->groupBy('d.id, d.p_fname, d.p_mname, d.p_lname') + ->orderBy('d.p_fname', 'ASC') + ->get() + ->getResult(); + } + + private function getAdmissionDepartments(): array + { + if (! $this->db->tableExists('hc_department')) { + return []; + } + + return $this->db->table('hc_department') + ->orderBy('vName', 'ASC') + ->get() + ->getResult(); + } + + private function getAdmissionReferMasters(): array + { + if (! $this->db->tableExists('refer_master')) { + return []; + } + + return $this->db->table('refer_master') + ->where('active', 1) + ->orderBy('f_name', 'ASC') + ->get() + ->getResult(); + } + + private function buildAdmissionHeaderPayload(array $panelData): array + { + $ipd = $panelData['ipd_info'] ?? null; + + return [ + 'admit_date' => (string) ($ipd->str_register_date ?? ''), + 'discharge_date' => (string) ($ipd->str_discharge_date ?? ''), + 'no_days' => (string) ($ipd->no_days ?? ''), + ]; + } + + private function formatHtmlDateInput(string $dateValue): string + { + $dateValue = trim($dateValue); + if ($dateValue === '') { + return date('Y-m-d'); + } + + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateValue) === 1) { + return $dateValue; + } + + $timestamp = strtotime($dateValue); + + return $timestamp !== false ? date('Y-m-d', $timestamp) : date('Y-m-d'); + } + + private function formatHtmlTimeInput(string $timeValue): string + { + $timeValue = trim($timeValue); + if ($timeValue === '') { + return date('H:i'); + } + + if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $timeValue) === 1) { + return substr($timeValue, 0, 5); + } + + $timestamp = strtotime($timeValue); + + return $timestamp !== false ? date('H:i', $timestamp) : date('H:i'); + } + + private function normalizeAdmissionDate(string $dateValue): ?string + { + $dateValue = trim($dateValue); + if ($dateValue === '') { + return null; + } + + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateValue) === 1) { + return $dateValue; + } + + $timestamp = strtotime($dateValue); + + return $timestamp !== false ? date('Y-m-d', $timestamp) : null; + } + + private function normalizeAdmissionTime(string $timeValue): ?string + { + $timeValue = trim($timeValue); + if ($timeValue === '') { + return null; + } + + if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $timeValue) === 1) { + return substr($timeValue, 0, 5); + } + + $timestamp = strtotime($timeValue); + + return $timestamp !== false ? date('H:i', $timestamp) : null; + } + private function requireAnyPermission(array $permissions) { if (! function_exists('auth')) { diff --git a/app/Models/IpdBillingModel.php b/app/Models/IpdBillingModel.php index c272b89..775a985 100644 --- a/app/Models/IpdBillingModel.php +++ b/app/Models/IpdBillingModel.php @@ -40,6 +40,7 @@ class IpdBillingModel extends Model 'str_register_date', 'str_discharge_date', 'status_desc', + 'action', ]; $totalBuilder = $this->baseIpdListQuery(); @@ -72,7 +73,8 @@ class IpdBillingModel extends Model if(i.ipd_status = 1,'Discharged','Admit') as Disstatus, date_format(i.register_date,'%d-%m-%Y') as str_register_date, date_format(i.discharge_date,'%d-%m-%Y') as str_discharge_date, - d.status_desc as status_desc", + d.status_desc as status_desc, + i.id as ipd_id", false ) ->orderBy($orderColumn, $orderDir) @@ -83,6 +85,14 @@ class IpdBillingModel extends Model $data = []; foreach ($rows as $row) { $item = []; + $ipdId = (int) ($row['ipd_id'] ?? 0); + $actionHtml = ''; + if ($ipdId > 0) { + $panelUrl = base_url('billing/ipd/panel/' . $ipdId); + $actionHtml = ''; + } + $row['action'] = $actionHtml; + foreach ($columns as $column) { $item[] = $row[$column] ?? ''; } diff --git a/app/Models/IpdModel.php b/app/Models/IpdModel.php index d7ae749..ddf0a36 100644 --- a/app/Models/IpdModel.php +++ b/app/Models/IpdModel.php @@ -110,6 +110,49 @@ class IpdModel extends Model return 1; } + public function replaceIpdDoctors(int $ipdId, array $doctorIds): void + { + $ipdId = (int) $ipdId; + if ($ipdId <= 0 || ! $this->db->tableExists('ipd_master_doc_list')) { + return; + } + + $doctorIds = array_values(array_unique(array_filter(array_map('intval', $doctorIds), static fn ($id) => $id > 0))); + + $existingRows = $this->db->table('ipd_master_doc_list') + ->select('id, doc_id') + ->where('ipd_id', $ipdId) + ->get() + ->getResultArray(); + + $existingDoctorIds = []; + foreach ($existingRows as $row) { + $docId = (int) ($row['doc_id'] ?? 0); + $rowId = (int) ($row['id'] ?? 0); + if ($docId <= 0 || $rowId <= 0) { + continue; + } + + $existingDoctorIds[] = $docId; + if (! in_array($docId, $doctorIds, true)) { + $this->removeIpdDoc($rowId); + } + } + + $userSignature = $this->getUserSignature(); + foreach ($doctorIds as $docId) { + if (in_array($docId, $existingDoctorIds, true)) { + continue; + } + + $this->addIpdDoc([ + 'ipd_id' => $ipdId, + 'doc_id' => $docId, + 'log' => 'Insert By :' . $userSignature, + ]); + } + } + private function filterTableDataByExistingColumns(string $table, array $data): array { if (! $this->db->tableExists($table)) { diff --git a/app/Views/billing/ipd/bill_print.php b/app/Views/billing/ipd/bill_print.php new file mode 100644 index 0000000..134fe52 --- /dev/null +++ b/app/Views/billing/ipd/bill_print.php @@ -0,0 +1,325 @@ + 0, 'net' => 0, 'paid' => 0, 'balance' => 0]; + +$printMode = (int) ($print_mode ?? 1); +$showPaymentDetails = (bool) ($show_payment_details ?? true); + +$hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital'; +$hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : ''; +$hospitalAddress2 = defined('H_address_2') ? (string) constant('H_address_2') : ''; +$hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : ''; +$hospitalLogoName = defined('H_logo') ? trim((string) constant('H_logo')) : ''; + +$logoPathCandidates = []; +if ($hospitalLogoName !== '') { + $logoPathCandidates[] = FCPATH . 'assets/images/' . ltrim($hospitalLogoName, '/\\'); + $logoPathCandidates[] = FCPATH . 'assets/img/' . ltrim($hospitalLogoName, '/\\'); +} +$logoPathCandidates[] = FCPATH . 'assets/img/logo.png'; +$logoPathCandidates[] = FCPATH . 'assets/images/logo.png'; + +$logoSrc = ''; +foreach ($logoPathCandidates as $candidate) { + if (is_file($candidate)) { + $logoSrc = str_replace('\\', '/', $candidate); + break; + } +} + +$titleByMode = [ + 1 => 'Provisional Bill', + 2 => 'Provisional Bill', + 3 => 'Item Wise Bill', + 4 => 'TPA Final Bill', + 5 => 'Provisional Bill', + 6 => 'Provisional Bill', +]; +$billTitle = $titleByMode[$printMode] ?? 'Provisional Bill'; + +$age = ''; +if ($person) { + $age = get_age_1($person->dob ?? null, $person->age ?? '', $person->age_in_month ?? '', $person->estimate_dob ?? ''); +} + +$admitDateText = trim((string) ($ipd->str_register_date ?? '')); +if ($admitDateText === '' && ! empty($ipd->register_date)) { + $admitDateText = (string) $ipd->register_date; +} +$admitTimeText = trim((string) ($ipd->reg_time ?? '')); +if ($admitTimeText !== '') { + $admitDateText = trim($admitDateText . ' ' . substr($admitTimeText, 0, 5)); +} + +$dischargeDateText = trim((string) ($ipd->str_discharge_date ?? '')); +if ($dischargeDateText === '' && ! empty($ipd->discharge_date)) { + $dischargeDateText = (string) $ipd->discharge_date; +} +$dischargeTimeText = trim((string) ($ipd->discharge_time ?? '')); +if ($dischargeTimeText !== '') { + $dischargeDateText = trim($dischargeDateText . ' ' . substr($dischargeTimeText, 0, 5)); +} + +$grossAmount = (float) ($ipd->gross_amount ?? 0); +if ($grossAmount <= 0) { + $grossAmount = (float) ($billTotals['gross'] ?? 0); +} + +$netAmount = (float) ($ipd->net_amount ?? 0); +if ($netAmount <= 0) { + $netAmount = (float) ($billTotals['net'] ?? 0); +} + +$paidAmount = (float) ($billTotals['paid'] ?? 0); +if ($paidAmount <= 0) { + $paidAmount = (float) ($ipd->total_paid_amount ?? 0); +} + +$balanceAmount = (float) ($ipd->balance_amount ?? 0); +if ($balanceAmount <= 0) { + $balanceAmount = (float) ($billTotals['balance'] ?? 0); +} + +$patientAddress = trim((string) (($person->add1 ?? '') . ' ' . ($person->add2 ?? '') . ' ' . ($person->city ?? '') . ' ' . ($person->state ?? ''))); +$patientAddress = preg_replace('/\s+/', ' ', $patientAddress ?? ''); + +$mode3ShowAmountAfterDiscount = $printMode === 3; +$isDischargeFinal = (int) ($ipd->discarge_patient_status ?? 0) > 0; +$billHeadingText = $isDischargeFinal + ? 'Bill No. : ' . (string) ($ipd->ipd_code ?? '') + : $billTitle; +?> + + + + + <?= esc($billTitle) ?> + + + + + + + + +
Page: {PAGENO}/{nbpg} + IPD-ID:ipd_code ?? '')) ?> / UHID:p_code ?? '')) ?> / Name:p_fname ?? '')) ?> +
+ + + + + + + +
+
+
+ +
Phone:
+ +
+ + + +
+ +
+ + + + + + +
+
Patient Name : p_fname ?? '')) ?>
+
SON OF : p_rname ?? '')) ?>
+
Patient-ID/UHID : p_code ?? '')) ?>
+
Gender : xgender ?? '')) ?>
+
Age :
+
Phone No : mphone1 ?? '')) ?>
+
Address :
+
+
IPD ID : ipd_code ?? '')) ?>
+
Admit Date :
+
Discharge Date :
+
No. of Days : no_days ?? '')) ?>
+
Bed No : bed_no ?? '')) ?>
+
Doctor Name : r_doc_name ?? '')) ?>
+ status_desc ?? '')) : ?> +
Status : status_desc ?? '')) ?>
+ +
+ + + + + + + + + + + + + + + + + ' . ($mode3ShowAmountAfterDiscount ? '' : '') . ''; + foreach ($packages as $row) { + $amt = (float) ($row->package_Amount ?? 0); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($mode3ShowAmountAfterDiscount) { + echo ''; + } + echo ''; + $srNo++; + } + } + + for ($i = 0, $n = count($ipdItems); $i < $n; $i++) { + $row = $ipdItems[$i]; + if ($headDesc !== (string) ($row->group_desc ?? '')) { + $headDesc = (string) ($row->group_desc ?? ''); + echo '' . ($mode3ShowAmountAfterDiscount ? '' : '') . ''; + } + + $amt = (float) ($row->item_amount ?? 0); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($mode3ShowAmountAfterDiscount) { + echo ''; + } + echo ''; + $srNo++; + $headTotal += $amt; + } + + for ($i = 0, $n = count($showItems); $i < $n; $i++) { + $row = $showItems[$i]; + if ($headDesc !== (string) ($row->Charge_type ?? '')) { + $headDesc = (string) ($row->Charge_type ?? ''); + echo '' . ($mode3ShowAmountAfterDiscount ? '' : '') . ''; + } + + $amt = (float) ($row->amount ?? 0); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($mode3ShowAmountAfterDiscount) { + echo ''; + } + echo ''; + $srNo++; + } + + foreach ($medicalItems as $row) { + $amt = (float) ($row->net_amount ?? 0); + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + if ($mode3ShowAmountAfterDiscount) { + echo ''; + } + echo ''; + $srNo++; + } + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#DescriptionOrg.CodeUnitRateAmountAmt After Discount
Package
' . $srNo . '' . esc((string) ($row->package_name ?? '')) . '' . esc((string) ($row->org_code ?? '')) . '' . esc(number_format($amt, 2)) . '' . esc(number_format($amt, 2)) . '
' . esc($headDesc) . '
' . $srNo . '' . esc(trim((string) (($row->item_name ?? '') . ' ' . ($row->comment ?? '')))) . '' . esc((string) ($row->org_code ?? '')) . '' . esc((string) ($row->item_qty ?? '')) . '' . esc(number_format((float) ($row->item_rate ?? 0), 2)) . '' . esc(number_format($amt, 2)) . '' . esc(number_format($amt, 2)) . '
' . esc($headDesc) . '
' . $srNo . '' . esc((string) ($row->idesc ?? '')) . '' . esc((string) ($row->orgcode ?? '')) . '' . esc((string) ($row->no_qty ?? '')) . '' . esc(number_format((float) ($row->item_rate ?? 0), 2)) . '' . esc(number_format($amt, 2)) . '' . esc(number_format($amt, 2)) . '
' . $srNo . '' . esc((string) ($row->inv_med_code ?? 'Medicine')) . '' . esc(number_format($amt, 2)) . '' . esc(number_format($amt, 2)) . '
Gross Total
Net Amount
Payment Recd.
Balance
+ + + + + + + + + diff --git a/app/Views/billing/ipd/cash_balance.php b/app/Views/billing/ipd/cash_balance.php index af07052..a242572 100644 --- a/app/Views/billing/ipd/cash_balance.php +++ b/app/Views/billing/ipd/cash_balance.php @@ -72,34 +72,49 @@ + +
+ + You do not have permission to edit admission details. Contact your administrator to request the billing.ipd.admission.edit permission. +
+ diff --git a/app/Views/billing/ipd/panel_bill_details.php b/app/Views/billing/ipd/panel_bill_details.php index b7e89c3..850be00 100644 --- a/app/Views/billing/ipd/panel_bill_details.php +++ b/app/Views/billing/ipd/panel_bill_details.php @@ -15,6 +15,9 @@ if ($ipd) { $billTotals = $bill_totals ?? ['gross' => 0.0, 'net' => 0.0]; $paidTotal = (float) ($billTotals['paid'] ?? 0); $balanceTotal = (float) ($billTotals['balance'] ?? 0); +$showPrintActions = (bool) ($show_print_actions ?? true); +$canPrintBill = (bool) ($can_print_bill ?? false); +$showPaymentDetails = (bool) ($show_payment_details ?? true); $grossAmount = (float) ($ipd->gross_amount ?? 0); $netAmount = (float) ($ipd->net_amount ?? 0); $balanceAmount = (float) ($ipd->balance_amount ?? 0); @@ -27,12 +30,28 @@ if ($netAmount <= 0) { if ($balanceAmount <= 0) { $balanceAmount = $balanceTotal; } +$isDischargeFinal = (int) ($ipd->discarge_patient_status ?? 0) > 0; +$billHeaderTitle = $isDischargeFinal ? 'Bill No. : ' . (string) ($ipd->ipd_code ?? '') : 'IPD Invoice'; ?>
- IPD Invoice - / IPD ID: ipd_code ?? '') ?> +
+
+ + / IPD ID: ipd_code ?? '') ?> +
+ id)) : ?> + + +

@@ -247,25 +266,27 @@ if ($balanceAmount <= 0) { Net Amount - - # - - Payment Recd.
- id ?? '') . ':' . esc($row->pay_mode ?? '') . ':' . esc($row->pay_date_str ?? '') . ':' . esc($row->amount ?? '') . '] / '; - if ($i % 3 === 0) { - echo '
'; + + + # + + Payment Recd.
+ id ?? '') . ':' . esc($row->pay_mode ?? '') . ':' . esc($row->pay_date_str ?? '') . ':' . esc($row->amount ?? '') . '] / '; + if ($i % 3 === 0) { + echo '
'; + } } - } - ?> - - - - 0 ? $paidTotal : (float) ($ipd->total_paid_amount ?? 0), 2)) ?> - + ?> + + + + 0 ? $paidTotal : (float) ($ipd->total_paid_amount ?? 0), 2)) ?> + + # diff --git a/app/Views/billing/ipd/panel_discharge.php b/app/Views/billing/ipd/panel_discharge.php index 9058934..223acef 100644 --- a/app/Views/billing/ipd/panel_discharge.php +++ b/app/Views/billing/ipd/panel_discharge.php @@ -1,26 +1,237 @@ id ?? 0); +$statusValue = (int) ($discharge->discarge_patient_status ?? 0); +$statusMap = [ + 0 => 'Status Pending', + 1 => 'Improved/ Recovered', + 2 => 'LAMA', + 3 => 'Referred', + 4 => 'Satisfactory', + 5 => 'Expired', + 6 => 'Admission Cancelled', + 7 => 'Discharge on Request', +]; + +$dischargeDate = (string) ($discharge->discharge_date ?? date('Y-m-d')); +$dischargeTime = (string) ($discharge->discharge_time ?? date('H:i')); +if (strlen($dischargeTime) > 5) { + $dischargeTime = substr($dischargeTime, 0, 5); +} ?> +

-
-
+
+
-
Discharge Status
-

Status : status_desc ?? 'Pending') ?>

-

Discharge Date : discharge_date ?? '') ?>

-

Discharge Time : discharge_time ?? '') ?>

-

Discharge By : discharge_by ?? '') ?>

+
Current Discharge Snapshot
+

Status : status_desc ?? ($statusMap[$statusValue] ?? 'Status Pending')) ?>

+

Discharge Date : discharge_date ?? '')) ?>

+

Discharge Time : discharge_time ?? '')) ?>

+

Discharge By : discharge_by ?? '')) ?>

+

Balance Approved By : discharge_balance_user ?? '')) ?>

+

Balance Remark : discharge_balance_remark ?? '')) ?>

-
-
+
+
-
Remarks
-

Remark : discharge_remark ?? '') ?>

-

Balance User : discharge_balance_user ?? '') ?>

-

Balance Remark : discharge_balance_remark ?? '') ?>

+
Current Financial Adjustments
+

Deduction 1 : Discount ?? 0), 2)) ?> | Discount_Remark ?? '')) ?>

+

Deduction 2 : Discount2 ?? 0), 2)) ?> | Discount_Remark2 ?? '')) ?>

+

Deduction 3 : Discount3 ?? 0), 2)) ?> | Discount_Remark3 ?? '')) ?>

+
+

Additional Charge 1 : chargeamount1 ?? 0), 2)) ?> | charge1 ?? '')) ?>

+

Additional Charge 2 : chargeamount2 ?? 0), 2)) ?> | charge2 ?? '')) ?>

+ + +
+
+
Discharge Process Controls
+ +
Update deductions/additional charges first, then confirm discharge status/date/time.
+ +
+ + + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + + + + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + + +
+ You do not have permission to edit discharge process details. Contact your administrator for billing.ipd.discharge.edit. +
+