Implement IPD admission/discharge editing, bill PDF modes, and invoice list view action

This commit is contained in:
DevSoftTech Server 2026-03-21 15:47:18 +05:30
parent addef91468
commit 38093da3b9
12 changed files with 1585 additions and 130 deletions

View File

@ -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',

View File

@ -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');

View File

@ -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')) {

View File

@ -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 = '<button class="btn btn-sm btn-primary" onclick="load_form_div(\'' . $panelUrl . '\',\'maindiv\',\'IPD Panel\');"><i class="bi bi-eye"></i> View</button>';
}
$row['action'] = $actionHtml;
foreach ($columns as $column) {
$item[] = $row[$column] ?? '';
}

View File

@ -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)) {

View File

@ -0,0 +1,325 @@
<?php
$ipd = $ipd_info ?? null;
$person = $person_info ?? null;
$packages = $ipd_packages ?? [];
$ipdItems = $ipd_invoice_items ?? [];
$showItems = $showinvoice ?? [];
$medicalItems = $inv_med_list ?? [];
$payments = $ipd_payment ?? [];
$billTotals = $bill_totals ?? ['gross' => 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;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= esc($billTitle) ?></title>
<style>
body { font-family: dejavusans, sans-serif; font-size: 10.2px; color: #111; }
.meta-top { width: 100%; margin-bottom: 6px; }
.meta-top td { border: none; font-size: 9.2px; }
.header { width: 100%; border-bottom: 1px solid #999; padding-bottom: 6px; margin-bottom: 8px; }
.header td { border: none; vertical-align: top; }
.logo { width: 58px; height: 58px; text-align: center; }
.logo img { width: 52px; height: 52px; object-fit: contain; }
.hospital-name { font-size: 20px; font-weight: 700; line-height: 1.15; }
.hospital-line { font-size: 11px; line-height: 1.25; }
.qr { text-align: right; width: 90px; }
.title { text-align: center; font-size: 19px; font-weight: 700; margin: 6px 0 8px; }
.info { width: 100%; margin-bottom: 8px; }
.info td { border: none; vertical-align: top; width: 50%; font-size: 10.4px; line-height: 1.3; }
.label { font-weight: 700; }
.bill-table { width: 100%; border-collapse: collapse; margin-top: 2px; }
.bill-table th, .bill-table td { border: 1px solid #888; padding: 3px 4px; font-size: 10.1px; }
.bill-table th { background: #f2f2f2; font-size: 10.2px; }
.center { text-align: center; }
.right { text-align: right; }
.group { font-weight: 700; font-size: 10.2px; }
.totalline { font-weight: 700; }
.footer-sign { margin-top: 14px; width: 100%; }
.footer-sign td { border: none; width: 50%; font-weight: 700; font-size: 10.4px; }
</style>
</head>
<body>
<table class="meta-top" cellspacing="0" cellpadding="0">
<tr>
<td>Page: {PAGENO}/{nbpg}</td>
<td style="text-align:right;">
IPD-ID:<?= esc((string) ($ipd->ipd_code ?? '')) ?> / UHID:<?= esc((string) ($person->p_code ?? '')) ?> / Name:<?= esc((string) ($person->p_fname ?? '')) ?>
</td>
</tr>
</table>
<table class="header" cellspacing="0" cellpadding="0">
<tr>
<td class="logo">
<?php if ($logoSrc !== '') : ?>
<img src="<?= esc($logoSrc) ?>" alt="logo" width="52" height="52">
<?php endif; ?>
</td>
<td>
<div class="hospital-name"><?= esc($hospitalName) ?></div>
<div class="hospital-line"><?= esc(trim($hospitalAddress1 . ', ' . $hospitalAddress2, ', ')) ?></div>
<?php if ($hospitalPhone !== '') : ?>
<div class="hospital-line">Phone: <?= esc($hospitalPhone) ?></div>
<?php endif; ?>
</td>
<td class="qr">
<?php if (! empty($letterhead_mode)) : ?>
<barcode code="<?= esc((string) ($ipd->ipd_code ?? 'IPD')) ?>|<?= esc((string) ($person->p_code ?? '')) ?>" size="0.75" type="QR" error="M" />
<?php endif; ?>
</td>
</tr>
</table>
<div class="title"><?= esc($billHeadingText) ?></div>
<table class="info" cellspacing="0" cellpadding="0">
<tr>
<td>
<div><span class="label">Patient Name :</span> <?= esc((string) ($person->p_fname ?? '')) ?></div>
<div><span class="label">SON OF :</span> <?= esc((string) ($person->p_rname ?? '')) ?></div>
<div><span class="label">Patient-ID/UHID :</span> <?= esc((string) ($person->p_code ?? '')) ?></div>
<div><span class="label">Gender :</span> <?= esc((string) ($person->xgender ?? '')) ?></div>
<div><span class="label">Age :</span> <?= esc((string) $age) ?></div>
<div><span class="label">Phone No :</span> <?= esc((string) ($person->mphone1 ?? '')) ?></div>
<div><span class="label">Address :</span> <?= esc((string) $patientAddress) ?></div>
</td>
<td>
<div><span class="label">IPD ID :</span> <?= esc((string) ($ipd->ipd_code ?? '')) ?></div>
<div><span class="label">Admit Date :</span> <?= esc((string) $admitDateText) ?></div>
<div><span class="label">Discharge Date :</span> <?= esc((string) $dischargeDateText) ?></div>
<div><span class="label">No. of Days :</span> <?= esc((string) ($ipd->no_days ?? '')) ?></div>
<div><span class="label">Bed No :</span> <?= esc((string) ($ipd->bed_no ?? '')) ?></div>
<div><span class="label">Doctor Name :</span> <?= esc((string) ($ipd->r_doc_name ?? '')) ?></div>
<?php if (! empty($ipd->status_desc ?? '')) : ?>
<div><span class="label">Status :</span> <?= esc((string) ($ipd->status_desc ?? '')) ?></div>
<?php endif; ?>
</td>
</tr>
</table>
<table class="bill-table" cellspacing="0" cellpadding="0">
<thead>
<tr>
<th style="width:5%;" class="center">#</th>
<th style="width:39%;">Description</th>
<th style="width:12%;" class="center">Org.Code</th>
<th style="width:8%;" class="center">Unit</th>
<th style="width:12%;" class="right">Rate</th>
<th style="width:12%;" class="right">Amount</th>
<?php if ($mode3ShowAmountAfterDiscount) : ?>
<th style="width:12%;" class="right">Amt After Discount</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php
$srNo = 1;
$headDesc = '';
$headTotal = 0.0;
if (! empty($packages)) {
echo '<tr><td></td><td class="group">Package</td><td></td><td></td><td></td><td></td>' . ($mode3ShowAmountAfterDiscount ? '<td></td>' : '') . '</tr>';
foreach ($packages as $row) {
$amt = (float) ($row->package_Amount ?? 0);
echo '<tr>';
echo '<td class="center">' . $srNo . '</td>';
echo '<td>' . esc((string) ($row->package_name ?? '')) . '</td>';
echo '<td class="center">' . esc((string) ($row->org_code ?? '')) . '</td>';
echo '<td class="center"></td>';
echo '<td class="right"></td>';
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
if ($mode3ShowAmountAfterDiscount) {
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
}
echo '</tr>';
$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 '<tr><td></td><td class="group">' . esc($headDesc) . '</td><td></td><td></td><td></td><td></td>' . ($mode3ShowAmountAfterDiscount ? '<td></td>' : '') . '</tr>';
}
$amt = (float) ($row->item_amount ?? 0);
echo '<tr>';
echo '<td class="center">' . $srNo . '</td>';
echo '<td>' . esc(trim((string) (($row->item_name ?? '') . ' ' . ($row->comment ?? '')))) . '</td>';
echo '<td class="center">' . esc((string) ($row->org_code ?? '')) . '</td>';
echo '<td class="center">' . esc((string) ($row->item_qty ?? '')) . '</td>';
echo '<td class="right">' . esc(number_format((float) ($row->item_rate ?? 0), 2)) . '</td>';
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
if ($mode3ShowAmountAfterDiscount) {
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
}
echo '</tr>';
$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 '<tr><td></td><td class="group">' . esc($headDesc) . '</td><td></td><td></td><td></td><td></td>' . ($mode3ShowAmountAfterDiscount ? '<td></td>' : '') . '</tr>';
}
$amt = (float) ($row->amount ?? 0);
echo '<tr>';
echo '<td class="center">' . $srNo . '</td>';
echo '<td>' . esc((string) ($row->idesc ?? '')) . '</td>';
echo '<td class="center">' . esc((string) ($row->orgcode ?? '')) . '</td>';
echo '<td class="center">' . esc((string) ($row->no_qty ?? '')) . '</td>';
echo '<td class="right">' . esc(number_format((float) ($row->item_rate ?? 0), 2)) . '</td>';
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
if ($mode3ShowAmountAfterDiscount) {
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
}
echo '</tr>';
$srNo++;
}
foreach ($medicalItems as $row) {
$amt = (float) ($row->net_amount ?? 0);
echo '<tr>';
echo '<td class="center">' . $srNo . '</td>';
echo '<td>' . esc((string) ($row->inv_med_code ?? 'Medicine')) . '</td>';
echo '<td></td><td></td><td></td>';
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
if ($mode3ShowAmountAfterDiscount) {
echo '<td class="right">' . esc(number_format($amt, 2)) . '</td>';
}
echo '</tr>';
$srNo++;
}
?>
<tr class="totalline">
<td></td>
<td colspan="<?= $mode3ShowAmountAfterDiscount ? '4' : '3' ?>" class="right">Gross Total</td>
<td class="right"><?= esc(number_format($grossAmount, 2)) ?></td>
<?php if ($mode3ShowAmountAfterDiscount) : ?><td class="right"></td><?php endif; ?>
</tr>
<tr class="totalline">
<td></td>
<td colspan="<?= $mode3ShowAmountAfterDiscount ? '4' : '3' ?>" class="right">Net Amount</td>
<td class="right"><?= esc(number_format($netAmount, 2)) ?></td>
<?php if ($mode3ShowAmountAfterDiscount) : ?><td class="right"></td><?php endif; ?>
</tr>
<?php if ($showPaymentDetails) : ?>
<tr>
<td></td>
<td colspan="<?= $mode3ShowAmountAfterDiscount ? '4' : '3' ?>" class="totalline">Payment Recd.</td>
<td class="right"><?= esc(number_format($paidAmount, 2)) ?></td>
<?php if ($mode3ShowAmountAfterDiscount) : ?><td></td><?php endif; ?>
</tr>
<tr class="totalline">
<td></td>
<td colspan="<?= $mode3ShowAmountAfterDiscount ? '4' : '3' ?>" class="right">Balance</td>
<td class="right"><?= esc(number_format($balanceAmount, 2)) ?></td>
<?php if ($mode3ShowAmountAfterDiscount) : ?><td></td><?php endif; ?>
</tr>
<?php endif; ?>
</tbody>
</table>
<table class="footer-sign" cellspacing="0" cellpadding="0">
<tr>
<td style="text-align:left;">Patient / Relative Signature</td>
<td style="text-align:right;">Authorized Signatory</td>
</tr>
</table>
</body>
</html>

View File

@ -72,34 +72,49 @@
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<script>
$(document).ready(function() {
var start = moment();
var end = moment();
var dateFilterReady = (typeof window.moment === 'function') && $.fn && (typeof $.fn.daterangepicker === 'function');
function cb(startDate, endDate) {
$('#reportrange span').html(startDate.format('D-MM-YYYY') + ' - ' + endDate.format('D-MM-YYYY'));
$('#ipd_date_range').val(startDate.format('YYYY-MM-DD') + 'S' + endDate.format('YYYY-MM-DD'));
function fallbackRangeToday() {
var iso = '<?= date('Y-m-d') ?>';
var display = '<?= date('d-m-Y') ?>';
$('#reportrange span').text(display + ' - ' + display);
$('#ipd_date_range').val(iso + 'S' + iso);
}
$('#reportrange').daterangepicker({
startDate: start,
endDate: end,
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
if (dateFilterReady) {
var start = moment();
var end = moment();
function cb(startDate, endDate) {
$('#reportrange span').html(startDate.format('D-MM-YYYY') + ' - ' + endDate.format('D-MM-YYYY'));
$('#ipd_date_range').val(startDate.format('YYYY-MM-DD') + 'S' + endDate.format('YYYY-MM-DD'));
}
}, cb);
cb(start, end);
$('#reportrange').daterangepicker({
startDate: start,
endDate: end,
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
}, cb);
$('#reportrange').on('apply.daterangepicker', function(ev, picker) {
var dateFirst = picker.startDate.format('YYYY-MM-DD');
var dateSecond = picker.endDate.format('YYYY-MM-DD');
$('#ipd_date_range').val(dateFirst + 'S' + dateSecond);
});
cb(start, end);
$('#reportrange').on('apply.daterangepicker', function(ev, picker) {
var dateFirst = picker.startDate.format('YYYY-MM-DD');
var dateSecond = picker.endDate.format('YYYY-MM-DD');
$('#ipd_date_range').val(dateFirst + 'S' + dateSecond);
});
} else {
$('#reportrange').addClass('text-muted');
$('#reportrange span').text('Date filter fallback mode (moment blocked by browser/privacy settings).');
fallbackRangeToday();
}
$('#showreport1').click(function() {
var range = $('#ipd_date_range').val();

View File

@ -59,6 +59,7 @@
<th>Registration</th>
<th>Discharge</th>
<th>Dis. Status</th>
<th>Action</th>
</tr>
</thead>
<thead>
@ -73,6 +74,7 @@
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</thead>
<tbody></tbody>
@ -92,32 +94,60 @@
return;
}
var start = moment();
var end = moment();
var dateFilterReady = (typeof window.moment === 'function') && $.fn && (typeof $.fn.daterangepicker === 'function');
var start = null;
var end = null;
function cb(startDate, endDate) {
$('#reportrange span').html(startDate.format('MMMM D, YYYY') + ' - ' + endDate.format('MMMM D, YYYY'));
function fmtYmd(dateValue) {
if (!dateValue) {
return '';
}
if (typeof dateValue.format === 'function') {
return dateValue.format('YYYY-MM-DD');
}
var txt = String(dateValue || '').trim();
if (/^\d{4}-\d{2}-\d{2}/.test(txt)) {
return txt.substring(0, 10);
}
return '';
}
$('#reportrange').daterangepicker({
startDate: start,
endDate: end,
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
}, cb);
if (dateFilterReady) {
start = moment();
end = moment();
cb(start, end);
function cb(startDate, endDate) {
$('#reportrange span').html(startDate.format('MMMM D, YYYY') + ' - ' + endDate.format('MMMM D, YYYY'));
}
$('#reportrange').daterangepicker({
startDate: start,
endDate: end,
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
}, cb);
cb(start, end);
} else {
$('#chk_date').prop('checked', false).prop('disabled', true);
$('#reportrange').addClass('text-muted');
$('#reportrange span').text('Date filter unavailable (moment blocked by browser/privacy settings).');
}
var dataTable = $('#ipd-invoice-grid').DataTable({
order: [[0, 'desc']],
processing: true,
serverSide: true,
columnDefs: [
{ targets: [10], orderable: false, searchable: false }
],
ajax: {
url: "<?= base_url('billing/ipd/list') ?>",
type: 'post',
@ -134,10 +164,13 @@
$('#ipd-invoice-grid_filter').css('display', 'none');
$('#chk_date').on('click', function() {
if (!dateFilterReady) {
return;
}
if (this.checked) {
var choAdmitType = $('#ipd_admit_type').val();
var dateFirst = start.format('YYYY-MM-DD');
var dateSecond = end.format('YYYY-MM-DD');
var dateFirst = fmtYmd(start);
var dateSecond = fmtYmd(end);
dataTable.columns(7).search(dateFirst + '/' + dateSecond + '/' + choAdmitType).draw();
} else {
dataTable.columns(7).search('').draw();
@ -145,10 +178,13 @@
});
$('#ipd_admit_type').change(function() {
if (!dateFilterReady) {
return;
}
if ($('#chk_date').is(':checked')) {
var choAdmitType = $('#ipd_admit_type').val();
var dateFirst = start.format('YYYY-MM-DD');
var dateSecond = end.format('YYYY-MM-DD');
var dateFirst = fmtYmd(start);
var dateSecond = fmtYmd(end);
dataTable.columns(7).search(dateFirst + '/' + dateSecond + '/' + choAdmitType).draw();
}
});
@ -160,6 +196,9 @@
});
$('#reportrange').on('apply.daterangepicker', function(ev, picker) {
if (!dateFilterReady) {
return;
}
var dateFirst = picker.startDate.format('YYYY-MM-DD');
var dateSecond = picker.endDate.format('YYYY-MM-DD');
start = picker.startDate;

View File

@ -91,11 +91,11 @@ if ($person) {
<strong>/ Age :</strong> <?= esc($age) ?>
<strong>/ Gender :</strong> <?= esc($person->xgender ?? '') ?>
<strong>/ UHID :</strong> <?= esc($person->p_code ?? '') ?>
<strong>/ No of Days :</strong> <?= esc($ipd->no_days ?? '') ?>
<strong>/ No of Days :</strong> <span id="ipd-panel-no-days"><?= esc($ipd->no_days ?? '') ?></span>
</p>
<p class="mb-0">
<strong>Admit Date :</strong> <?= esc($ipd->str_register_date ?? '') ?>
<strong>/ Discharge Date :</strong> <?= esc($ipd->str_discharge_date ?? '') ?>
<strong>Admit Date :</strong> <span id="ipd-panel-admit-date"><?= esc($ipd->str_register_date ?? '') ?></span>
<strong>/ Discharge Date :</strong> <span id="ipd-panel-discharge-date"><?= esc($ipd->str_discharge_date ?? '') ?></span>
</p>
</div>
@ -284,6 +284,42 @@ if ($person) {
loadTabContent(firstTab);
}
if (window.jQuery) {
$(document).on('ipd:admission-updated', function(event, detail) {
detail = detail || {};
var admitDate = document.getElementById('ipd-panel-admit-date');
var dischargeDate = document.getElementById('ipd-panel-discharge-date');
var noDays = document.getElementById('ipd-panel-no-days');
if (admitDate && typeof detail.admit_date === 'string') {
admitDate.textContent = detail.admit_date;
}
if (dischargeDate && typeof detail.discharge_date === 'string') {
dischargeDate.textContent = detail.discharge_date;
}
if (noDays && typeof detail.no_days === 'string') {
noDays.textContent = detail.no_days;
}
});
} else {
document.addEventListener('ipd:admission-updated', function(event) {
var detail = event.detail || {};
var admitDate = document.getElementById('ipd-panel-admit-date');
var dischargeDate = document.getElementById('ipd-panel-discharge-date');
var noDays = document.getElementById('ipd-panel-no-days');
if (admitDate && typeof detail.admit_date === 'string') {
admitDate.textContent = detail.admit_date;
}
if (dischargeDate && typeof detail.discharge_date === 'string') {
dischargeDate.textContent = detail.discharge_date;
}
if (noDays && typeof detail.no_days === 'string') {
noDays.textContent = detail.no_days;
}
});
}
var payModal = document.getElementById('payModal');
if (payModal) {
payModal.addEventListener('shown.bs.modal', function(event) {

View File

@ -1,14 +1,59 @@
<?php
$ipd = $ipd_info ?? null;
$person = $person_info ?? null;
$admissionDoctors = $admission_doctors ?? [];
$availableDoctors = $available_doctors ?? [];
$selectedDoctorIds = $selected_doctor_ids ?? [];
$departments = $departments ?? [];
$referMaster = $refer_master ?? [];
$registerDateInput = $register_date_input ?? date('Y-m-d');
$regTimeInput = $reg_time_input ?? date('H:i');
$age = '';
if ($person) {
$age = get_age_1($person->dob ?? null, $person->age ?? '', $person->age_in_month ?? '', $person->estimate_dob ?? '');
}
$departmentName = 'Not set';
foreach ($departments as $department) {
if ((int) ($department->iId ?? 0) === (int) ($ipd->dept_id ?? 0)) {
$departmentName = (string) ($department->vName ?? 'Not set');
break;
}
}
$referByName = 'Not set';
foreach ($referMaster as $refer) {
if ((int) ($refer->id ?? 0) === (int) ($ipd->refer_by ?? 0)) {
$referByName = trim((string) (($refer->title ?? '') . ' ' . ($refer->f_name ?? '')));
if ($referByName === '') {
$referByName = 'Not set';
}
break;
}
}
$doctorSummary = [];
foreach ($admissionDoctors as $doctor) {
$doctorName = trim((string) ($doctor->doctor_name ?? ''));
if ($doctorName !== '') {
$doctorSummary[] = $doctorName;
}
}
$admissionMode = (int) ($ipd->case_type ?? 0) === 1 ? 'MLC' : 'NON MLC';
$contactPersonName = (string) ($ipd->contact_person_Name ?? '');
$contactRelation = (string) ($ipd->relation ?? '');
$contactMobile1 = (string) ($ipd->P_mobile1 ?? '');
$contactMobile2 = (string) ($ipd->P_mobile2 ?? '');
$problemText = (string) ($ipd->problem ?? '');
$remarkText = (string) ($ipd->remark ?? '');
$canEditAdmission = (bool) ($can_edit_admission ?? false);
$formId = 'ipd-admission-form-' . (int) ($ipd->id ?? 0);
$alertId = 'ipd-admission-alert-' . (int) ($ipd->id ?? 0);
?>
<div class="row g-3">
<div class="col-md-6">
<div class="card">
<div class="row g-3" id="ipd-admission-wrap-<?= (int) ($ipd->id ?? 0) ?>">
<div class="col-xl-4 col-md-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Patient</h5>
<p class="mb-1"><strong>Name :</strong> <?= esc($person->p_fname ?? '') ?> {<?= esc($person->p_rname ?? '') ?>}</p>
@ -19,16 +64,225 @@ if ($person) {
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="col-xl-4 col-md-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Admission</h5>
<p class="mb-1"><strong>IPD Code :</strong> <?= esc($ipd->ipd_code ?? '') ?></p>
<p class="mb-1"><strong>Admit Date :</strong> <?= esc($ipd->str_register_date ?? '') ?></p>
<p class="mb-1"><strong>Admit Time :</strong> <?= esc($regTimeInput) ?></p>
<p class="mb-1"><strong>Discharge Date :</strong> <?= esc($ipd->str_discharge_date ?? '') ?></p>
<p class="mb-1"><strong>No. of Days :</strong> <?= esc($ipd->no_days ?? '') ?></p>
<p class="mb-0"><strong>Insurance :</strong> <?= esc($ipd->ins_company_name ?? 'Direct') ?></p>
</div>
</div>
</div>
<div class="col-xl-4 col-md-12">
<div class="card h-100 border-primary-subtle">
<div class="card-body">
<h5 class="card-title">Editable Details</h5>
<p class="mb-1"><strong>Mode :</strong> <?= esc($admissionMode) ?></p>
<p class="mb-1"><strong>Department :</strong> <?= esc($departmentName) ?></p>
<p class="mb-1"><strong>Refer By :</strong> <?= esc($referByName) ?></p>
<?php if ($contactPersonName !== '') : ?>
<p class="mb-1"><strong>Contact Person :</strong> <?= esc($contactPersonName) ?><?= $contactRelation !== '' ? ' (' . esc($contactRelation) . ')' : '' ?></p>
<?php if ($contactMobile1 !== '') : ?><p class="mb-1"><strong>Contact Mobile :</strong> <?= esc($contactMobile1) ?><?= $contactMobile2 !== '' ? ' / ' . esc($contactMobile2) : '' ?></p><?php endif; ?>
<?php endif; ?>
<?php if ($problemText !== '') : ?>
<p class="mb-1"><strong>Problem :</strong> <?= esc($problemText) ?></p>
<?php endif; ?>
<p class="mb-2"><strong>Assigned Doctors :</strong></p>
<?php if (! empty($doctorSummary)) : ?>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($doctorSummary as $doctorName) : ?>
<span class="badge bg-primary-subtle text-primary border border-primary-subtle"><?= esc($doctorName) ?></span>
<?php endforeach; ?>
</div>
<?php else : ?>
<p class="mb-0 text-muted">No doctor assigned.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php if ($canEditAdmission) : ?>
<div class="card mt-3 border-warning-subtle">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<h5 class="card-title mb-1">Edit Admission Info</h5>
<p class="text-muted mb-0">Update the admission timing, mode, department, referral source, and assigned doctors from this tab.</p>
</div>
</div>
<div id="<?= esc($alertId) ?>"></div>
<form id="<?= esc($formId) ?>" action="<?= site_url('billing/ipd/panel/' . (int) ($ipd->id ?? 0) . '/admission') ?>" method="post">
<div class="row g-3">
<div class="col-lg-3 col-md-6">
<label class="form-label">Admission Date</label>
<input type="date" name="register_date" class="form-control" value="<?= esc($registerDateInput) ?>" required>
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">Admission Time</label>
<input type="time" name="reg_time" class="form-control" value="<?= esc($regTimeInput) ?>" step="60" required>
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label d-block">Admission Mode</label>
<div class="form-check form-check-inline mt-2">
<input class="form-check-input" type="radio" name="case_type" id="case_type_non_mlc_<?= (int) ($ipd->id ?? 0) ?>" value="0" <?= (int) ($ipd->case_type ?? 0) !== 1 ? 'checked' : '' ?>>
<label class="form-check-label" for="case_type_non_mlc_<?= (int) ($ipd->id ?? 0) ?>">NON MLC</label>
</div>
<div class="form-check form-check-inline mt-2">
<input class="form-check-input" type="radio" name="case_type" id="case_type_mlc_<?= (int) ($ipd->id ?? 0) ?>" value="1" <?= (int) ($ipd->case_type ?? 0) === 1 ? 'checked' : '' ?>>
<label class="form-check-label" for="case_type_mlc_<?= (int) ($ipd->id ?? 0) ?>">MLC</label>
</div>
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label">Department</label>
<select name="dept_id" class="form-select">
<option value="0">Select Department</option>
<?php foreach ($departments as $department) : ?>
<option value="<?= (int) ($department->iId ?? 0) ?>" <?= (int) ($department->iId ?? 0) === (int) ($ipd->dept_id ?? 0) ? 'selected' : '' ?>><?= esc($department->vName ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-lg-6 col-md-12">
<label class="form-label">Refer By</label>
<select name="refer_by" class="form-select">
<option value="0">Select Refer By</option>
<?php foreach ($referMaster as $refer) : ?>
<option value="<?= (int) ($refer->id ?? 0) ?>" <?= (int) ($refer->id ?? 0) === (int) ($ipd->refer_by ?? 0) ? 'selected' : '' ?>><?= esc(trim((string) (($refer->title ?? '') . ' ' . ($refer->f_name ?? '')))) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12">
<label class="form-label">Assigned Doctors</label>
<div class="border rounded p-3 bg-light-subtle" style="max-height: 240px; overflow-y: auto;">
<div class="row g-2">
<?php foreach ($availableDoctors as $doctor) : ?>
<div class="col-xl-4 col-lg-6 col-md-6">
<label class="form-check d-flex gap-2 align-items-start">
<input class="form-check-input mt-1" type="checkbox" name="doc_id[]" value="<?= (int) ($doctor->id ?? 0) ?>" <?= in_array((int) ($doctor->id ?? 0), $selectedDoctorIds, true) ? 'checked' : '' ?>>
<span class="form-check-label small"><?= esc($doctor->DocSpecName ?? $doctor->p_fname ?? '') ?></span>
</label>
</div>
<?php endforeach; ?>
<?php if (empty($availableDoctors)) : ?>
<div class="col-12 text-muted small">No active doctors available.</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-12"><hr class="my-1"><h6 class="text-muted mb-2">Contact Person</h6></div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Contact Name</label>
<input type="text" name="contact_person_Name" class="form-control" maxlength="50" value="<?= esc($contactPersonName) ?>" placeholder="Name of contact person">
</div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Relation</label>
<input type="text" name="relation" class="form-control" maxlength="50" value="<?= esc($contactRelation) ?>" placeholder="e.g. Son, Wife">
</div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Mobile 1</label>
<input type="text" name="P_mobile1" class="form-control" maxlength="50" value="<?= esc($contactMobile1) ?>" placeholder="Primary contact number">
</div>
<div class="col-lg-4 col-md-6">
<label class="form-label">Mobile 2</label>
<input type="text" name="P_mobile2" class="form-control" maxlength="50" value="<?= esc($contactMobile2) ?>" placeholder="Secondary contact number">
</div>
<div class="col-12"><hr class="my-1"><h6 class="text-muted mb-2">Clinical Notes</h6></div>
<div class="col-lg-6 col-md-12">
<label class="form-label">Problem / Chief Complaint</label>
<input type="text" name="problem" class="form-control" maxlength="200" value="<?= esc($problemText) ?>" placeholder="Brief description of presenting problem">
</div>
<div class="col-lg-6 col-md-12">
<label class="form-label">Remark</label>
<textarea name="remark" class="form-control" rows="2" placeholder="Additional remarks"><?= esc($remarkText) ?></textarea>
</div>
<div class="col-12 d-flex flex-wrap gap-2 justify-content-end">
<button type="submit" class="btn btn-primary">Save Admission Changes</button>
<button type="button" class="btn btn-outline-secondary" onclick="if (typeof load_form_div === 'function') { load_form_div('<?= site_url('billing/ipd/panel/' . (int) ($ipd->id ?? 0) . '/tab/admission') ?>', 'tab_admission_content'); }">Reset</button>
</div>
</div>
</form>
</div>
</div>
<script>
(function() {
var form = document.getElementById('<?= esc($formId) ?>');
var alertBox = document.getElementById('<?= esc($alertId) ?>');
if (!form || !window.jQuery) {
return;
}
function renderAlert(type, message) {
if (!alertBox) {
return;
}
alertBox.innerHTML = '<div class="alert alert-' + type + ' mb-3">' + message + '</div>';
}
function setSavingState(isSaving) {
var submitButton = form.querySelector('button[type="submit"]');
if (!submitButton) {
return;
}
submitButton.disabled = isSaving;
submitButton.textContent = isSaving ? 'Saving...' : 'Save Admission Changes';
}
form.addEventListener('submit', function(event) {
event.preventDefault();
setSavingState(true);
if (alertBox) {
alertBox.innerHTML = '';
}
$.ajax({
url: form.getAttribute('action'),
type: 'POST',
dataType: 'json',
data: $(form).serialize()
}).done(function(response) {
if (!response || Number(response.update || 0) !== 1) {
renderAlert('danger', response && response.message ? response.message : 'Unable to update admission details.');
return;
}
if (response.html) {
$('#tab_admission_content').html(response.html);
}
if (response.header) {
$(document).trigger('ipd:admission-updated', [response.header]);
}
if (typeof window.notify === 'function') {
window.notify('success', 'Admission Updated', response.message || 'Admission details updated.');
}
}).fail(function(xhr) {
var message = 'Unable to update admission details.';
if (xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
renderAlert('danger', message);
}).always(function() {
setSavingState(false);
});
});
})();
</script>
<?php else : ?>
<div class="alert alert-secondary mt-3 mb-0 d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock-fill flex-shrink-0" viewBox="0 0 16 16"><path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/></svg>
You do not have permission to edit admission details. Contact your administrator to request the <strong>billing.ipd.admission.edit</strong> permission.
</div>
<?php endif; ?>

View File

@ -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';
?>
<div class="card border-top border-3 border-danger">
<div class="card-header">
<strong>IPD Invoice</strong>
<span class="text-muted">/ IPD ID: <?= esc($ipd->ipd_code ?? '') ?></span>
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
<div>
<strong><?= esc($billHeaderTitle) ?></strong>
<span class="text-muted">/ IPD ID: <?= esc($ipd->ipd_code ?? '') ?></span>
</div>
<?php if ($showPrintActions && $canPrintBill && ! empty($ipd->id)) : ?>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-sm btn-outline-primary" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/1') ?>">Print Bill</a>
<a class="btn btn-sm btn-outline-secondary" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/2') ?>">Print Without Payment</a>
<a class="btn btn-sm btn-outline-dark" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/5') ?>">Letter Head w/o Payment</a>
<a class="btn btn-sm btn-outline-dark" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/6') ?>">Letter Head</a>
<a class="btn btn-sm btn-outline-info" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/3') ?>">Item Amt. With Discount</a>
<a class="btn btn-sm btn-outline-success" target="_blank" href="<?= site_url('billing/ipd/bill-print/' . (int) $ipd->id . '/4') ?>">TPA Final Bill</a>
</div>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<p class="mb-2">
@ -247,25 +266,27 @@ if ($balanceAmount <= 0) {
<th>Net Amount</th>
<th class="text-end"><?= esc(number_format($netAmount, 2)) ?></th>
</tr>
<tr>
<th>#</th>
<th colspan="2">
Payment Recd.<br>
<?php
$i = 1;
foreach ($ipd_payment ?? [] as $row) {
$i++;
echo '[' . esc($row->id ?? '') . ':' . esc($row->pay_mode ?? '') . ':' . esc($row->pay_date_str ?? '') . ':' . esc($row->amount ?? '') . '] / ';
if ($i % 3 === 0) {
echo '<br>';
<?php if ($showPaymentDetails) : ?>
<tr>
<th>#</th>
<th colspan="2">
Payment Recd.<br>
<?php
$i = 1;
foreach ($ipd_payment ?? [] as $row) {
$i++;
echo '[' . esc($row->id ?? '') . ':' . esc($row->pay_mode ?? '') . ':' . esc($row->pay_date_str ?? '') . ':' . esc($row->amount ?? '') . '] / ';
if ($i % 3 === 0) {
echo '<br>';
}
}
}
?>
</th>
<th></th>
<th></th>
<th class="text-end"><?= esc(number_format($paidTotal > 0 ? $paidTotal : (float) ($ipd->total_paid_amount ?? 0), 2)) ?></th>
</tr>
?>
</th>
<th></th>
<th></th>
<th class="text-end"><?= esc(number_format($paidTotal > 0 ? $paidTotal : (float) ($ipd->total_paid_amount ?? 0), 2)) ?></th>
</tr>
<?php endif; ?>
<tr>
<th>#</th>
<th></th>

View File

@ -1,26 +1,237 @@
<?php
$discharge = $discharge_info[0] ?? null;
$ipd = $ipd_info ?? null;
$canEditDischarge = (bool) ($can_edit_discharge ?? false);
$ipdId = (int) ($ipd->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);
}
?>
<div class="row g-3">
<div class="col-md-6">
<div class="card">
<div class="col-lg-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Discharge Status</h5>
<p><strong>Status :</strong> <?= esc($discharge->status_desc ?? 'Pending') ?></p>
<p><strong>Discharge Date :</strong> <?= esc($discharge->discharge_date ?? '') ?></p>
<p><strong>Discharge Time :</strong> <?= esc($discharge->discharge_time ?? '') ?></p>
<p><strong>Discharge By :</strong> <?= esc($discharge->discharge_by ?? '') ?></p>
<h5 class="card-title">Current Discharge Snapshot</h5>
<p class="mb-1"><strong>Status :</strong> <?= esc($discharge->status_desc ?? ($statusMap[$statusValue] ?? 'Status Pending')) ?></p>
<p class="mb-1"><strong>Discharge Date :</strong> <?= esc((string) ($discharge->discharge_date ?? '')) ?></p>
<p class="mb-1"><strong>Discharge Time :</strong> <?= esc((string) ($discharge->discharge_time ?? '')) ?></p>
<p class="mb-1"><strong>Discharge By :</strong> <?= esc((string) ($discharge->discharge_by ?? '')) ?></p>
<p class="mb-1"><strong>Balance Approved By :</strong> <?= esc((string) ($discharge->discharge_balance_user ?? '')) ?></p>
<p class="mb-0"><strong>Balance Remark :</strong> <?= esc((string) ($discharge->discharge_balance_remark ?? '')) ?></p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="col-lg-6">
<div class="card h-100 border-primary-subtle">
<div class="card-body">
<h5 class="card-title">Remarks</h5>
<p><strong>Remark :</strong> <?= esc($discharge->discharge_remark ?? '') ?></p>
<p><strong>Balance User :</strong> <?= esc($discharge->discharge_balance_user ?? '') ?></p>
<p><strong>Balance Remark :</strong> <?= esc($discharge->discharge_balance_remark ?? '') ?></p>
<h5 class="card-title">Current Financial Adjustments</h5>
<p class="mb-1"><strong>Deduction 1 :</strong> <?= esc(number_format((float) ($ipd->Discount ?? 0), 2)) ?> | <?= esc((string) ($ipd->Discount_Remark ?? '')) ?></p>
<p class="mb-1"><strong>Deduction 2 :</strong> <?= esc(number_format((float) ($ipd->Discount2 ?? 0), 2)) ?> | <?= esc((string) ($ipd->Discount_Remark2 ?? '')) ?></p>
<p class="mb-1"><strong>Deduction 3 :</strong> <?= esc(number_format((float) ($ipd->Discount3 ?? 0), 2)) ?> | <?= esc((string) ($ipd->Discount_Remark3 ?? '')) ?></p>
<hr>
<p class="mb-1"><strong>Additional Charge 1 :</strong> <?= esc(number_format((float) ($ipd->chargeamount1 ?? 0), 2)) ?> | <?= esc((string) ($ipd->charge1 ?? '')) ?></p>
<p class="mb-0"><strong>Additional Charge 2 :</strong> <?= esc(number_format((float) ($ipd->chargeamount2 ?? 0), 2)) ?> | <?= esc((string) ($ipd->charge2 ?? '')) ?></p>
</div>
</div>
</div>
</div>
<?php if ($canEditDischarge) : ?>
<div class="card mt-3 border-warning-subtle">
<div class="card-body">
<h5 class="card-title mb-3">Discharge Process Controls</h5>
<div class="alert alert-info py-2 px-3 mb-3">Update deductions/additional charges first, then confirm discharge status/date/time.</div>
<div class="row g-3">
<?php for ($slot = 1; $slot <= 3; $slot++) : ?>
<?php $amountField = $slot === 1 ? 'Discount' : 'Discount' . $slot; ?>
<?php $remarkField = $slot === 1 ? 'Discount_Remark' : 'Discount_Remark' . $slot; ?>
<div class="col-12">
<div class="border rounded p-3 bg-light-subtle">
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label class="form-label">Deduction Remark <?= $slot ?></label>
<input type="text" class="form-control" id="dis_remark_<?= $slot ?>" value="<?= esc((string) ($ipd->{$remarkField} ?? '')) ?>" placeholder="Deduction remark">
</div>
<div class="col-md-3">
<label class="form-label">Deduction Amount <?= $slot ?></label>
<input type="number" step="0.01" min="0" class="form-control" id="dis_amount_<?= $slot ?>" value="<?= esc((string) number_format((float) ($ipd->{$amountField} ?? 0), 2, '.', '')) ?>" placeholder="0.00">
</div>
<div class="col-md-4">
<button type="button" class="btn btn-primary w-100 js-update-discount" data-slot="<?= $slot ?>">Update Deduction <?= $slot ?></button>
</div>
</div>
</div>
</div>
<?php endfor; ?>
<?php for ($slot = 1; $slot <= 2; $slot++) : ?>
<?php $amountField = 'chargeamount' . $slot; ?>
<?php $remarkField = 'charge' . $slot; ?>
<div class="col-12">
<div class="border rounded p-3 bg-light-subtle">
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label class="form-label">Additional Charge Remark <?= $slot ?></label>
<input type="text" class="form-control" id="charge_remark_<?= $slot ?>" value="<?= esc((string) ($ipd->{$remarkField} ?? '')) ?>" placeholder="Charge remark">
</div>
<div class="col-md-3">
<label class="form-label">Additional Charge Amount <?= $slot ?></label>
<input type="number" step="0.01" min="0" class="form-control" id="charge_amount_<?= $slot ?>" value="<?= esc((string) number_format((float) ($ipd->{$amountField} ?? 0), 2, '.', '')) ?>" placeholder="0.00">
</div>
<div class="col-md-4">
<button type="button" class="btn btn-primary w-100 js-update-charge" data-slot="<?= $slot ?>">Update Additional Charge <?= $slot ?></button>
</div>
</div>
</div>
</div>
<?php endfor; ?>
</div>
<hr class="my-4">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Discharge Date</label>
<input type="date" class="form-control" id="discharge_date" value="<?= esc($dischargeDate) ?>">
</div>
<div class="col-md-3">
<label class="form-label">Discharge Time</label>
<input type="time" class="form-control" id="discharge_time" value="<?= esc($dischargeTime) ?>" step="60">
</div>
<div class="col-md-3">
<label class="form-label">Patient Status</label>
<select class="form-select" id="discharge_status">
<?php foreach ($statusMap as $statusId => $statusLabel) : ?>
<option value="<?= $statusId ?>" <?= $statusId === $statusValue ? 'selected' : '' ?>><?= esc($statusLabel) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Balance Approved By</label>
<input type="text" class="form-control" id="balance_user" value="<?= esc((string) ($discharge->discharge_balance_user ?? '')) ?>" placeholder="Full name">
</div>
<div class="col-md-9">
<label class="form-label">Discharge / Balance Remark</label>
<input type="text" class="form-control" id="discharge_remark" value="<?= esc((string) ($discharge->discharge_remark ?? '')) ?>" placeholder="Discharge remark">
</div>
<div class="col-md-3">
<label class="form-label">Balance Remark</label>
<input type="text" class="form-control" id="balance_remark" value="<?= esc((string) ($discharge->discharge_balance_remark ?? '')) ?>" placeholder="Balance remark">
</div>
<div class="col-12 d-flex justify-content-end">
<button type="button" id="btn-update-discharge-process" class="btn btn-danger">Update Discharge Status</button>
</div>
</div>
</div>
</div>
<script>
(function() {
if (!window.jQuery) {
return;
}
var ipdId = <?= $ipdId ?>;
function refreshDischargeTab(response) {
if (response && Number(response.update || 0) === 1 && response.html) {
$('#tab_discharge_content').html(response.html);
if (typeof window.notify === 'function') {
window.notify('success', 'Discharge Process', response.message || 'Updated successfully');
}
} else if (response && response.message) {
if (typeof window.notify === 'function') {
window.notify('danger', 'Discharge Process', response.message);
} else {
alert(response.message);
}
}
}
function post(url, payload) {
return $.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: payload
});
}
$(document).on('click', '#tab_discharge_content .js-update-discount', function() {
var slot = Number($(this).data('slot') || 0);
if (slot < 1 || slot > 3) {
return;
}
post('<?= site_url('billing/ipd/panel/' . $ipdId . '/discharge/discount') ?>/' + slot, {
amount: $('#dis_amount_' + slot).val(),
remark: $('#dis_remark_' + slot).val()
}).done(refreshDischargeTab).fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Unable to update discount';
alert(msg);
});
});
$(document).on('click', '#tab_discharge_content .js-update-charge', function() {
var slot = Number($(this).data('slot') || 0);
if (slot < 1 || slot > 2) {
return;
}
post('<?= site_url('billing/ipd/panel/' . $ipdId . '/discharge/charge') ?>/' + slot, {
amount: $('#charge_amount_' + slot).val(),
remark: $('#charge_remark_' + slot).val()
}).done(refreshDischargeTab).fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Unable to update additional charge';
alert(msg);
});
});
$(document).on('click', '#tab_discharge_content #btn-update-discharge-process', function() {
var status = Number($('#discharge_status').val() || 0);
var disTime = String($('#discharge_time').val() || '').trim();
if (status === 0) {
alert('Please select patient status.');
return;
}
if (disTime === '') {
alert('Please enter discharge time.');
return;
}
post('<?= site_url('billing/ipd/panel/' . $ipdId . '/discharge/update') ?>', {
discharge_status: status,
discharge_date: $('#discharge_date').val(),
discharge_time: disTime,
discharge_remark: $('#discharge_remark').val(),
balance_user: $('#balance_user').val(),
balance_remark: $('#balance_remark').val()
}).done(refreshDischargeTab).fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Unable to update discharge status';
alert(msg);
});
});
})();
</script>
<?php else : ?>
<div class="alert alert-secondary mt-3 mb-0">
You do not have permission to edit discharge process details. Contact your administrator for billing.ipd.discharge.edit.
</div>
<?php endif; ?>