Add report panel updates and related billing/discharge enhancements
This commit is contained in:
parent
2127063c7f
commit
aa845ac5ff
|
|
@ -102,6 +102,7 @@ class AuthGroups extends ShieldAuthGroups
|
|||
'template.ct' => 'Can access CT templates',
|
||||
'template.mri' => 'Can access MRI templates',
|
||||
'template.echo' => 'Can access ECHO templates',
|
||||
'template.discharge' => 'Can access IPD discharge templates',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -233,6 +233,15 @@ $routes->match(['get', 'post'], 'Ipd_discharge/ipd_select/(:num)/(:num)', 'Ipd_d
|
|||
$routes->get('Ipd_discharge/section_past_data', 'Ipd_discharge::section_past_data');
|
||||
$routes->get('Ipd_discharge/section_template_list', 'Ipd_discharge::section_template_list');
|
||||
$routes->post('Ipd_discharge/section_template_save', 'Ipd_discharge::section_template_save');
|
||||
$routes->get('Ipd_discharge/surgery_master_lookup', 'Ipd_discharge::surgery_master_lookup');
|
||||
$routes->get('Ipd_discharge/surgery_master_list', 'Ipd_discharge::surgery_master_list');
|
||||
$routes->post('Ipd_discharge/surgery_master_save', 'Ipd_discharge::surgery_master_save');
|
||||
$routes->post('Ipd_discharge/surgery_master_delete', 'Ipd_discharge::surgery_master_delete');
|
||||
$routes->get('Ipd_discharge/diagnosis_icd_lookup', 'Ipd_discharge::diagnosis_icd_lookup');
|
||||
$routes->post('Ipd_discharge/diagnosis_icd_seed_starter', 'Ipd_discharge::diagnosis_icd_seed_starter');
|
||||
$routes->get('Ipd_discharge/dietary_master_list', 'Ipd_discharge::dietary_master_list');
|
||||
$routes->post('Ipd_discharge/dietary_master_save', 'Ipd_discharge::dietary_master_save');
|
||||
$routes->post('Ipd_discharge/dietary_master_delete', 'Ipd_discharge::dietary_master_delete');
|
||||
$routes->match(['get', 'post'], 'Ipd_discharge/preview_discharge_report/(:num)', 'Ipd_discharge::preview_discharge_report/$1');
|
||||
$routes->get('Ipd_discharge/show_discharge/(:num)', 'Ipd_discharge::show_discharge/$1');
|
||||
$routes->get('Ipd_discharge/show_discharge/(:num)/(:num)', 'Ipd_discharge::show_discharge/$1/$2');
|
||||
|
|
@ -521,12 +530,21 @@ $routes->post('Opdcase/showfee', 'Opdcase::showfee');
|
|||
$routes->post('Opdcase/confirm_opd', 'Opdcase::confirm_opd');
|
||||
$routes->get('Opdcase/invoice/(:num)', 'Opd::invoice/$1');
|
||||
|
||||
$routes->get('Report', 'Report::index');
|
||||
$routes->get('Report/index', 'Report::index');
|
||||
$routes->get('report', 'Report::index');
|
||||
$routes->get('report/index', 'Report::index');
|
||||
|
||||
$routes->get('Report/collection_report', 'Report::collection_report');
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)', 'Report::report_total_payment_app_show/$1/$2/$3');
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)/(:num)', 'Report::report_total_payment_app_show/$1/$2/$3/$4');
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)/(:segment)', 'Report::report_total_payment_app_show/$1/$2/$3/$4');
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)/(:segment)/(:num)', 'Report::report_total_payment_app_show/$1/$2/$3/$4/$5');
|
||||
|
||||
$routes->get('Report/billing_operations_report', 'Report::billing_operations_report');
|
||||
$routes->get('Report/billing_operations_report_data/(:segment)/(:segment)', 'Report::billing_operations_report_data/$1/$2');
|
||||
$routes->get('Report/billing_operations_report_data/(:segment)/(:segment)/(:num)', 'Report::billing_operations_report_data/$1/$2/$3');
|
||||
|
||||
$routes->get('Report/diagnosis_report', 'Report::diagnosis_report');
|
||||
$routes->get('Report/diagnosis_report_data/(:segment)', 'Report::diagnosis_report_data/$1');
|
||||
$routes->get('Report/diagnosis_report_data/(:segment)/(:segment)', 'Report::diagnosis_report_data/$1/$2');
|
||||
|
|
@ -549,6 +567,14 @@ $routes->get('Report/insurance_combined_report', 'Report::insurance_combined_rep
|
|||
$routes->get('Report/insurance_combined_report_data/(:segment)/(:segment)/(:segment)', 'Report::insurance_combined_report_data/$1/$2/$3');
|
||||
$routes->get('Report/insurance_combined_report_data/(:segment)/(:segment)/(:segment)/(:num)', 'Report::insurance_combined_report_data/$1/$2/$3/$4');
|
||||
|
||||
// Legacy Report4 compatibility for Insurance Case reports
|
||||
$routes->get('Report4/echs_ipd_list_main', 'Report::echs_ipd_list_main');
|
||||
$routes->get('Report4/echs_opd_list_main', 'Report::echs_opd_list_main');
|
||||
$routes->get('Report4/echs_ipd_list_data/(:segment)/(:segment)/(:segment)', 'Report::echs_ipd_list_data/$1/$2/$3');
|
||||
$routes->get('Report4/echs_ipd_list_data/(:segment)/(:segment)/(:segment)/(:num)', 'Report::echs_ipd_list_data/$1/$2/$3/$4');
|
||||
$routes->get('Report4/echs_opd_list_data/(:segment)/(:segment)/(:segment)', 'Report::echs_opd_list_data/$1/$2/$3');
|
||||
$routes->get('Report4/echs_opd_list_data/(:segment)/(:segment)/(:segment)/(:num)', 'Report::echs_opd_list_data/$1/$2/$3/$4');
|
||||
|
||||
// Organization Packing Routes
|
||||
$routes->get('org-packing', 'OrgPacking::index');
|
||||
$routes->post('org-packing/search', 'OrgPacking::search');
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ namespace App\Controllers\Billing;
|
|||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\InvoiceModel;
|
||||
use App\Models\ItemModel;
|
||||
use App\Models\OrganizationCaseBillDetailModel;
|
||||
use App\Models\OrganizationCaseModel;
|
||||
use Mpdf\Mpdf;
|
||||
|
||||
class CaseMaster extends BaseController
|
||||
{
|
||||
|
|
@ -118,8 +119,6 @@ class CaseMaster extends BaseController
|
|||
'input_insurance_id' => 'required|min_length[2]|max_length[30]',
|
||||
'input_card_holder_name' => 'required|min_length[2]|max_length[50]',
|
||||
];
|
||||
|
||||
$validation = service('validation');
|
||||
$validation->setRules($rules);
|
||||
if (! $validation->withRequest($this->request)->run()) {
|
||||
$errorText = implode("\n", $validation->getErrors());
|
||||
|
|
@ -157,7 +156,6 @@ class CaseMaster extends BaseController
|
|||
];
|
||||
|
||||
$existing = $this->db->table('organization_case_master')
|
||||
->where('status', 0)
|
||||
->where('case_type', $caseType)
|
||||
->where('p_id', $pId)
|
||||
->get()
|
||||
|
|
@ -484,7 +482,11 @@ class CaseMaster extends BaseController
|
|||
$data['payment_history'] = $this->db->query($sql)->getResult();
|
||||
|
||||
if ($print > 0) {
|
||||
return view('Invoice/caseinvoice_print_V', $data);
|
||||
$html = view('Invoice/caseinvoice_print_V', $data);
|
||||
$caseCode = (string) ($data['orgcase'][0]->case_id_code ?? $casecode);
|
||||
$fileName = 'Org_Invoice_' . preg_replace('/[^A-Za-z0-9\-_]/', '_', $caseCode) . '_' . date('YmdHis') . '.pdf';
|
||||
|
||||
return $this->renderInlinePdf($html, $fileName, 'Organization Invoice ' . $caseCode);
|
||||
}
|
||||
|
||||
return view('Invoice/caseinvoice_V', $data);
|
||||
|
|
@ -794,8 +796,10 @@ class CaseMaster extends BaseController
|
|||
->get()
|
||||
->getResult();
|
||||
|
||||
$data['showinvoice'] = $this->db->query('select * from v_case_invoice v where v.id=' . $caseid . ' order by s_Date')->getResult();
|
||||
$data['invoiceGtotal'] = $this->db->query('select sum(amount) as GTotal from v_case_invoice v where v.id=' . $caseid)->getResult();
|
||||
$organizationCaseModel = new OrganizationCaseModel();
|
||||
$invoiceData = $organizationCaseModel->getInvoiceRowsAndTotal($caseid);
|
||||
$data['showinvoice'] = $invoiceData['rows'];
|
||||
$data['invoiceGtotal'] = [(object) ['GTotal' => $invoiceData['total']]];
|
||||
|
||||
return view('Invoice/caseinvoice_V', $data);
|
||||
}
|
||||
|
|
@ -922,7 +926,13 @@ class CaseMaster extends BaseController
|
|||
->getResult();
|
||||
|
||||
if ($print === 1) {
|
||||
return $this->response->setBody((string) ($data['orgcase'][0]->contingent_bill ?? ''));
|
||||
$content = (string) ($data['orgcase'][0]->contingent_bill ?? '');
|
||||
$pageHead = '<style>@page {margin-top: 0.5cm;margin-bottom: 0.5cm;margin-left: 0.5cm;margin-right: 0.5cm;}</style>';
|
||||
$html = $pageHead . $content;
|
||||
$caseCode = (string) ($data['orgcase'][0]->case_id_code ?? $caseid);
|
||||
$fileName = 'Contingent_Bill_' . preg_replace('/[^A-Za-z0-9\-_]/', '_', $caseCode) . '_' . date('YmdHis') . '.pdf';
|
||||
|
||||
return $this->renderInlinePdf($html, $fileName, 'Contingent Bill ' . $caseCode);
|
||||
}
|
||||
|
||||
return view('Invoice/Org_Contingent_bill', $data);
|
||||
|
|
@ -958,10 +968,10 @@ class CaseMaster extends BaseController
|
|||
->get()
|
||||
->getResult();
|
||||
|
||||
$sql = 'select * from v_case_invoice v where v.id=' . $caseid . ' order by Charge_type,Adate';
|
||||
$data['showinvoice'] = $this->db->query($sql)->getResult();
|
||||
|
||||
$data['invoiceGtotal'] = $this->db->query('select sum(amount) as GTotal from v_case_invoice v where v.id=' . $caseid)->getResult();
|
||||
$organizationCaseModel = new OrganizationCaseModel();
|
||||
$invoiceData = $organizationCaseModel->getInvoiceRowsAndTotal($caseid);
|
||||
$data['showinvoice'] = $invoiceData['rows'];
|
||||
$data['invoiceGtotal'] = [(object) ['GTotal' => $invoiceData['total']]];
|
||||
|
||||
$claimId = (string) ($data['orgcase'][0]->insurance_no_1 ?? '');
|
||||
|
||||
|
|
@ -1360,4 +1370,28 @@ class CaseMaster extends BaseController
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderInlinePdf(string $html, string $fileName, string $title)
|
||||
{
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'orientation' => 'P',
|
||||
'margin_left' => 8,
|
||||
'margin_right' => 8,
|
||||
'margin_top' => 8,
|
||||
'margin_bottom' => 8,
|
||||
'default_font' => 'dejavusans',
|
||||
'tempDir' => WRITEPATH . 'cache',
|
||||
]);
|
||||
|
||||
$mpdf->SetTitle($title);
|
||||
$mpdf->WriteHTML($html);
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
||||
->setBody($mpdf->Output($fileName, 'S'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Models\BedAssignmentHistoryModel;
|
|||
use App\Models\ItemIpdModel;
|
||||
use App\Models\IpdBillingModel;
|
||||
use App\Models\IpdModel;
|
||||
use App\Models\NursingBedsideItemModel;
|
||||
use App\Models\PaymentModel;
|
||||
|
||||
class Ipd extends BaseController
|
||||
|
|
@ -15,6 +16,7 @@ class Ipd extends BaseController
|
|||
protected BedAssignmentHistoryModel $bedAssignmentModel;
|
||||
protected ItemIpdModel $itemIpdModel;
|
||||
protected IpdModel $ipdEditModel;
|
||||
protected NursingBedsideItemModel $nursingBedsideItemModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -22,6 +24,7 @@ class Ipd extends BaseController
|
|||
$this->bedAssignmentModel = new BedAssignmentHistoryModel();
|
||||
$this->itemIpdModel = new ItemIpdModel();
|
||||
$this->ipdEditModel = new IpdModel();
|
||||
$this->nursingBedsideItemModel = new NursingBedsideItemModel();
|
||||
helper(['common', 'form', 'age']);
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +196,7 @@ class Ipd extends BaseController
|
|||
}
|
||||
|
||||
if ($tab === 'ipd-charges') {
|
||||
$this->syncNursingChargesToInvoice($ipdId);
|
||||
$panelData['ipd_charges_grouped'] = $this->ipdModel->getIpdChargesGrouped($ipdId);
|
||||
$panelData['ipd_charges_total'] = $this->ipdModel->getIpdChargesTotal($ipdId);
|
||||
$panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId);
|
||||
|
|
@ -203,8 +207,14 @@ class Ipd extends BaseController
|
|||
$insCompId = 1;
|
||||
}
|
||||
|
||||
$panelData['bedside_items_by_category'] = $this->nursingBedsideItemModel->getBillableGroupedByCategory($insCompId);
|
||||
|
||||
$panelData['ipd_insurance_id'] = $insCompId;
|
||||
$panelData['doc_list'] = $this->ipdModel->getIpdDoctorList();
|
||||
$doctorIds = array_map(static fn ($doc) => (int) ($doc->id ?? 0), $panelData['doc_list']);
|
||||
$doctorIds = array_values(array_filter($doctorIds, static fn ($id) => $id > 0));
|
||||
$panelData['doctor_visit_fee_types'] = $this->ipdModel->getDoctorVisitFeeTypes();
|
||||
$panelData['doctor_visit_fee_map'] = $this->ipdModel->getDoctorVisitFeeMap($doctorIds);
|
||||
$panelData['item_types'] = $this->itemIpdModel->getItemTypes();
|
||||
|
||||
$itemLists = [];
|
||||
|
|
@ -419,6 +429,238 @@ class Ipd extends BaseController
|
|||
return $this->response->setJSON(['ok' => true]);
|
||||
}
|
||||
|
||||
public function addBedsideCharge(int $ipdId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
'billing.access',
|
||||
'billing.ipd.invoice',
|
||||
'billing.ipd.current-admission',
|
||||
]);
|
||||
if ($permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$bedsideItemId = (int) ($this->request->getPost('bedside_item_id') ?? 0);
|
||||
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
||||
$itemDate = (string) ($this->request->getPost('item_date') ?? date('Y-m-d'));
|
||||
$visitTime = (string) ($this->request->getPost('visit_time') ?? '');
|
||||
$comment = (string) ($this->request->getPost('comment') ?? '');
|
||||
$docId = (int) ($this->request->getPost('doc_id') ?? 0);
|
||||
$referName = (string) ($this->request->getPost('refer_name') ?? '');
|
||||
|
||||
if ($bedsideItemId <= 0) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Select a bedside item']);
|
||||
}
|
||||
|
||||
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
||||
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
||||
|
||||
$item = $this->nursingBedsideItemModel->getBillableItemForInsurance($bedsideItemId, $insId);
|
||||
if (! is_array($item) || empty($item)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Bedside item not found or not billable']);
|
||||
}
|
||||
|
||||
if ($qty <= 0) {
|
||||
$qty = 1;
|
||||
}
|
||||
|
||||
$rateInput = $this->request->getPost('item_rate');
|
||||
$insuranceRate = $item['insurance_rate'] ?? null;
|
||||
$rate = ($rateInput !== null && $rateInput !== '')
|
||||
? (float) $rateInput
|
||||
: ($insuranceRate !== null ? (float) $insuranceRate : (float) ($item['default_rate'] ?? 0));
|
||||
if ($rate < 0) {
|
||||
$rate = 0;
|
||||
}
|
||||
|
||||
$orgCode = trim((string) ($item['insurance_code'] ?? ''));
|
||||
|
||||
$docName = '';
|
||||
if ($docId > 0) {
|
||||
$docName = $this->ipdModel->getDoctorNameById($docId);
|
||||
} else {
|
||||
$docName = $referName;
|
||||
}
|
||||
|
||||
$typeId = $this->resolveBedsideItemTypeId((string) ($item['category'] ?? 'Bedside'));
|
||||
$amount = $rate * $qty;
|
||||
|
||||
$nursingChargeId = $this->insertNursingIpdChargeRow([
|
||||
'ipd_id' => $ipdId,
|
||||
'item_id' => $bedsideItemId,
|
||||
'item_name' => (string) ($item['item_name'] ?? ''),
|
||||
'item_type' => (string) ($item['item_type'] ?? 'Bedside'),
|
||||
'doctor_id' => $docId > 0 ? $docId : null,
|
||||
'doctor_name' => $docName !== '' ? $docName : null,
|
||||
'visit_date' => $itemDate,
|
||||
'visit_time' => $visitTime !== '' ? $visitTime : null,
|
||||
'consultation_type' => null,
|
||||
'duration' => null,
|
||||
'specialization' => null,
|
||||
'hospital_clinic' => null,
|
||||
'is_outside_doctor' => 0,
|
||||
'rate' => $rate,
|
||||
'qty' => (int) $qty,
|
||||
'amount' => $amount,
|
||||
'performed_by' => null,
|
||||
'performed_date' => $itemDate,
|
||||
'performed_time' => $visitTime !== '' ? $visitTime : null,
|
||||
'remarks' => $comment,
|
||||
'include_in_bill' => 1,
|
||||
'updated_by' => null,
|
||||
]);
|
||||
|
||||
$invoiceComment = $comment;
|
||||
if ($nursingChargeId > 0) {
|
||||
$marker = '[NURSING_CHARGE_ID:' . $nursingChargeId . ']';
|
||||
$invoiceComment = trim($invoiceComment . ' ' . $marker);
|
||||
}
|
||||
|
||||
$insertData = [
|
||||
'ipd_id' => $ipdId,
|
||||
'item_type' => $typeId,
|
||||
'item_id' => $bedsideItemId,
|
||||
'item_name' => (string) ($item['item_name'] ?? ''),
|
||||
'item_rate' => $rate,
|
||||
'item_added_date' => date('Y-m-d H:i:s'),
|
||||
'item_qty' => $qty,
|
||||
'item_amount' => $amount,
|
||||
'doc_id' => $docId,
|
||||
'doc_name' => $docName,
|
||||
'date_item' => $itemDate,
|
||||
'comment' => $invoiceComment,
|
||||
'ins_id' => $insId,
|
||||
'org_code' => $orgCode,
|
||||
];
|
||||
|
||||
$insertId = $this->ipdEditModel->insertIpdItem($insertData);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => $insertId > 0,
|
||||
'id' => $insertId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addDoctorVisitCharge(int $ipdId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
'billing.access',
|
||||
'billing.ipd.invoice',
|
||||
'billing.ipd.current-admission',
|
||||
]);
|
||||
if ($permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$docId = (int) ($this->request->getPost('doc_id') ?? 0);
|
||||
$feeTypeId = (int) ($this->request->getPost('fee_type_id') ?? 0);
|
||||
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
||||
$itemDate = (string) ($this->request->getPost('item_date') ?? date('Y-m-d'));
|
||||
$visitTime = (string) ($this->request->getPost('visit_time') ?? '');
|
||||
$duration = (int) ($this->request->getPost('duration') ?? 0);
|
||||
$hospitalClinic = trim((string) ($this->request->getPost('hospital_clinic') ?? ''));
|
||||
$isOutsideDoctor = (int) ($this->request->getPost('is_outside_doctor') ?? 0) === 1 ? 1 : 0;
|
||||
$comment = trim((string) ($this->request->getPost('comment') ?? ''));
|
||||
|
||||
if ($docId <= 0) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Select doctor']);
|
||||
}
|
||||
|
||||
if ($qty <= 0) {
|
||||
$qty = 1;
|
||||
}
|
||||
|
||||
$feeDetail = $this->ipdModel->getDoctorVisitFeeDetail($docId, $feeTypeId);
|
||||
|
||||
$rateInput = $this->request->getPost('item_rate');
|
||||
$rate = ($rateInput !== null && $rateInput !== '')
|
||||
? (float) $rateInput
|
||||
: (float) ($feeDetail['amount'] ?? 0);
|
||||
if ($rate < 0) {
|
||||
$rate = 0;
|
||||
}
|
||||
|
||||
$docName = $this->ipdModel->getDoctorNameById($docId);
|
||||
if ($docName === '') {
|
||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Doctor not found']);
|
||||
}
|
||||
|
||||
$visitLabel = trim((string) ($feeDetail['fee_desc'] ?? 'Doctor Visit'));
|
||||
if ($visitLabel === '') {
|
||||
$visitLabel = 'Doctor Visit';
|
||||
}
|
||||
|
||||
$itemName = 'Doctor Visit - ' . $visitLabel;
|
||||
$typeId = $this->resolveDoctorVisitItemTypeId();
|
||||
$amount = $rate * $qty;
|
||||
|
||||
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
||||
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
||||
|
||||
$nursingChargeId = $this->insertNursingIpdChargeRow([
|
||||
'ipd_id' => $ipdId,
|
||||
'item_id' => (int) ($feeDetail['id'] ?? $docId),
|
||||
'item_name' => $itemName,
|
||||
'item_type' => 'Doctor Visit',
|
||||
'doctor_id' => $docId,
|
||||
'doctor_name' => $docName,
|
||||
'visit_date' => $itemDate,
|
||||
'visit_time' => $visitTime !== '' ? $visitTime : null,
|
||||
'consultation_type' => (string) ($feeDetail['fee_type'] ?? ''),
|
||||
'duration' => $duration > 0 ? $duration : null,
|
||||
'specialization' => null,
|
||||
'hospital_clinic' => $hospitalClinic !== '' ? $hospitalClinic : null,
|
||||
'is_outside_doctor' => $isOutsideDoctor,
|
||||
'rate' => $rate,
|
||||
'qty' => (int) $qty,
|
||||
'amount' => $amount,
|
||||
'performed_by' => null,
|
||||
'performed_date' => $itemDate,
|
||||
'performed_time' => $visitTime !== '' ? $visitTime : null,
|
||||
'remarks' => $comment,
|
||||
'include_in_bill' => 1,
|
||||
'updated_by' => null,
|
||||
]);
|
||||
|
||||
$invoiceComment = $comment;
|
||||
if ($nursingChargeId > 0) {
|
||||
$marker = '[NURSING_CHARGE_ID:' . $nursingChargeId . ']';
|
||||
$invoiceComment = trim($invoiceComment . ' ' . $marker);
|
||||
}
|
||||
|
||||
$insertData = [
|
||||
'ipd_id' => $ipdId,
|
||||
'item_type' => $typeId,
|
||||
'item_id' => (int) ($feeDetail['id'] ?? $docId),
|
||||
'item_name' => $itemName,
|
||||
'item_rate' => $rate,
|
||||
'item_added_date' => date('Y-m-d H:i:s'),
|
||||
'item_qty' => $qty,
|
||||
'item_amount' => $amount,
|
||||
'doc_id' => $docId,
|
||||
'doc_name' => $docName,
|
||||
'date_item' => $itemDate,
|
||||
'comment' => $invoiceComment,
|
||||
'ins_id' => $insId,
|
||||
'org_code' => '',
|
||||
];
|
||||
|
||||
$insertId = $this->ipdEditModel->insertIpdItem($insertData);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => $insertId > 0,
|
||||
'id' => $insertId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteIpdCharge(int $itemId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
|
|
@ -434,8 +676,165 @@ class Ipd extends BaseController
|
|||
}
|
||||
|
||||
$result = $this->ipdEditModel->deleteIpdInvoiceItem($itemId);
|
||||
if ($result !== 1) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => false,
|
||||
'error' => 'Unable to remove charge item. Please refresh and try again.',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['ok' => $result === 1]);
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'deleted_id' => $itemId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateNursingCharge(int $ipdId, int $chargeId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
'billing.access',
|
||||
'billing.ipd.invoice',
|
||||
'billing.ipd.current-admission',
|
||||
]);
|
||||
if ($permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
if ($chargeId <= 0 || $ipdId <= 0 || ! $this->db->tableExists('nursing_ipd_charges')) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid charge reference']);
|
||||
}
|
||||
|
||||
$row = $this->db->table('nursing_ipd_charges')
|
||||
->where('charge_id', $chargeId)
|
||||
->where('ipd_id', $ipdId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (! is_array($row) || empty($row)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Charge history row not found']);
|
||||
}
|
||||
|
||||
$rate = (float) ($this->request->getPost('rate') ?? ($row['rate'] ?? 0));
|
||||
$qty = (float) ($this->request->getPost('qty') ?? ($row['qty'] ?? 1));
|
||||
$comment = trim((string) ($this->request->getPost('comment') ?? ($row['remarks'] ?? '')));
|
||||
$visitDate = (string) ($this->request->getPost('visit_date') ?? ($row['visit_date'] ?? date('Y-m-d')));
|
||||
$visitTime = (string) ($this->request->getPost('visit_time') ?? ($row['visit_time'] ?? ''));
|
||||
|
||||
if ($qty <= 0) {
|
||||
$qty = 1;
|
||||
}
|
||||
if ($rate < 0) {
|
||||
$rate = 0;
|
||||
}
|
||||
|
||||
$amount = $rate * $qty;
|
||||
$user = auth()->user();
|
||||
$updatedBy = ($user && isset($user->username))
|
||||
? (string) $user->username
|
||||
: (($user && isset($user->email)) ? (string) $user->email : 'system');
|
||||
|
||||
$this->db->table('nursing_ipd_charges')
|
||||
->where('charge_id', $chargeId)
|
||||
->where('ipd_id', $ipdId)
|
||||
->update([
|
||||
'rate' => $rate,
|
||||
'qty' => $qty,
|
||||
'amount' => $amount,
|
||||
'remarks' => $comment,
|
||||
'visit_date' => $visitDate,
|
||||
'visit_time' => $visitTime,
|
||||
'updated_by' => $updatedBy . ' [' . date('Y-m-d H:i:s') . ']',
|
||||
]);
|
||||
|
||||
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
||||
$invoiceRows = $this->db->table('ipd_invoice_item')
|
||||
->select('id')
|
||||
->where('ipd_id', $ipdId)
|
||||
->like('comment', $marker)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$invoiceComment = trim($comment . ' ' . $marker);
|
||||
foreach ($invoiceRows as $invoiceRow) {
|
||||
$invoiceId = (int) ($invoiceRow['id'] ?? 0);
|
||||
if ($invoiceId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$this->ipdEditModel->updateIpdItem([
|
||||
'item_rate' => $rate,
|
||||
'item_qty' => $qty,
|
||||
'item_amount' => $amount,
|
||||
'date_item' => $visitDate,
|
||||
'comment' => $invoiceComment,
|
||||
], $invoiceId);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['ok' => true]);
|
||||
}
|
||||
|
||||
public function deleteNursingCharge(int $ipdId, int $chargeId)
|
||||
{
|
||||
$permission = $this->requireAnyPermission([
|
||||
'billing.access',
|
||||
'billing.ipd.invoice',
|
||||
'billing.ipd.current-admission',
|
||||
]);
|
||||
if ($permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
if ($chargeId <= 0 || $ipdId <= 0 || ! $this->db->tableExists('nursing_ipd_charges')) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid charge reference']);
|
||||
}
|
||||
|
||||
$row = $this->db->table('nursing_ipd_charges')
|
||||
->where('charge_id', $chargeId)
|
||||
->where('ipd_id', $ipdId)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (! is_array($row) || empty($row)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['error' => 'Charge history row not found']);
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
$updatedBy = ($user && isset($user->username))
|
||||
? (string) $user->username
|
||||
: (($user && isset($user->email)) ? (string) $user->email : 'system');
|
||||
|
||||
$this->db->table('nursing_ipd_charges')
|
||||
->where('charge_id', $chargeId)
|
||||
->where('ipd_id', $ipdId)
|
||||
->update([
|
||||
'include_in_bill' => 0,
|
||||
'updated_by' => $updatedBy . ' [' . date('Y-m-d H:i:s') . ']',
|
||||
]);
|
||||
|
||||
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
||||
$invoiceRows = $this->db->table('ipd_invoice_item')
|
||||
->select('id')
|
||||
->where('ipd_id', $ipdId)
|
||||
->like('comment', $marker)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($invoiceRows as $invoiceRow) {
|
||||
$invoiceId = (int) ($invoiceRow['id'] ?? 0);
|
||||
if ($invoiceId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$this->ipdEditModel->deleteIpdInvoiceItem($invoiceId);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['ok' => true]);
|
||||
}
|
||||
|
||||
public function updateIpdChargePackage(int $itemId)
|
||||
|
|
@ -939,4 +1338,141 @@ class Ipd extends BaseController
|
|||
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
private function resolveBedsideItemTypeId(string $category): int
|
||||
{
|
||||
$groupDesc = 'Bedside - ' . trim($category);
|
||||
if ($groupDesc === 'Bedside -') {
|
||||
$groupDesc = 'Bedside - General';
|
||||
}
|
||||
|
||||
$row = $this->db->table('ipd_item_type')
|
||||
->select('itype_id')
|
||||
->where('group_desc', $groupDesc)
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if ($row) {
|
||||
return (int) ($row->itype_id ?? 0);
|
||||
}
|
||||
|
||||
$this->db->table('ipd_item_type')->insert([
|
||||
'group_desc' => $groupDesc,
|
||||
]);
|
||||
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
private function resolveDoctorVisitItemTypeId(): int
|
||||
{
|
||||
$groupDesc = 'Doctor Visit';
|
||||
|
||||
$row = $this->db->table('ipd_item_type')
|
||||
->select('itype_id')
|
||||
->where('group_desc', $groupDesc)
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if ($row) {
|
||||
return (int) ($row->itype_id ?? 0);
|
||||
}
|
||||
|
||||
$this->db->table('ipd_item_type')->insert([
|
||||
'group_desc' => $groupDesc,
|
||||
]);
|
||||
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
private function insertNursingIpdChargeRow(array $data): int
|
||||
{
|
||||
if (! $this->db->tableExists('nursing_ipd_charges')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->db->table('nursing_ipd_charges')->insert($data);
|
||||
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
private function syncNursingChargesToInvoice(int $ipdId): void
|
||||
{
|
||||
if (! $this->db->tableExists('nursing_ipd_charges')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = $this->db->table('nursing_ipd_charges')
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('include_in_bill', 1)
|
||||
->orderBy('charge_id', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
||||
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$chargeId = (int) ($row['charge_id'] ?? 0);
|
||||
if ($chargeId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
||||
$existsByMarker = $this->db->table('ipd_invoice_item')
|
||||
->where('ipd_id', $ipdId)
|
||||
->like('comment', $marker)
|
||||
->countAllResults();
|
||||
if ($existsByMarker > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemName = trim((string) ($row['item_name'] ?? ''));
|
||||
$dateItem = (string) ($row['visit_date'] ?? date('Y-m-d'));
|
||||
$qty = (float) ($row['qty'] ?? 1);
|
||||
$rate = (float) ($row['rate'] ?? 0);
|
||||
$amount = (float) ($row['amount'] ?? ($rate * $qty));
|
||||
$docId = (int) ($row['doctor_id'] ?? 0);
|
||||
|
||||
$existsByValue = $this->db->table('ipd_invoice_item')
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('item_name', $itemName)
|
||||
->where('date_item', $dateItem)
|
||||
->where('item_qty', $qty)
|
||||
->where('item_amount', $amount)
|
||||
->where('doc_id', $docId)
|
||||
->countAllResults();
|
||||
if ($existsByValue > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rawType = trim((string) ($row['item_type'] ?? ''));
|
||||
$typeId = stripos($rawType, 'doctor') !== false
|
||||
? $this->resolveDoctorVisitItemTypeId()
|
||||
: $this->resolveBedsideItemTypeId($rawType !== '' ? $rawType : 'General');
|
||||
|
||||
$remarks = trim((string) ($row['remarks'] ?? ''));
|
||||
$comment = trim($remarks . ' ' . $marker);
|
||||
|
||||
$this->ipdEditModel->insertIpdItem([
|
||||
'ipd_id' => $ipdId,
|
||||
'item_type' => $typeId,
|
||||
'item_id' => (int) ($row['item_id'] ?? $chargeId),
|
||||
'item_name' => $itemName,
|
||||
'item_rate' => $rate,
|
||||
'item_added_date' => date('Y-m-d H:i:s'),
|
||||
'item_qty' => $qty,
|
||||
'item_amount' => $amount,
|
||||
'doc_id' => $docId,
|
||||
'doc_name' => (string) ($row['doctor_name'] ?? ''),
|
||||
'date_item' => $dateItem,
|
||||
'comment' => $comment,
|
||||
'ins_id' => $insId,
|
||||
'org_code' => '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -266,4 +266,5 @@ class ItemIpd extends BaseController
|
|||
'csrfHash' => csrf_hash(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,7 @@ class Patient extends BaseController
|
|||
|
||||
public function create()
|
||||
{
|
||||
if (!$this->request->isAJAX()) {
|
||||
return $this->response
|
||||
->setStatusCode(400)
|
||||
->setJSON(['insertid' => 0, 'error_text' => 'Invalid request']);
|
||||
}
|
||||
$isAjax = $this->request->isAJAX();
|
||||
|
||||
$chk_age = $this->request->getPost('chk_age');
|
||||
$age_month = (string) $this->request->getPost('input_age_month');
|
||||
|
|
@ -67,12 +63,18 @@ class Patient extends BaseController
|
|||
$validation->setRules($rules);
|
||||
if (!$validation->withRequest($this->request)->run()) {
|
||||
$errorText = implode("\n", $validation->getErrors());
|
||||
if ($isAjax) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'error_text' => $errorText,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('billing/patient'))
|
||||
->withInput()
|
||||
->with('error', $errorText);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'mphone1' => $this->request->getPost('input_mphone1'),
|
||||
'p_fname' => strtoupper((string) $this->request->getPost('input_name')),
|
||||
|
|
@ -100,12 +102,23 @@ class Patient extends BaseController
|
|||
$patientModel = new PatientModel();
|
||||
$insertId = $patientModel->insertPatient($data);
|
||||
if ($insertId <= 0) {
|
||||
if ($isAjax) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'error_text' => 'Unable to save patient.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('billing/patient'))
|
||||
->withInput()
|
||||
->with('error', 'Unable to save patient.');
|
||||
}
|
||||
|
||||
$this->saveNamesToNameList([
|
||||
(string) $this->request->getPost('input_name'),
|
||||
(string) $this->request->getPost('input_relative_name'),
|
||||
]);
|
||||
|
||||
// Check Multiple UHID
|
||||
$relativeName = trim(strtoupper((string) $this->request->getPost('input_relative_name')));
|
||||
$patientName = trim(strtoupper((string) $this->request->getPost('input_name')));
|
||||
|
|
@ -143,9 +156,13 @@ class Patient extends BaseController
|
|||
$patientModel->insertDuplicateLog($dupData);
|
||||
}
|
||||
|
||||
if ($isAjax) {
|
||||
return $this->response->setJSON(['insertid' => $insertId]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('billing/patient/person_record/' . $insertId));
|
||||
}
|
||||
|
||||
public function search_adv()
|
||||
{
|
||||
$inputMphone1 = trim((string) $this->request->getPost('input_mphone1'));
|
||||
|
|
@ -416,6 +433,10 @@ class Patient extends BaseController
|
|||
$pid = (int) $this->request->getPost('p_id');
|
||||
$patientModel = new PatientModel();
|
||||
$patientModel->updatePatient($data, $pid);
|
||||
$this->saveNamesToNameList([
|
||||
(string) $this->request->getPost('input_name'),
|
||||
(string) $this->request->getPost('input_relative_name'),
|
||||
]);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'update' => 1,
|
||||
|
|
@ -778,19 +799,8 @@ class Patient extends BaseController
|
|||
return $this->response->setJSON([]);
|
||||
}
|
||||
|
||||
$sql = "select * from name_list where name like '" . $this->db->escapeLikeString($q) . "%' order by name limit 20";
|
||||
$query = $this->db->query($sql);
|
||||
$rows = $query->getResultArray();
|
||||
|
||||
$rowSet = [];
|
||||
foreach ($rows as $row) {
|
||||
$rowSet[] = [
|
||||
'label' => trim($row['name']),
|
||||
'value' => trim($row['name']),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON($rowSet);
|
||||
$patientModel = new PatientModel();
|
||||
return $this->response->setJSON($patientModel->getNameSuggestions($q));
|
||||
}
|
||||
|
||||
private function getProfileFilePath(int $fileId): string
|
||||
|
|
@ -922,10 +932,38 @@ class Patient extends BaseController
|
|||
return null;
|
||||
}
|
||||
|
||||
$date = trim($date);
|
||||
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $date);
|
||||
if (!$dt) {
|
||||
$dt = \DateTime::createFromFormat('d/m/Y', $date);
|
||||
}
|
||||
|
||||
return $dt ? $dt->format('Y-m-d') : null;
|
||||
}
|
||||
|
||||
private function saveNamesToNameList(array $names): void
|
||||
{
|
||||
if (! $this->db->tableExists('name_list')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('name_list');
|
||||
foreach ($names as $rawName) {
|
||||
$name = preg_replace('/\s+/', ' ', trim((string) $rawName));
|
||||
if ($name === '' || strlen($name) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = ucwords(strtolower($name));
|
||||
$exists = $builder->select('id')->where('name', $name)->limit(1)->get()->getRowArray();
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder->insert(['name' => $name]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ namespace App\Controllers;
|
|||
|
||||
class Report extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('report/index');
|
||||
}
|
||||
|
||||
public function collection_report()
|
||||
{
|
||||
$employees = $this->db->table('users')
|
||||
|
|
@ -142,6 +147,189 @@ class Report extends BaseController
|
|||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
public function billing_operations_report()
|
||||
{
|
||||
$doctors = $this->db->table('opd_master')
|
||||
->select('doc_name')
|
||||
->where('doc_name IS NOT NULL', null, false)
|
||||
->where("doc_name != ''", null, false)
|
||||
->groupBy('doc_name')
|
||||
->orderBy('doc_name', 'ASC')
|
||||
->get()
|
||||
->getResult();
|
||||
|
||||
return view('report/billing_operations_report', [
|
||||
'doctors' => $doctors,
|
||||
]);
|
||||
}
|
||||
|
||||
public function billing_operations_report_data(string $dateRange, string $doctor = '0', int $output = 0)
|
||||
{
|
||||
[$minRange, $maxRange] = $this->parseDateRange($dateRange);
|
||||
|
||||
$doctorFilter = trim(urldecode($doctor));
|
||||
|
||||
$opdBuilder = $this->db->table('opd_master o')
|
||||
->select('o.doc_name')
|
||||
->select('COUNT(o.opd_id) as total_opd', false)
|
||||
->where('o.apointment_date >=', $minRange)
|
||||
->where('o.apointment_date <=', $maxRange)
|
||||
->where('o.doc_name IS NOT NULL', null, false)
|
||||
->where("o.doc_name != ''", null, false);
|
||||
|
||||
if ($doctorFilter !== '' && $doctorFilter !== '0') {
|
||||
$opdBuilder->where('o.doc_name', $doctorFilter);
|
||||
}
|
||||
|
||||
$opdRows = $opdBuilder
|
||||
->groupBy('o.doc_name')
|
||||
->orderBy('total_opd', 'DESC')
|
||||
->get()
|
||||
->getResult();
|
||||
|
||||
$invoiceRows = $this->db->table('invoice_master m')
|
||||
->select('m.id, m.inv_name, m.inv_date, m.total_amount, m.discount_amount')
|
||||
->where('m.inv_date >=', $minRange)
|
||||
->where('m.inv_date <=', $maxRange)
|
||||
->where('m.invoice_status', 1)
|
||||
->orderBy('m.id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$invoiceIds = array_values(array_filter(array_map(static function (array $row): int {
|
||||
return (int) ($row['id'] ?? 0);
|
||||
}, $invoiceRows)));
|
||||
|
||||
$testAgg = [];
|
||||
$paymentAgg = [];
|
||||
if (! empty($invoiceIds)) {
|
||||
$testRows = $this->db->table('invoice_item')
|
||||
->select('inv_master_id')
|
||||
->select('COUNT(id) as test_count', false)
|
||||
->select('SUM(item_qty) as test_qty', false)
|
||||
->select('SUM(item_amount) as test_amount', false)
|
||||
->whereIn('inv_master_id', $invoiceIds)
|
||||
->groupBy('inv_master_id')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($testRows as $row) {
|
||||
$testAgg[(int) ($row['inv_master_id'] ?? 0)] = $row;
|
||||
}
|
||||
|
||||
$payRows = $this->db->table('payment_history')
|
||||
->select('payof_id')
|
||||
->select("SUM(IF(credit_debit=0, amount, 0)) as total_received", false)
|
||||
->select("SUM(IF(credit_debit=1, amount, 0)) as total_refund", false)
|
||||
->where('payof_type', 2)
|
||||
->whereIn('payof_id', $invoiceIds)
|
||||
->groupBy('payof_id')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($payRows as $row) {
|
||||
$paymentAgg[(int) ($row['payof_id'] ?? 0)] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$invoiceDetailRows = [];
|
||||
$invoiceSummary = [
|
||||
'invoice_count' => 0,
|
||||
'total_invoice_amount' => 0.0,
|
||||
'total_discount' => 0.0,
|
||||
'total_test_count' => 0,
|
||||
'total_test_qty' => 0.0,
|
||||
'total_test_amount' => 0.0,
|
||||
'total_received' => 0.0,
|
||||
'total_refund' => 0.0,
|
||||
];
|
||||
|
||||
foreach ($invoiceRows as $row) {
|
||||
$invId = (int) ($row['id'] ?? 0);
|
||||
$test = $testAgg[$invId] ?? ['test_count' => 0, 'test_qty' => 0, 'test_amount' => 0];
|
||||
$pay = $paymentAgg[$invId] ?? ['total_received' => 0, 'total_refund' => 0];
|
||||
|
||||
$invoiceAmount = (float) ($row['total_amount'] ?? 0);
|
||||
$discountAmount = (float) ($row['discount_amount'] ?? 0);
|
||||
$received = (float) ($pay['total_received'] ?? 0);
|
||||
$refund = (float) ($pay['total_refund'] ?? 0);
|
||||
$netReceived = $received - $refund;
|
||||
|
||||
$invoiceSummary['invoice_count']++;
|
||||
$invoiceSummary['total_invoice_amount'] += $invoiceAmount;
|
||||
$invoiceSummary['total_discount'] += $discountAmount;
|
||||
$invoiceSummary['total_test_count'] += (int) ($test['test_count'] ?? 0);
|
||||
$invoiceSummary['total_test_qty'] += (float) ($test['test_qty'] ?? 0);
|
||||
$invoiceSummary['total_test_amount'] += (float) ($test['test_amount'] ?? 0);
|
||||
$invoiceSummary['total_received'] += $received;
|
||||
$invoiceSummary['total_refund'] += $refund;
|
||||
|
||||
$invoiceDetailRows[] = (object) [
|
||||
'id' => $invId,
|
||||
'inv_name' => (string) ($row['inv_name'] ?? ''),
|
||||
'inv_date' => (string) ($row['inv_date'] ?? ''),
|
||||
'invoice_amount' => $invoiceAmount,
|
||||
'discount_amount' => $discountAmount,
|
||||
'test_count' => (int) ($test['test_count'] ?? 0),
|
||||
'test_qty' => (float) ($test['test_qty'] ?? 0),
|
||||
'test_amount' => (float) ($test['test_amount'] ?? 0),
|
||||
'received_amount' => $received,
|
||||
'refund_amount' => $refund,
|
||||
'net_received' => $netReceived,
|
||||
'pending_amount' => $invoiceAmount - $netReceived,
|
||||
];
|
||||
}
|
||||
|
||||
$ipdPaymentRows = $this->db->table('payment_history p')
|
||||
->select('p.payof_id as ipd_id, i.ipd_code, pat.p_fname as patient_name')
|
||||
->select("SUM(IF(p.credit_debit=0, p.amount, 0)) as total_received", false)
|
||||
->select("SUM(IF(p.credit_debit=1, p.amount, 0)) as total_refund", false)
|
||||
->join('ipd_master i', 'i.id = p.payof_id', 'left')
|
||||
->join('patient_master pat', 'pat.id = i.p_id', 'left')
|
||||
->where('p.payof_type', 4)
|
||||
->where('p.payment_date >=', $minRange)
|
||||
->where('p.payment_date <=', $maxRange)
|
||||
->groupBy('p.payof_id')
|
||||
->orderBy('total_received', 'DESC')
|
||||
->get()
|
||||
->getResult();
|
||||
|
||||
$ipdSummary = [
|
||||
'total_received' => 0.0,
|
||||
'total_refund' => 0.0,
|
||||
'net_received' => 0.0,
|
||||
'ipd_count' => 0,
|
||||
];
|
||||
|
||||
foreach ($ipdPaymentRows as $row) {
|
||||
$received = (float) ($row->total_received ?? 0);
|
||||
$refund = (float) ($row->total_refund ?? 0);
|
||||
$ipdSummary['total_received'] += $received;
|
||||
$ipdSummary['total_refund'] += $refund;
|
||||
$ipdSummary['ipd_count']++;
|
||||
}
|
||||
$ipdSummary['net_received'] = $ipdSummary['total_received'] - $ipdSummary['total_refund'];
|
||||
|
||||
$data = [
|
||||
'min_range' => $minRange,
|
||||
'max_range' => $maxRange,
|
||||
'doctor_filter' => $doctorFilter,
|
||||
'opd_rows' => $opdRows,
|
||||
'invoice_summary' => $invoiceSummary,
|
||||
'invoice_rows' => $invoiceDetailRows,
|
||||
'ipd_summary' => $ipdSummary,
|
||||
'ipd_payment_rows' => $ipdPaymentRows,
|
||||
];
|
||||
|
||||
$content = view('report/billing_operations_report_table', $data);
|
||||
if ($output === 1) {
|
||||
ExportExcel($content, 'Billing_Operations_Report');
|
||||
return $this->response->setBody('');
|
||||
}
|
||||
|
||||
return $this->response->setBody($content);
|
||||
}
|
||||
|
||||
public function diagnosis_report()
|
||||
{
|
||||
$itemTypes = $this->db->table('hc_item_type')
|
||||
|
|
@ -228,6 +416,48 @@ class Report extends BaseController
|
|||
return view('report/insurance_credit_main');
|
||||
}
|
||||
|
||||
public function echs_ipd_list_main()
|
||||
{
|
||||
return $this->insurance_ipd_report();
|
||||
}
|
||||
|
||||
public function echs_opd_list_main()
|
||||
{
|
||||
return $this->insurance_opd_report();
|
||||
}
|
||||
|
||||
public function echs_ipd_list_data(
|
||||
string $dateRange,
|
||||
string $orgStatus = '-1',
|
||||
string $ipdType = '0',
|
||||
int $output = 0
|
||||
) {
|
||||
$insuranceId = $this->mapLegacyInsuranceFilter($ipdType);
|
||||
return $this->insurance_ipd_report_data($dateRange, $insuranceId, $orgStatus, $output);
|
||||
}
|
||||
|
||||
public function echs_opd_list_data(
|
||||
string $dateRange,
|
||||
string $orgStatus = '-1',
|
||||
string $ipdType = '0',
|
||||
int $output = 0
|
||||
) {
|
||||
$insuranceId = $this->mapLegacyInsuranceFilter($ipdType);
|
||||
return $this->insurance_opd_report_data($dateRange, $insuranceId, $orgStatus, $output);
|
||||
}
|
||||
|
||||
private function mapLegacyInsuranceFilter(string $legacyType): string
|
||||
{
|
||||
$legacyValue = (int) trim($legacyType);
|
||||
if ($legacyValue > 0) {
|
||||
return 'G' . $legacyValue;
|
||||
}
|
||||
if ($legacyValue < 0) {
|
||||
return (string) abs($legacyValue);
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
public function insurance_opd_report()
|
||||
{
|
||||
$insuranceGroups = $this->db->table('h_insurance_group')
|
||||
|
|
@ -494,4 +724,668 @@ class Report extends BaseController
|
|||
|
||||
return $this->response->setBody($content);
|
||||
}
|
||||
|
||||
public function nabh_audit_report()
|
||||
{
|
||||
return view('report/nabh_audit_report');
|
||||
}
|
||||
|
||||
public function nabh_audit_report_data(
|
||||
string $dateRange,
|
||||
string $module = 'all',
|
||||
string $status = 'all',
|
||||
int $output = 0
|
||||
) {
|
||||
[$minRange, $maxRange] = $this->parseDateRange($dateRange);
|
||||
|
||||
$module = strtolower(trim($module));
|
||||
if (! in_array($module, ['all', 'ipd', 'opd'], true)) {
|
||||
$module = 'all';
|
||||
}
|
||||
|
||||
$status = strtolower(trim($status));
|
||||
if (! in_array($status, ['all', 'critical-missing', 'compliant'], true)) {
|
||||
$status = 'all';
|
||||
}
|
||||
|
||||
$ipdRows = [];
|
||||
$opdRows = [];
|
||||
|
||||
if ($module === 'all' || $module === 'ipd') {
|
||||
$ipdRows = $this->getIpdNabhAuditRows($minRange, $maxRange);
|
||||
}
|
||||
|
||||
if ($module === 'all' || $module === 'opd') {
|
||||
$opdRows = $this->getOpdNabhAuditRows($minRange, $maxRange);
|
||||
}
|
||||
|
||||
$rows = array_merge($ipdRows, $opdRows);
|
||||
|
||||
if ($status === 'critical-missing') {
|
||||
$rows = array_values(array_filter($rows, static function (array $row): bool {
|
||||
return (int) ($row['critical_missing_count'] ?? 0) > 0;
|
||||
}));
|
||||
} elseif ($status === 'compliant') {
|
||||
$rows = array_values(array_filter($rows, static function (array $row): bool {
|
||||
return (int) ($row['critical_missing_count'] ?? 0) === 0;
|
||||
}));
|
||||
}
|
||||
|
||||
usort($rows, static function (array $a, array $b): int {
|
||||
return strcmp((string) ($b['encounter_datetime'] ?? ''), (string) ($a['encounter_datetime'] ?? ''));
|
||||
});
|
||||
|
||||
$summary = $this->buildNabhSummary($rows);
|
||||
|
||||
$data = [
|
||||
'rows' => $rows,
|
||||
'summary' => $summary,
|
||||
'min_range' => $minRange,
|
||||
'max_range' => $maxRange,
|
||||
'module_filter' => $module,
|
||||
'status_filter' => $status,
|
||||
];
|
||||
|
||||
$content = view('report/nabh_audit_report_table', $data);
|
||||
if ($output === 1) {
|
||||
ExportExcel($content, 'NABH_Audit_Report');
|
||||
return $this->response->setBody('');
|
||||
}
|
||||
|
||||
return $this->response->setBody($content);
|
||||
}
|
||||
|
||||
private function buildNabhSummary(array $rows): array
|
||||
{
|
||||
$summary = [
|
||||
'all' => ['label' => 'Overall', 'total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0.0],
|
||||
'ipd' => ['label' => 'IPD Discharge', 'total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0.0],
|
||||
'opd' => ['label' => 'OPD Consult', 'total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0.0],
|
||||
];
|
||||
|
||||
$completionAccumulator = [
|
||||
'all' => 0.0,
|
||||
'ipd' => 0.0,
|
||||
'opd' => 0.0,
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$module = strtolower((string) ($row['module_key'] ?? 'all'));
|
||||
if (! isset($summary[$module])) {
|
||||
$module = 'all';
|
||||
}
|
||||
|
||||
$isCompliant = (int) ($row['critical_missing_count'] ?? 0) === 0;
|
||||
$completion = (float) ($row['completion_percent'] ?? 0);
|
||||
|
||||
$summary['all']['total']++;
|
||||
$completionAccumulator['all'] += $completion;
|
||||
if ($isCompliant) {
|
||||
$summary['all']['compliant']++;
|
||||
} else {
|
||||
$summary['all']['critical_missing']++;
|
||||
}
|
||||
|
||||
if ($module !== 'all') {
|
||||
$summary[$module]['total']++;
|
||||
$completionAccumulator[$module] += $completion;
|
||||
if ($isCompliant) {
|
||||
$summary[$module]['compliant']++;
|
||||
} else {
|
||||
$summary[$module]['critical_missing']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($summary as $key => $item) {
|
||||
$total = (int) ($item['total'] ?? 0);
|
||||
$summary[$key]['avg_completion'] = $total > 0
|
||||
? round($completionAccumulator[$key] / $total, 1)
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
private function getIpdNabhAuditRows(string $minRange, string $maxRange): array
|
||||
{
|
||||
if (! $this->db->tableExists('ipd_master')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ipdFields = $this->db->getFieldNames('ipd_master') ?? [];
|
||||
if (! in_array('id', $ipdFields, true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dateCol = $this->resolveExistingColumn($ipdFields, [
|
||||
'discharge_date',
|
||||
'discharged_at',
|
||||
'discharge_datetime',
|
||||
'register_date',
|
||||
'admit_date',
|
||||
'created_at',
|
||||
]);
|
||||
if ($dateCol === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ipdCodeCol = $this->resolveExistingColumn($ipdFields, ['ipd_code', 'ipd_no', 'ipd_number']);
|
||||
$patientFkCol = $this->resolveExistingColumn($ipdFields, ['p_id', 'patient_id']);
|
||||
$statusCol = $this->resolveExistingColumn($ipdFields, ['ipd_status', 'discharge_status', 'discharg_status', 'is_discharged']);
|
||||
|
||||
$builder = $this->db->table('ipd_master i')
|
||||
->select('i.id as encounter_id')
|
||||
->select('i.' . $dateCol . ' as encounter_datetime', false);
|
||||
|
||||
if ($ipdCodeCol !== null) {
|
||||
$builder->select('i.' . $ipdCodeCol . ' as encounter_code', false);
|
||||
} else {
|
||||
$builder->select("'' as encounter_code", false);
|
||||
}
|
||||
|
||||
if ($patientFkCol !== null) {
|
||||
$builder->select('i.' . $patientFkCol . ' as patient_id', false);
|
||||
} else {
|
||||
$builder->select('0 as patient_id', false);
|
||||
}
|
||||
|
||||
$patientNameExpr = "''";
|
||||
$patientCodeExpr = "''";
|
||||
if ($patientFkCol !== null && $this->db->tableExists('patient_master')) {
|
||||
$patientFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$nameCol = $this->resolveExistingColumn($patientFields, ['p_fname', 'patient_name', 'name']);
|
||||
$codeCol = $this->resolveExistingColumn($patientFields, ['p_code', 'patient_code', 'uhid_no']);
|
||||
$pkCol = $this->resolveExistingColumn($patientFields, ['id']);
|
||||
|
||||
if ($pkCol !== null) {
|
||||
$builder->join('patient_master p', 'p.' . $pkCol . ' = i.' . $patientFkCol, 'left');
|
||||
if ($nameCol !== null) {
|
||||
$patientNameExpr = 'p.' . $nameCol;
|
||||
}
|
||||
if ($codeCol !== null) {
|
||||
$patientCodeExpr = 'p.' . $codeCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$builder->select($patientNameExpr . ' as patient_name', false)
|
||||
->select($patientCodeExpr . ' as patient_code', false)
|
||||
->where('i.' . $dateCol . ' >=', $minRange)
|
||||
->where('i.' . $dateCol . ' <=', $maxRange)
|
||||
->orderBy('i.' . $dateCol, 'DESC');
|
||||
|
||||
if ($statusCol !== null) {
|
||||
$builder->where('i.' . $statusCol, 1);
|
||||
}
|
||||
|
||||
$resultRows = $builder->get()->getResultArray();
|
||||
$finalRows = [];
|
||||
|
||||
foreach ($resultRows as $row) {
|
||||
$ipdId = (int) ($row['encounter_id'] ?? 0);
|
||||
if ($ipdId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$checklist = $this->buildIpdChecklistForAudit($ipdId, (int) ($row['patient_id'] ?? 0));
|
||||
$total = (int) ($checklist['total_count'] ?? 0);
|
||||
$okCount = (int) ($checklist['ok_count'] ?? 0);
|
||||
|
||||
$finalRows[] = [
|
||||
'module_key' => 'ipd',
|
||||
'module_label' => 'IPD Discharge',
|
||||
'encounter_id' => $ipdId,
|
||||
'encounter_code' => (string) ($row['encounter_code'] ?? ''),
|
||||
'patient_code' => (string) ($row['patient_code'] ?? ''),
|
||||
'patient_name' => (string) ($row['patient_name'] ?? ''),
|
||||
'encounter_datetime' => (string) ($row['encounter_datetime'] ?? ''),
|
||||
'ok_count' => $okCount,
|
||||
'total_count' => $total,
|
||||
'completion_percent' => $total > 0 ? round(($okCount * 100) / $total, 1) : 0.0,
|
||||
'critical_missing_count' => (int) ($checklist['critical_missing_count'] ?? 0),
|
||||
'critical_missing_labels' => implode(', ', $checklist['critical_missing'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
return $finalRows;
|
||||
}
|
||||
|
||||
private function buildIpdChecklistForAudit(int $ipdId, int $patientId): array
|
||||
{
|
||||
$hasAdmissionReason = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_complaint',
|
||||
$ipdId,
|
||||
['comp_report', 'comp_remark']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd('ipd_discharge_complaint_remark', $ipdId, ['comp_remark']);
|
||||
|
||||
$hasDiagnosis = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_diagnosis',
|
||||
$ipdId,
|
||||
['comp_report', 'comp_remark']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd('ipd_discharge_diagnosis_remark', $ipdId, ['comp_remark']);
|
||||
|
||||
$hasCourse = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_course',
|
||||
$ipdId,
|
||||
['comp_report', 'comp_remark']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd('ipd_discharge_course_remark', $ipdId, ['comp_remark']);
|
||||
|
||||
$hasProcedure = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_surgery',
|
||||
$ipdId,
|
||||
['surgery_name', 'surgery_remark']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_procedure',
|
||||
$ipdId,
|
||||
['procedure_name', 'procedure_remark']
|
||||
);
|
||||
|
||||
$hasCondition = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_1_b_final',
|
||||
$ipdId,
|
||||
['discharge_condition', 'condition_at_discharge', 'condition_remark', 'remark']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_general_exam_col',
|
||||
$ipdId,
|
||||
['value']
|
||||
);
|
||||
|
||||
$hasMedication = $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_drug',
|
||||
$ipdId,
|
||||
['drug_name', 'drug_dose', 'drug_day']
|
||||
) || $this->tableHasAnyNonEmptyValueByIpd(
|
||||
'ipd_discharge_prescrption_prescribed',
|
||||
$ipdId,
|
||||
['med_name', 'med_type', 'qty', 'no_of_days', 'remark']
|
||||
);
|
||||
|
||||
$instructionText = $this->firstNonEmptyValueByIpd(
|
||||
'ipd_discharge_instructions',
|
||||
$ipdId,
|
||||
['comp_remark', 'footer_text']
|
||||
);
|
||||
$reviewAfter = $this->firstNonEmptyValueByIpd('ipd_discharge_instructions', $ipdId, ['review_after']);
|
||||
$hasFollowUp = trim($instructionText) !== '' || trim($reviewAfter) !== '';
|
||||
|
||||
$allergySnapshot = $this->getLatestOpdAllergySnapshotByPatient($patientId);
|
||||
$hasAllergyAdr = trim((string) ($allergySnapshot['drug_allergy_status'] ?? '')) !== ''
|
||||
|| trim((string) ($allergySnapshot['drug_allergy_details'] ?? '')) !== ''
|
||||
|| trim((string) ($allergySnapshot['adr_history'] ?? '')) !== '';
|
||||
|
||||
$instructionLower = strtolower((string) $instructionText);
|
||||
$hasRedFlags = strpos($instructionLower, 'emergency') !== false
|
||||
|| strpos($instructionLower, 'warning') !== false
|
||||
|| strpos($instructionLower, 'red flag') !== false
|
||||
|| strpos($instructionLower, 'immediately') !== false
|
||||
|| strpos($instructionLower, 'return if') !== false;
|
||||
|
||||
$items = [
|
||||
['label' => 'Reason for admission / presenting complaints', 'ok' => $hasAdmissionReason, 'critical' => true],
|
||||
['label' => 'Final diagnosis documented', 'ok' => $hasDiagnosis, 'critical' => true],
|
||||
['label' => 'Course / treatment in hospital documented', 'ok' => $hasCourse, 'critical' => true],
|
||||
['label' => 'Surgery/procedure documented (if applicable)', 'ok' => $hasProcedure, 'critical' => false],
|
||||
['label' => 'Condition at discharge documented', 'ok' => $hasCondition, 'critical' => true],
|
||||
['label' => 'Discharge medication documented', 'ok' => $hasMedication, 'critical' => true],
|
||||
['label' => 'Follow-up instructions / review plan documented', 'ok' => $hasFollowUp, 'critical' => true],
|
||||
['label' => 'Drug allergy / ADR history documented', 'ok' => $hasAllergyAdr, 'critical' => false],
|
||||
['label' => 'Red-flag / emergency return advice documented', 'ok' => $hasRedFlags, 'critical' => false],
|
||||
];
|
||||
|
||||
$criticalMissing = [];
|
||||
$okCount = 0;
|
||||
foreach ($items as $item) {
|
||||
if (! empty($item['ok'])) {
|
||||
$okCount++;
|
||||
continue;
|
||||
}
|
||||
if (! empty($item['critical'])) {
|
||||
$criticalMissing[] = (string) ($item['label'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ok_count' => $okCount,
|
||||
'total_count' => count($items),
|
||||
'critical_missing' => $criticalMissing,
|
||||
'critical_missing_count' => count($criticalMissing),
|
||||
];
|
||||
}
|
||||
|
||||
private function getOpdNabhAuditRows(string $minRange, string $maxRange): array
|
||||
{
|
||||
if (! $this->db->tableExists('opd_master')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$opdFields = $this->db->getFieldNames('opd_master') ?? [];
|
||||
if (! in_array('opd_id', $opdFields, true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dateCol = $this->resolveExistingColumn($opdFields, [
|
||||
'apointment_date',
|
||||
'appointment_date',
|
||||
'created_at',
|
||||
'entry_date',
|
||||
]);
|
||||
if ($dateCol === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$opdCodeCol = $this->resolveExistingColumn($opdFields, ['opd_code', 'opd_no']);
|
||||
$patientFkCol = $this->resolveExistingColumn($opdFields, ['p_id', 'patient_id']);
|
||||
|
||||
$builder = $this->db->table('opd_master o')
|
||||
->select('o.opd_id as encounter_id')
|
||||
->select('o.' . $dateCol . ' as encounter_datetime', false);
|
||||
|
||||
if ($opdCodeCol !== null) {
|
||||
$builder->select('o.' . $opdCodeCol . ' as encounter_code', false);
|
||||
} else {
|
||||
$builder->select("'' as encounter_code", false);
|
||||
}
|
||||
|
||||
if ($patientFkCol !== null) {
|
||||
$builder->select('o.' . $patientFkCol . ' as patient_id', false);
|
||||
} else {
|
||||
$builder->select('0 as patient_id', false);
|
||||
}
|
||||
|
||||
$patientNameExpr = "''";
|
||||
$patientCodeExpr = "''";
|
||||
if ($patientFkCol !== null && $this->db->tableExists('patient_master')) {
|
||||
$patientFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
$nameCol = $this->resolveExistingColumn($patientFields, ['p_fname', 'patient_name', 'name']);
|
||||
$codeCol = $this->resolveExistingColumn($patientFields, ['p_code', 'patient_code', 'uhid_no']);
|
||||
$pkCol = $this->resolveExistingColumn($patientFields, ['id']);
|
||||
|
||||
if ($pkCol !== null) {
|
||||
$builder->join('patient_master p', 'p.' . $pkCol . ' = o.' . $patientFkCol, 'left');
|
||||
if ($nameCol !== null) {
|
||||
$patientNameExpr = 'p.' . $nameCol;
|
||||
}
|
||||
if ($codeCol !== null) {
|
||||
$patientCodeExpr = 'p.' . $codeCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prescriptionCols = [];
|
||||
if ($this->db->tableExists('opd_prescription')) {
|
||||
$prescriptionFields = $this->db->getFieldNames('opd_prescription') ?? [];
|
||||
if (in_array('id', $prescriptionFields, true) && in_array('opd_id', $prescriptionFields, true)) {
|
||||
$builder->join(
|
||||
'opd_prescription pr',
|
||||
'pr.id = (SELECT MAX(px.id) FROM opd_prescription px WHERE px.opd_id = o.opd_id)',
|
||||
'left',
|
||||
false
|
||||
);
|
||||
|
||||
foreach ([
|
||||
'complaints',
|
||||
'diagnosis',
|
||||
'Provisional_diagnosis',
|
||||
'Finding_Examinations',
|
||||
'Prescriber_Remarks',
|
||||
'investigation',
|
||||
'advice',
|
||||
'next_visit',
|
||||
'refer_to',
|
||||
'bp',
|
||||
'diastolic',
|
||||
'pulse',
|
||||
'temp',
|
||||
'spo2',
|
||||
'rr_min',
|
||||
'height',
|
||||
'weight',
|
||||
'drug_allergy_status',
|
||||
'drug_allergy_details',
|
||||
'adr_history',
|
||||
'current_medications',
|
||||
] as $column) {
|
||||
if (in_array($column, $prescriptionFields, true)) {
|
||||
$builder->select('pr.' . $column, false);
|
||||
$prescriptionCols[] = $column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$builder->select($patientNameExpr . ' as patient_name', false)
|
||||
->select($patientCodeExpr . ' as patient_code', false)
|
||||
->where('o.' . $dateCol . ' >=', $minRange)
|
||||
->where('o.' . $dateCol . ' <=', $maxRange)
|
||||
->orderBy('o.' . $dateCol, 'DESC');
|
||||
|
||||
$resultRows = $builder->get()->getResultArray();
|
||||
$finalRows = [];
|
||||
|
||||
foreach ($resultRows as $row) {
|
||||
$checklist = $this->buildOpdChecklistForAudit($row, $prescriptionCols);
|
||||
$total = (int) ($checklist['total_count'] ?? 0);
|
||||
$okCount = (int) ($checklist['ok_count'] ?? 0);
|
||||
|
||||
$finalRows[] = [
|
||||
'module_key' => 'opd',
|
||||
'module_label' => 'OPD Consult',
|
||||
'encounter_id' => (int) ($row['encounter_id'] ?? 0),
|
||||
'encounter_code' => (string) ($row['encounter_code'] ?? ''),
|
||||
'patient_code' => (string) ($row['patient_code'] ?? ''),
|
||||
'patient_name' => (string) ($row['patient_name'] ?? ''),
|
||||
'encounter_datetime' => (string) ($row['encounter_datetime'] ?? ''),
|
||||
'ok_count' => $okCount,
|
||||
'total_count' => $total,
|
||||
'completion_percent' => $total > 0 ? round(($okCount * 100) / $total, 1) : 0.0,
|
||||
'critical_missing_count' => (int) ($checklist['critical_missing_count'] ?? 0),
|
||||
'critical_missing_labels' => implode(', ', $checklist['critical_missing'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
return $finalRows;
|
||||
}
|
||||
|
||||
private function buildOpdChecklistForAudit(array $row, array $availableCols): array
|
||||
{
|
||||
$hasComplaints = $this->rowHasAnyNonEmptyValue($row, ['complaints']);
|
||||
$hasDiagnosis = $this->rowHasAnyNonEmptyValue($row, ['diagnosis', 'Provisional_diagnosis']);
|
||||
$hasClinicalCourse = $this->rowHasAnyNonEmptyValue($row, ['Finding_Examinations', 'Prescriber_Remarks']);
|
||||
$hasProcedure = $this->rowHasAnyNonEmptyValue($row, ['investigation']);
|
||||
$hasCondition = $this->rowHasAnyNonEmptyValue($row, ['bp', 'diastolic', 'pulse', 'temp', 'spo2', 'rr_min', 'height', 'weight']);
|
||||
$hasMedicationPlan = $this->rowHasAnyNonEmptyValue($row, ['advice', 'current_medications']);
|
||||
$hasFollowUp = $this->rowHasAnyNonEmptyValue($row, ['next_visit', 'refer_to']);
|
||||
|
||||
$hasAllergyAdr = $this->rowHasAnyNonEmptyValue($row, ['drug_allergy_status', 'drug_allergy_details', 'adr_history']);
|
||||
|
||||
$adviceText = strtolower(trim((string) ($row['advice'] ?? '')) . ' ' . trim((string) ($row['Prescriber_Remarks'] ?? '')));
|
||||
$hasRedFlags = strpos($adviceText, 'emergency') !== false
|
||||
|| strpos($adviceText, 'warning') !== false
|
||||
|| strpos($adviceText, 'red flag') !== false
|
||||
|| strpos($adviceText, 'immediately') !== false
|
||||
|| strpos($adviceText, 'return if') !== false;
|
||||
|
||||
// If OPD prescription table is unavailable for the row, mark item as not applicable by treating as empty.
|
||||
if ($availableCols === []) {
|
||||
$hasComplaints = false;
|
||||
$hasDiagnosis = false;
|
||||
$hasClinicalCourse = false;
|
||||
$hasProcedure = false;
|
||||
$hasCondition = false;
|
||||
$hasMedicationPlan = false;
|
||||
$hasFollowUp = false;
|
||||
$hasAllergyAdr = false;
|
||||
$hasRedFlags = false;
|
||||
}
|
||||
|
||||
$items = [
|
||||
['label' => 'Reason for consultation / presenting complaints', 'ok' => $hasComplaints, 'critical' => true],
|
||||
['label' => 'Diagnosis documented', 'ok' => $hasDiagnosis, 'critical' => true],
|
||||
['label' => 'Clinical findings / assessment documented', 'ok' => $hasClinicalCourse, 'critical' => true],
|
||||
['label' => 'Investigations/procedures documented (if applicable)', 'ok' => $hasProcedure, 'critical' => false],
|
||||
['label' => 'Clinical condition / vitals documented', 'ok' => $hasCondition, 'critical' => true],
|
||||
['label' => 'Treatment/medication plan documented', 'ok' => $hasMedicationPlan, 'critical' => true],
|
||||
['label' => 'Follow-up or referral plan documented', 'ok' => $hasFollowUp, 'critical' => true],
|
||||
['label' => 'Drug allergy / ADR history documented', 'ok' => $hasAllergyAdr, 'critical' => false],
|
||||
['label' => 'Red-flag / emergency return advice documented', 'ok' => $hasRedFlags, 'critical' => false],
|
||||
];
|
||||
|
||||
$criticalMissing = [];
|
||||
$okCount = 0;
|
||||
foreach ($items as $item) {
|
||||
if (! empty($item['ok'])) {
|
||||
$okCount++;
|
||||
continue;
|
||||
}
|
||||
if (! empty($item['critical'])) {
|
||||
$criticalMissing[] = (string) ($item['label'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'ok_count' => $okCount,
|
||||
'total_count' => count($items),
|
||||
'critical_missing' => $criticalMissing,
|
||||
'critical_missing_count' => count($criticalMissing),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveExistingColumn(array $fields, array $candidates): ?string
|
||||
{
|
||||
foreach ($candidates as $candidate) {
|
||||
if (in_array($candidate, $fields, true)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function rowHasAnyNonEmptyValue(array $row, array $fields): bool
|
||||
{
|
||||
foreach ($fields as $field) {
|
||||
$value = trim((string) ($row[$field] ?? ''));
|
||||
if ($value !== '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function tableHasAnyNonEmptyValueByIpd(string $table, int $ipdId, array $preferredFields): bool
|
||||
{
|
||||
if ($ipdId <= 0 || ! $this->db->tableExists($table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames($table) ?? [];
|
||||
if (! in_array('ipd_id', $fields, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$selected = [];
|
||||
foreach ($preferredFields as $field) {
|
||||
if (in_array($field, $fields, true)) {
|
||||
$selected[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if ($selected === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rows = $this->db->table($table)
|
||||
->select(implode(',', $selected))
|
||||
->where('ipd_id', $ipdId)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if ($this->rowHasAnyNonEmptyValue($row, $selected)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function firstNonEmptyValueByIpd(string $table, int $ipdId, array $preferredFields): string
|
||||
{
|
||||
if ($ipdId <= 0 || ! $this->db->tableExists($table)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames($table) ?? [];
|
||||
if (! in_array('ipd_id', $fields, true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$selected = [];
|
||||
foreach ($preferredFields as $field) {
|
||||
if (in_array($field, $fields, true)) {
|
||||
$selected[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if ($selected === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$builder = $this->db->table($table)->where('ipd_id', $ipdId);
|
||||
if (in_array('id', $fields, true)) {
|
||||
$builder->orderBy('id', 'DESC');
|
||||
}
|
||||
|
||||
$row = $builder->get(1)->getRowArray() ?? [];
|
||||
foreach ($selected as $field) {
|
||||
$value = trim((string) ($row[$field] ?? ''));
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getLatestOpdAllergySnapshotByPatient(int $patientId): array
|
||||
{
|
||||
if ($patientId <= 0 || ! $this->db->tableExists('opd_master') || ! $this->db->tableExists('opd_prescription')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$opdFields = $this->db->getFieldNames('opd_master') ?? [];
|
||||
$rxFields = $this->db->getFieldNames('opd_prescription') ?? [];
|
||||
|
||||
if (! in_array('opd_id', $opdFields, true) || ! in_array('p_id', $opdFields, true) || ! in_array('opd_id', $rxFields, true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$selects = [];
|
||||
foreach (['drug_allergy_status', 'drug_allergy_details', 'adr_history'] as $field) {
|
||||
if (in_array($field, $rxFields, true)) {
|
||||
$selects[] = 'r.' . $field;
|
||||
}
|
||||
}
|
||||
|
||||
if ($selects === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('opd_master o')
|
||||
->select(implode(',', $selects), false)
|
||||
->join('opd_prescription r', 'r.opd_id = o.opd_id', 'inner')
|
||||
->where('o.p_id', $patientId);
|
||||
|
||||
if (in_array('apointment_date', $opdFields, true)) {
|
||||
$builder->orderBy('o.apointment_date', 'DESC');
|
||||
}
|
||||
if (in_array('id', $rxFields, true)) {
|
||||
$builder->orderBy('r.id', 'DESC');
|
||||
}
|
||||
|
||||
$row = $builder->get(1)->getRowArray();
|
||||
return is_array($row) ? $row : [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class Template extends BaseController
|
|||
'template.ct',
|
||||
'template.mri',
|
||||
'template.echo',
|
||||
'template.discharge',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
|
@ -649,4 +650,197 @@ class Template extends BaseController
|
|||
'mstTestKey' => $mstTestKey,
|
||||
]);
|
||||
}
|
||||
|
||||
private function ensureDischargeTemplateTable(): void
|
||||
{
|
||||
if ($this->db->tableExists('ipd_discharge_templates')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS ipd_discharge_templates (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
template_name VARCHAR(120) NOT NULL,
|
||||
template_html LONGTEXT NOT NULL,
|
||||
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
||||
status TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
||||
|
||||
$this->db->query($sql);
|
||||
}
|
||||
|
||||
private function defaultDischargeTemplateHtml(): string
|
||||
{
|
||||
return '<h3 style="margin:0 0 8px 0;">Discharge Summary</h3>'
|
||||
. '<table style="width:100%;border-collapse:collapse;margin-bottom:10px;" border="1" cellpadding="6">'
|
||||
. '<tr>'
|
||||
. '<td><b>Patient</b>: {{PATIENT_NAME}}</td>'
|
||||
. '<td><b>UHID</b>: {{UHID}}</td>'
|
||||
. '<td><b>IPD</b>: {{IPD_CODE}}</td>'
|
||||
. '</tr>'
|
||||
. '<tr>'
|
||||
. '<td><b>Age/Gender</b>: {{AGE_GENDER}}</td>'
|
||||
. '<td><b>Admit Date</b>: {{ADMIT_DATE}}</td>'
|
||||
. '<td><b>Discharge Date</b>: {{DISCHARGE_DATE}}</td>'
|
||||
. '</tr>'
|
||||
. '</table>'
|
||||
. '<div>{{CONTENT}}</div>';
|
||||
}
|
||||
|
||||
private function nabhDischargeTemplateHtml(): string
|
||||
{
|
||||
return '<h2 style="margin:0 0 10px 0;text-align:center;">DISCHARGE SUMMARY</h2>'
|
||||
. '<table style="width:100%;border-collapse:collapse;margin-bottom:10px;" border="1" cellpadding="6">'
|
||||
. '<tr>'
|
||||
. '<td><b>Patient Name</b>: {{PATIENT_NAME}}</td>'
|
||||
. '<td><b>UHID</b>: {{UHID}}</td>'
|
||||
. '<td><b>IPD No.</b>: {{IPD_CODE}}</td>'
|
||||
. '</tr>'
|
||||
. '<tr>'
|
||||
. '<td><b>Age/Gender</b>: {{AGE_GENDER}}</td>'
|
||||
. '<td><b>Date of Admission</b>: {{ADMIT_DATE}}</td>'
|
||||
. '<td><b>Date of Discharge</b>: {{DISCHARGE_DATE}}</td>'
|
||||
. '</tr>'
|
||||
. '<tr>'
|
||||
. '<td colspan="3"><b>Prepared On</b>: {{CURRENT_DATE}}</td>'
|
||||
. '</tr>'
|
||||
. '</table>'
|
||||
. '<div style="font-size:11px;color:#334155;margin-bottom:10px;">'
|
||||
. 'NABH guidance note: Ensure diagnosis, procedures, clinical course, condition at discharge, medication with dose/duration, follow-up advice, red-flag signs, and emergency contact are documented.'
|
||||
. '</div>'
|
||||
. '<div style="margin-bottom:10px;">{{CONTENT}}</div>'
|
||||
. '<h4 style="margin:12px 0 6px 0;">Counselling & Handover Confirmation</h4>'
|
||||
. '<table style="width:100%;border-collapse:collapse;margin-bottom:10px;" border="1" cellpadding="6">'
|
||||
. '<tr><td style="width:32%;">Medication explained to patient/attendant</td><td style="width:8%;"></td><td style="width:60%;">Remarks:</td></tr>'
|
||||
. '<tr><td>Follow-up date and department explained</td><td></td><td>Next Visit: __________________</td></tr>'
|
||||
. '<tr><td>Red-flag symptoms explained</td><td></td><td>Emergency Contact: __________________</td></tr>'
|
||||
. '<tr><td>Diet and activity instructions explained</td><td></td><td></td></tr>'
|
||||
. '</table>'
|
||||
. '<table style="width:100%;border-collapse:collapse;margin-top:20px;" border="1" cellpadding="10">'
|
||||
. '<tr>'
|
||||
. '<td style="width:33%;vertical-align:bottom;">____________________<br>Consultant Name/Signature</td>'
|
||||
. '<td style="width:33%;vertical-align:bottom;">____________________<br>Medical Officer Signature</td>'
|
||||
. '<td style="width:34%;vertical-align:bottom;">____________________<br>Patient/Attendant Signature & Date</td>'
|
||||
. '</tr>'
|
||||
. '</table>';
|
||||
}
|
||||
|
||||
private function ensureDefaultDischargeTemplateSeeded(): void
|
||||
{
|
||||
$this->ensureDischargeTemplateTable();
|
||||
if (! $this->db->tableExists('ipd_discharge_templates')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->db->table('ipd_discharge_templates');
|
||||
$count = (int) ($table->countAllResults() ?? 0);
|
||||
if ($count === 0) {
|
||||
$table->insert([
|
||||
'template_name' => 'Default Discharge Template',
|
||||
'template_html' => $this->defaultDischargeTemplateHtml(),
|
||||
'is_default' => 1,
|
||||
'status' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
$nabhExists = $this->db->table('ipd_discharge_templates')
|
||||
->where('template_name', 'NABH Compliant Discharge Summary')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
|
||||
if (empty($nabhExists)) {
|
||||
$table->insert([
|
||||
'template_name' => 'NABH Compliant Discharge Summary',
|
||||
'template_html' => $this->nabhDischargeTemplateHtml(),
|
||||
'is_default' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function discharge_templates()
|
||||
{
|
||||
if ($resp = $this->requireAnyPermission(['template.discharge'])) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$this->ensureDefaultDischargeTemplateSeeded();
|
||||
|
||||
$notice = '';
|
||||
$noticeType = 'success';
|
||||
|
||||
if (strtolower($this->request->getMethod()) === 'post') {
|
||||
$id = (int) ($this->request->getPost('id') ?? 0);
|
||||
$templateName = trim((string) ($this->request->getPost('template_name') ?? ''));
|
||||
$templateHtml = (string) ($this->request->getPost('template_html') ?? '');
|
||||
$isDefault = (int) ($this->request->getPost('is_default') ?? 0) === 1 ? 1 : 0;
|
||||
$status = (int) ($this->request->getPost('status') ?? 1) === 1 ? 1 : 0;
|
||||
|
||||
if ($templateName === '' || trim($templateHtml) === '') {
|
||||
$notice = 'Template name and template HTML are required.';
|
||||
$noticeType = 'danger';
|
||||
} else {
|
||||
$table = $this->db->table('ipd_discharge_templates');
|
||||
$data = [
|
||||
'template_name' => $templateName,
|
||||
'template_html' => $templateHtml,
|
||||
'is_default' => $isDefault,
|
||||
'status' => $status,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$table->where('id', $id)->update($data);
|
||||
$notice = 'Template updated.';
|
||||
} else {
|
||||
$table->insert($data);
|
||||
$id = (int) $this->db->insertID();
|
||||
$notice = 'Template created.';
|
||||
}
|
||||
|
||||
if ($isDefault === 1 && $id > 0) {
|
||||
$this->db->table('ipd_discharge_templates')
|
||||
->where('id <>', $id)
|
||||
->update(['is_default' => 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$editId = (int) ($this->request->getGet('edit') ?? 0);
|
||||
$editRow = [];
|
||||
if ($editId > 0) {
|
||||
$editRow = $this->db->table('ipd_discharge_templates')
|
||||
->where('id', $editId)
|
||||
->get(1)
|
||||
->getRowArray() ?? [];
|
||||
}
|
||||
|
||||
$rows = $this->db->table('ipd_discharge_templates')
|
||||
->orderBy('is_default', 'DESC')
|
||||
->orderBy('id', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return view('Setting/Template/discharge_templates', [
|
||||
'rows' => $rows,
|
||||
'edit_row' => $editRow,
|
||||
'notice' => $notice,
|
||||
'notice_type' => $noticeType,
|
||||
]);
|
||||
}
|
||||
|
||||
public function discharge_template_delete(int $id)
|
||||
{
|
||||
if ($resp = $this->requireAnyPermission(['template.discharge'])) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$id = (int) $id;
|
||||
if ($id > 0 && $this->db->tableExists('ipd_discharge_templates')) {
|
||||
$this->db->table('ipd_discharge_templates')->where('id', $id)->delete();
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('setting/template/discharge_templates'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateIpdDischargeTemplatesTable extends Migration
|
||||
{
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
$row = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getRowArray();
|
||||
|
||||
return ! empty($row);
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if ($this->hasTable('ipd_discharge_templates')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'template_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => false,
|
||||
],
|
||||
'template_html' => [
|
||||
'type' => 'LONGTEXT',
|
||||
'null' => false,
|
||||
],
|
||||
'is_default' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 1,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('is_default');
|
||||
$this->forge->addKey('status');
|
||||
$this->forge->createTable('ipd_discharge_templates', true);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropTable('ipd_discharge_templates', true);
|
||||
}
|
||||
}
|
||||
74
app/Database/Seeds/IndianNameListSeeder.php
Normal file
74
app/Database/Seeds/IndianNameListSeeder.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class IndianNameListSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if (! $this->db->tableExists('name_list')) {
|
||||
$forge = \Config\Database::forge();
|
||||
$forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
'null' => false,
|
||||
],
|
||||
]);
|
||||
$forge->addKey('id', true);
|
||||
$forge->addKey('name');
|
||||
$forge->createTable('name_list', true);
|
||||
}
|
||||
|
||||
$names = [
|
||||
'Aarav', 'Aarush', 'Aditi', 'Akanksha', 'Akash', 'Akhil', 'Alok', 'Aman', 'Amar', 'Amit',
|
||||
'Amrita', 'Anand', 'Ananya', 'Anil', 'Anjali', 'Ankit', 'Ankur', 'Ansh', 'Anshika', 'Anuj',
|
||||
'Arjun', 'Arnav', 'Arun', 'Aryan', 'Asha', 'Ashish', 'Ayush', 'Bhavna', 'Bhavesh', 'Bhavya',
|
||||
'Chandni', 'Charu', 'Chetan', 'Darpan', 'Deepa', 'Deepak', 'Dev', 'Devansh', 'Devesh', 'Dhruv',
|
||||
'Diksha', 'Divya', 'Gaurav', 'Geeta', 'Gopal', 'Govind', 'Harish', 'Harsh', 'Harshita', 'Himanshu',
|
||||
'Isha', 'Ishaan', 'Jatin', 'Jaya', 'Jyoti', 'Kabir', 'Kajal', 'Karan', 'Kartik', 'Kavita',
|
||||
'Khushi', 'Kiran', 'Komal', 'Krishna', 'Kunal', 'Kusum', 'Lakshmi', 'Lokesh', 'Madhav', 'Madhuri',
|
||||
'Mahesh', 'Manav', 'Manish', 'Meera', 'Megha', 'Mohan', 'Mohit', 'Mukesh', 'Nandini', 'Naveen',
|
||||
'Neeraj', 'Neha', 'Nikhil', 'Nilesh', 'Niraj', 'Nisha', 'Nitin', 'Pankaj', 'Pooja', 'Pradeep',
|
||||
'Prakash', 'Pranav', 'Prashant', 'Pratik', 'Preeti', 'Priya', 'Rahul', 'Raj', 'Rajat', 'Rajesh',
|
||||
'Rakesh', 'Rani', 'Ravi', 'Rekha', 'Ritika', 'Rohan', 'Rohit', 'Sahil', 'Sakshi', 'Sameer',
|
||||
'Sandeep', 'Sanjay', 'Sanjana', 'Sarthak', 'Satish', 'Seema', 'Shalini', 'Shankar', 'Shivam', 'Shreya',
|
||||
'Shruti', 'Simran', 'Sonali', 'Sourabh', 'Suhani', 'Suman', 'Sunil', 'Suraj', 'Suresh', 'Swati',
|
||||
'Tanvi', 'Tarun', 'Uday', 'Vaibhav', 'Varun', 'Vikas', 'Vinay', 'Vineet', 'Vishal', 'Vivek',
|
||||
'Yash', 'Yashika', 'Yogesh', 'Zoya',
|
||||
'Aggarwal', 'Agarwal', 'Arora', 'Bansal', 'Bhat', 'Bhatt', 'Bhardwaj', 'Chauhan', 'Chawla', 'Das',
|
||||
'Dubey', 'Dwivedi', 'Garg', 'Goswami', 'Gupta', 'Jain', 'Joshi', 'Kapoor', 'Khanna', 'Khan',
|
||||
'Kumar', 'Mishra', 'Nair', 'Pandey', 'Patel', 'Rao', 'Rawat', 'Saha', 'Saxena', 'Shah',
|
||||
'Sharma', 'Shukla', 'Singh', 'Srivastava', 'Thakur', 'Tripathi', 'Tyagi', 'Taygi', 'Varma', 'Verma',
|
||||
'Yadav'
|
||||
];
|
||||
|
||||
$existingRows = $this->db->table('name_list')->select('name')->get()->getResultArray();
|
||||
$existing = [];
|
||||
foreach ($existingRows as $row) {
|
||||
$existing[strtolower(trim((string) ($row['name'] ?? '')))] = true;
|
||||
}
|
||||
|
||||
$batch = [];
|
||||
foreach ($names as $name) {
|
||||
$key = strtolower($name);
|
||||
if (isset($existing[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$batch[] = ['name' => $name];
|
||||
}
|
||||
|
||||
if (! empty($batch)) {
|
||||
$this->db->table('name_list')->insertBatch($batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -215,6 +215,101 @@ class IpdBillingModel extends Model
|
|||
->getResult();
|
||||
}
|
||||
|
||||
public function getDoctorVisitFeeTypes(): array
|
||||
{
|
||||
if (! $this->db->tableExists('doc_ipd_fee_type')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->db->table('doc_ipd_fee_type')
|
||||
->select('id, fee_type')
|
||||
->orderBy('fee_type', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
public function getDoctorVisitFeeMap(array $doctorIds): array
|
||||
{
|
||||
if (empty($doctorIds) || ! $this->db->tableExists('doc_ipd_fee') || ! $this->db->tableExists('doc_ipd_fee_type')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('doc_ipd_fee d')
|
||||
->select('d.doc_id, d.doc_fee_type as fee_type_id, d.doc_fee_desc, d.amount, t.fee_type')
|
||||
->join('doc_ipd_fee_type t', 't.id = d.doc_fee_type', 'left')
|
||||
->whereIn('d.doc_id', array_map('intval', $doctorIds));
|
||||
|
||||
if ($this->db->fieldExists('isdelete', 'doc_ipd_fee')) {
|
||||
$builder->where('d.isdelete', 0);
|
||||
}
|
||||
|
||||
$rows = $builder->orderBy('d.id', 'ASC')->get()->getResultArray();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$docId = (int) ($row['doc_id'] ?? 0);
|
||||
$feeTypeId = (int) ($row['fee_type_id'] ?? 0);
|
||||
if ($docId <= 0 || $feeTypeId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$feeType = (string) ($row['fee_type'] ?? '');
|
||||
$feeDesc = trim((string) ($row['doc_fee_desc'] ?? ''));
|
||||
if ($feeDesc === '') {
|
||||
$feeDesc = $feeType;
|
||||
}
|
||||
|
||||
$map[$docId][$feeTypeId] = [
|
||||
'fee_type_id' => $feeTypeId,
|
||||
'fee_type' => $feeType,
|
||||
'fee_desc' => $feeDesc,
|
||||
'amount' => (float) ($row['amount'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function getDoctorVisitFeeDetail(int $docId, int $feeTypeId = 0): ?array
|
||||
{
|
||||
if ($docId <= 0 || ! $this->db->tableExists('doc_ipd_fee') || ! $this->db->tableExists('doc_ipd_fee_type')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('doc_ipd_fee d')
|
||||
->select('d.id, d.doc_id, d.doc_fee_type as fee_type_id, d.doc_fee_desc, d.amount, t.fee_type')
|
||||
->join('doc_ipd_fee_type t', 't.id = d.doc_fee_type', 'left')
|
||||
->where('d.doc_id', $docId);
|
||||
|
||||
if ($this->db->fieldExists('isdelete', 'doc_ipd_fee')) {
|
||||
$builder->where('d.isdelete', 0);
|
||||
}
|
||||
|
||||
if ($feeTypeId > 0) {
|
||||
$builder->where('d.doc_fee_type', $feeTypeId);
|
||||
}
|
||||
|
||||
$row = $builder->orderBy('d.id', 'ASC')->get()->getRowArray();
|
||||
if (! is_array($row) || empty($row)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$feeType = (string) ($row['fee_type'] ?? '');
|
||||
$feeDesc = trim((string) ($row['doc_fee_desc'] ?? ''));
|
||||
if ($feeDesc === '') {
|
||||
$feeDesc = $feeType;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'doc_id' => (int) ($row['doc_id'] ?? 0),
|
||||
'fee_type_id' => (int) ($row['fee_type_id'] ?? 0),
|
||||
'fee_type' => $feeType,
|
||||
'fee_desc' => $feeDesc,
|
||||
'amount' => (float) ($row['amount'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBankPaymentSources(): array
|
||||
{
|
||||
return $this->db->table('hospital_bank m')
|
||||
|
|
@ -387,6 +482,26 @@ class IpdBillingModel extends Model
|
|||
->getResult();
|
||||
}
|
||||
|
||||
public function getNursingChargeHistory(int $ipdId): array
|
||||
{
|
||||
if (! $this->db->tableExists('nursing_ipd_charges')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('nursing_ipd_charges')
|
||||
->select('charge_id, item_name, item_type, doctor_name, visit_date, visit_time, qty, rate, amount, remarks, include_in_bill')
|
||||
->where('ipd_id', $ipdId);
|
||||
|
||||
if ($this->db->fieldExists('include_in_bill', 'nursing_ipd_charges')) {
|
||||
$builder->where('include_in_bill', 1);
|
||||
}
|
||||
|
||||
return $builder
|
||||
->orderBy('charge_id', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
private function baseIpdListQuery()
|
||||
{
|
||||
$builder = $this->db->table('ipd_master i');
|
||||
|
|
|
|||
|
|
@ -296,11 +296,35 @@ class IpdModel extends Model
|
|||
return 0;
|
||||
}
|
||||
|
||||
$nursingChargeId = $this->extractNursingChargeIdFromComment((string) ($row['comment'] ?? ''));
|
||||
if ($nursingChargeId > 0 && $this->db->tableExists('nursing_ipd_charges')) {
|
||||
$nursingFields = $this->db->getFieldNames('nursing_ipd_charges');
|
||||
$nursingUpdate = [];
|
||||
if (in_array('include_in_bill', $nursingFields, true)) {
|
||||
$nursingUpdate['include_in_bill'] = 0;
|
||||
}
|
||||
if (in_array('updated_by', $nursingFields, true)) {
|
||||
$user = $this->getUserIdentity();
|
||||
$nursingUpdate['updated_by'] = $user['name'] . ' [' . $user['id'] . '] D:' . date('Y-m-d H:i:s');
|
||||
}
|
||||
if (! empty($nursingUpdate)) {
|
||||
$this->db->table('nursing_ipd_charges')
|
||||
->where('charge_id', $nursingChargeId)
|
||||
->update($nursingUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
$user = $this->getUserIdentity();
|
||||
$row['update_by_id'] = $user['id'];
|
||||
$row['update_by'] = $user['name'];
|
||||
$row['update_action'] = '1';
|
||||
|
||||
// Keep only columns that exist in the audit table to avoid schema drift failures.
|
||||
$auditTableFields = $this->db->getFieldNames('ipd_invoice_item_update');
|
||||
if (! empty($auditTableFields)) {
|
||||
$row = array_intersect_key($row, array_flip($auditTableFields));
|
||||
}
|
||||
|
||||
$inserted = $this->db->table('ipd_invoice_item_update')->insert($row);
|
||||
if (! $inserted) {
|
||||
return 0;
|
||||
|
|
@ -310,6 +334,10 @@ class IpdModel extends Model
|
|||
->where('id', $id)
|
||||
->delete();
|
||||
|
||||
if ($this->db->affectedRows() < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -409,6 +437,19 @@ class IpdModel extends Model
|
|||
];
|
||||
}
|
||||
|
||||
private function extractNursingChargeIdFromComment(string $comment): int
|
||||
{
|
||||
if ($comment === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (preg_match('/\[NURSING_CHARGE_ID:(\d+)\]/', $comment, $matches) !== 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) ($matches[1] ?? 0);
|
||||
}
|
||||
|
||||
private function getUserSignature(): string
|
||||
{
|
||||
$user = $this->getUserIdentity();
|
||||
|
|
|
|||
|
|
@ -33,4 +33,54 @@ class OrganizationCaseModel extends Model
|
|||
->where('id', $id)
|
||||
->update($data);
|
||||
}
|
||||
|
||||
public function getInvoiceRowsAndTotal(int $caseId): array
|
||||
{
|
||||
$sql1 = "select c.id AS id,c.case_id_code AS case_id_code,c.p_id AS p_id,
|
||||
o.apointment_date AS s_Date,'OPD' AS Charge_type,0 AS Charge_type_id,
|
||||
o.opd_id AS item_id,0 AS master_item_id,o.apointment_date AS Adate,
|
||||
o.opd_fee_amount AS item_rate,1 AS item_qty,
|
||||
date_format(o.apointment_date,'%d-%m-%Y') AS str_date,
|
||||
concat('OPD Charge: Dr. ',o.doc_name) AS Description,
|
||||
o.opd_code AS Code,o.opd_fee_amount AS Amount,'1' AS orgcode
|
||||
from opd_master o
|
||||
join organization_case_master c on o.insurance_case_id = c.id
|
||||
where o.opd_status in (1,2) and c.id=" . $caseId .
|
||||
" order by Adate";
|
||||
$showinvoice1 = $this->db->query($sql1)->getResult();
|
||||
|
||||
$sql2 = "select c.id AS id,c.case_id_code AS case_id_code,c.p_id AS p_id,
|
||||
i.inv_date AS s_Date,l.group_desc AS Charge_type,l.itype_id AS Charge_type_id,
|
||||
t.id AS item_id,t.item_id AS master_item_id,i.inv_date AS Adate,
|
||||
t.item_rate AS item_rate,t.item_qty AS item_qty,
|
||||
date_format(i.inv_date,'%d-%m-%Y') AS str_date,
|
||||
concat(t.item_name) AS Description,i.invoice_code AS Code,
|
||||
t.item_amount AS Amount,t.org_code AS orgcode
|
||||
from invoice_master i
|
||||
join organization_case_master c on i.insurance_case_id = c.id
|
||||
join invoice_item t on t.inv_master_id = i.id
|
||||
join hc_item_type l on t.item_type = l.itype_id
|
||||
left join hc_items_insurance it on t.item_id = it.hc_items_id and i.insurance_id = it.hc_insurance_id
|
||||
where i.ipd_include = 1 and i.invoice_status = 1 and c.id=" . $caseId .
|
||||
" order by Adate";
|
||||
$showinvoice2 = $this->db->query($sql2)->getResult();
|
||||
|
||||
$rows = array_merge($showinvoice1, $showinvoice2);
|
||||
usort($rows, static function ($a, $b): int {
|
||||
$left = strtotime((string) ($a->s_Date ?? $a->Adate ?? ''));
|
||||
$right = strtotime((string) ($b->s_Date ?? $b->Adate ?? ''));
|
||||
|
||||
return $left <=> $right;
|
||||
});
|
||||
|
||||
$total = 0.0;
|
||||
foreach ($rows as $row) {
|
||||
$total += (float) ($row->Amount ?? 0);
|
||||
}
|
||||
|
||||
return [
|
||||
'rows' => $rows,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,87 @@ class PatientModel extends Model
|
|||
return $rowSet;
|
||||
}
|
||||
|
||||
public function getNameSuggestions(string $q): array
|
||||
{
|
||||
$term = strtolower(trim($q));
|
||||
if ($term === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
if ($this->db->tableExists('name_list')) {
|
||||
try {
|
||||
$rows = $this->db->table('name_list')
|
||||
->select('name')
|
||||
->like('name', $term, 'after')
|
||||
->orderBy('name', 'ASC')
|
||||
->limit(20)
|
||||
->get()
|
||||
->getResultArray();
|
||||
} catch (\Throwable $e) {
|
||||
$rows = [];
|
||||
}
|
||||
}
|
||||
|
||||
$rowSet = [];
|
||||
foreach ($rows as $row) {
|
||||
$name = trim((string) ($row['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowSet[] = [
|
||||
'label' => $name,
|
||||
'value' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($rowSet)) {
|
||||
return $rowSet;
|
||||
}
|
||||
|
||||
$fallback = [
|
||||
'Aarav', 'Aarush', 'Aditi', 'Akanksha', 'Akash', 'Akhil', 'Alok', 'Aman', 'Amar', 'Amit',
|
||||
'Amrita', 'Anand', 'Ananya', 'Anil', 'Anjali', 'Ankit', 'Ankur', 'Ansh', 'Anshika', 'Anuj',
|
||||
'Arjun', 'Arnav', 'Arun', 'Aryan', 'Asha', 'Ashish', 'Ayush', 'Bhavna', 'Bhavesh', 'Bhavya',
|
||||
'Chandni', 'Charu', 'Chetan', 'Darpan', 'Deepa', 'Deepak', 'Dev', 'Devansh', 'Devesh', 'Dhruv',
|
||||
'Diksha', 'Divya', 'Gaurav', 'Geeta', 'Gopal', 'Govind', 'Harish', 'Harsh', 'Harshita', 'Himanshu',
|
||||
'Isha', 'Ishaan', 'Jatin', 'Jaya', 'Jyoti', 'Kabir', 'Kajal', 'Karan', 'Kartik', 'Kavita',
|
||||
'Khushi', 'Kiran', 'Komal', 'Krishna', 'Kunal', 'Kusum', 'Lakshmi', 'Lokesh', 'Madhav', 'Madhuri',
|
||||
'Mahesh', 'Manav', 'Manish', 'Meera', 'Megha', 'Mohan', 'Mohit', 'Mukesh', 'Nandini', 'Naveen',
|
||||
'Neeraj', 'Neha', 'Nikhil', 'Nilesh', 'Niraj', 'Nisha', 'Nitin', 'Pankaj', 'Pooja', 'Pradeep',
|
||||
'Prakash', 'Pranav', 'Prashant', 'Pratik', 'Preeti', 'Priya', 'Rahul', 'Raj', 'Rajat', 'Rajesh',
|
||||
'Rakesh', 'Rani', 'Ravi', 'Rekha', 'Ritika', 'Rohan', 'Rohit', 'Sahil', 'Sakshi', 'Sameer',
|
||||
'Sandeep', 'Sanjay', 'Sanjana', 'Sarthak', 'Satish', 'Seema', 'Shalini', 'Shankar', 'Shivam', 'Shreya',
|
||||
'Shruti', 'Simran', 'Sonali', 'Sourabh', 'Suhani', 'Suman', 'Sunil', 'Suraj', 'Suresh', 'Swati',
|
||||
'Tanvi', 'Tarun', 'Uday', 'Vaibhav', 'Varun', 'Vikas', 'Vinay', 'Vineet', 'Vishal', 'Vivek',
|
||||
'Yash', 'Yashika', 'Yogesh', 'Zoya',
|
||||
'Aggarwal', 'Agarwal', 'Arora', 'Bansal', 'Bhat', 'Bhatt', 'Bhardwaj', 'Chauhan', 'Chawla', 'Das',
|
||||
'Dubey', 'Dwivedi', 'Garg', 'Goswami', 'Gupta', 'Jain', 'Joshi', 'Kapoor', 'Khanna', 'Khan',
|
||||
'Kumar', 'Mishra', 'Nair', 'Pandey', 'Patel', 'Rao', 'Rawat', 'Saha', 'Saxena', 'Shah',
|
||||
'Sharma', 'Shukla', 'Singh', 'Srivastava', 'Thakur', 'Tripathi', 'Tyagi', 'Taygi', 'Varma', 'Verma',
|
||||
'Yadav'
|
||||
];
|
||||
|
||||
$result = [];
|
||||
foreach ($fallback as $name) {
|
||||
if (stripos($name, $term) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'label' => $name,
|
||||
'value' => $name,
|
||||
];
|
||||
|
||||
if (count($result) >= 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function insertRemark(array $data): int
|
||||
{
|
||||
$builder = $this->db->table('patient_remark');
|
||||
|
|
|
|||
|
|
@ -63,12 +63,9 @@ $('#updatereport').click(function(){
|
|||
{
|
||||
var csrf_name = '<?= csrf_token() ?>';
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
$.post('<?= base_url('billing/case/create_contingent_bill') ?>/'+caseid,{
|
||||
"case_id": caseid,
|
||||
[csrf_name]: csrf_value
|
||||
}, function(data){
|
||||
$.get('<?= base_url('Orgcase/create_contingent_bill') ?>/'+caseid, function(data){
|
||||
notify('success','Please Attention',data);
|
||||
load_form_div('<?= base_url('billing/case/contingent_bill') ?>/'+caseid,'org_CONTINGENT');
|
||||
load_form_div('<?= base_url('Orgcase/contingent_bill') ?>/'+caseid,'org_CONTINGENT');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -76,12 +73,13 @@ $('#updatereport').click(function(){
|
|||
|
||||
$('#showreport').click(function(){
|
||||
var caseid = $('#caseid').val();
|
||||
load_report_div('<?= base_url('billing/case/contingent_bill') ?>/'+caseid+'/1','org_CONTINGENT');
|
||||
var finalUrl = '<?= base_url('Orgcase/contingent_bill') ?>/' + caseid + '/1';
|
||||
window.open(finalUrl, '_blank');
|
||||
});
|
||||
|
||||
$('#editreport').click(function(){
|
||||
var caseid = $('#caseid').val();
|
||||
load_form_div('<?= base_url('billing/case/contingent_bill') ?>/'+caseid+'/0','org_CONTINGENT');
|
||||
load_form_div('<?= base_url('Orgcase/contingent_bill') ?>/'+caseid+'/0','org_CONTINGENT');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
<i class="bi bi-list"></i>
|
||||
Charge Groups
|
||||
</button>
|
||||
<button class="btn btn-outline-primary" type="button" onclick="load_form_div('<?= base_url('nursing-bedside-items') ?>','maindiv','Clinical Procedure / Test Master');">
|
||||
<i class="bi bi-heart-pulse"></i>
|
||||
Clinical Procedure/Test Master
|
||||
</button>
|
||||
<button onclick="load_form_div('<?= base_url('item-ipd/add') ?>','maindiv','New Charge');" type="button" class="btn btn-primary">Add New Charge</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@
|
|||
<span>IPD Charges</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('nursing-bedside-items') ?>','maindiv','Clinical Procedure / Test Master');">
|
||||
<i class="bi bi-heart-pulse"></i>
|
||||
<span>Clinical Procedure/Test</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('package/search') ?>','maindiv','Package IPD');">
|
||||
<i class="bi bi-collection"></i>
|
||||
|
|
|
|||
160
app/Views/Setting/Template/discharge_templates.php
Normal file
160
app/Views/Setting/Template/discharge_templates.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$edit = $edit_row ?? [];
|
||||
$notice = (string) ($notice ?? '');
|
||||
$noticeType = (string) ($notice_type ?? 'success');
|
||||
|
||||
$editId = (int) ($edit['id'] ?? 0);
|
||||
$templateName = (string) ($edit['template_name'] ?? '');
|
||||
$templateHtml = (string) ($edit['template_html'] ?? '<div>{{CONTENT}}</div>');
|
||||
$isDefault = (int) ($edit['is_default'] ?? 0);
|
||||
$status = (int) ($edit['status'] ?? 1);
|
||||
?>
|
||||
|
||||
<section class="content">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">IPD Discharge Templates</h6>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="javascript:load_form_div('<?= base_url('setting/template/discharge_templates') ?>','maindiv','IPD Discharge Template');">Reset</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($notice !== ''): ?>
|
||||
<div class="alert alert-<?= esc($noticeType) ?> py-2 mb-3"><?= esc($notice) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="alert alert-info py-2 small">
|
||||
Available placeholders: <code>{{CONTENT}}</code>, <code>{{PATIENT_NAME}}</code>, <code>{{UHID}}</code>, <code>{{IPD_CODE}}</code>,
|
||||
<code>{{AGE_GENDER}}</code>, <code>{{ADMIT_DATE}}</code>, <code>{{DISCHARGE_DATE}}</code>, <code>{{CURRENT_DATE}}</code>.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning py-2 small">
|
||||
NABH drafting checklist: include reason for admission, significant findings, diagnosis, procedures, course in hospital,
|
||||
condition at discharge, discharge medication with dose/duration, follow-up plan, and warning signs/emergency contact.
|
||||
</div>
|
||||
|
||||
<form method="post" action="<?= base_url('setting/template/discharge_templates') ?>" class="mb-4" id="discharge_template_form">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= $editId ?>">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small">Template Name</label>
|
||||
<input type="text" name="template_name" class="form-control form-control-sm" value="<?= esc($templateName) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small">Status</label>
|
||||
<select name="status" class="form-select form-select-sm">
|
||||
<option value="1" <?= $status === 1 ? 'selected' : '' ?>>Active</option>
|
||||
<option value="0" <?= $status === 0 ? 'selected' : '' ?>>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="form-check mb-1">
|
||||
<input class="form-check-input" type="checkbox" name="is_default" id="is_default" value="1" <?= $isDefault === 1 ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="is_default">Set as default</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small">Template HTML</label>
|
||||
<textarea name="template_html" id="template_html_editor" rows="12" class="form-control" required><?= esc($templateHtml) ?></textarea>
|
||||
<small class="text-muted">Use the editor to format layout. Use the <code>Source</code> button for raw HTML. Keep <code>{{CONTENT}}</code> in the template.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm"><?= $editId > 0 ? 'Update Template' : 'Create Template' ?></button>
|
||||
<?php if ($editId > 0): ?>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="javascript:load_form_div('<?= base_url('setting/template/discharge_templates') ?>','maindiv','IPD Discharge Template');">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:60px;">ID</th>
|
||||
<th>Template Name</th>
|
||||
<th style="width:90px;">Default</th>
|
||||
<th style="width:90px;">Status</th>
|
||||
<th style="width:180px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="5" class="text-center text-muted">No templates found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<tr>
|
||||
<td><?= (int) ($row['id'] ?? 0) ?></td>
|
||||
<td><?= esc((string) ($row['template_name'] ?? '')) ?></td>
|
||||
<td><?= (int) ($row['is_default'] ?? 0) === 1 ? 'Yes' : 'No' ?></td>
|
||||
<td><?= (int) ($row['status'] ?? 0) === 1 ? 'Active' : 'Inactive' ?></td>
|
||||
<td>
|
||||
<a class="btn btn-outline-primary btn-sm" href="javascript:load_form_div('<?= base_url('setting/template/discharge_templates?edit=' . (int) ($row['id'] ?? 0)) ?>','maindiv','IPD Discharge Template');">Edit</a>
|
||||
<a class="btn btn-outline-danger btn-sm" href="javascript:if(confirm('Delete this template?')) load_form_div('<?= base_url('setting/template/discharge_templates/delete/' . (int) ($row['id'] ?? 0)) ?>','maindiv','IPD Discharge Template');">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById('discharge_template_form');
|
||||
var editorFieldId = 'template_html_editor';
|
||||
|
||||
function initTemplateEditor() {
|
||||
if (!window.CKEDITOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
CKEDITOR.config.versionCheck = false;
|
||||
CKEDITOR.config.removePlugins = '';
|
||||
|
||||
if (CKEDITOR.instances[editorFieldId]) {
|
||||
CKEDITOR.instances[editorFieldId].destroy(true);
|
||||
}
|
||||
|
||||
if (document.getElementById(editorFieldId)) {
|
||||
CKEDITOR.replace(editorFieldId, {
|
||||
height: 360,
|
||||
toolbar: [
|
||||
{ name: 'document', items: ['Source'] },
|
||||
{ name: 'clipboard', items: ['Undo', 'Redo'] },
|
||||
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline', 'RemoveFormat'] },
|
||||
{ name: 'paragraph', items: ['NumberedList', 'BulletedList', 'Outdent', 'Indent', 'Blockquote'] },
|
||||
{ name: 'links', items: ['Link', 'Unlink'] },
|
||||
{ name: 'insert', items: ['Table', 'HorizontalRule', 'SpecialChar'] },
|
||||
{ name: 'styles', items: ['Format', 'Font', 'FontSize'] },
|
||||
{ name: 'colors', items: ['TextColor', 'BGColor'] },
|
||||
{ name: 'tools', items: ['Maximize'] }
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function syncTemplateEditor() {
|
||||
if (!window.CKEDITOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CKEDITOR.instances[editorFieldId]) {
|
||||
CKEDITOR.instances[editorFieldId].updateElement();
|
||||
}
|
||||
}
|
||||
|
||||
initTemplateEditor();
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
syncTemplateEditor();
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -6,13 +6,14 @@ $canXray = $user && method_exists($user, 'can') ? $user->can('template.xray') :
|
|||
$canCt = $user && method_exists($user, 'can') ? $user->can('template.ct') : false;
|
||||
$canMri = $user && method_exists($user, 'can') ? $user->can('template.mri') : false;
|
||||
$canEcho = $user && method_exists($user, 'can') ? $user->can('template.echo') : false;
|
||||
$canDischarge = $user && method_exists($user, 'can') ? $user->can('template.discharge') : false;
|
||||
$canOpdPrintTemplate = $user && method_exists($user, 'can') ? $user->can('billing.opd.edit') : false;
|
||||
|
||||
if (! $canOpdPrintTemplate && $user && method_exists($user, 'inGroup')) {
|
||||
$canOpdPrintTemplate = $user->inGroup('OPDEdit', 'admin', 'superadmin', 'developer');
|
||||
}
|
||||
|
||||
$hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $canMri || $canEcho;
|
||||
$hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $canMri || $canEcho || $canDischarge;
|
||||
?>
|
||||
<section class="content">
|
||||
<div class="pagetitle">
|
||||
|
|
@ -194,6 +195,15 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
|
|||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($canDischarge) { ?>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/discharge_templates') ?>','maindiv','IPD Discharge Template');">
|
||||
<span class="tile-icon"><i class="bi bi-file-earmark-medical"></i></span>
|
||||
<span>IPD Discharge Template</span>
|
||||
</a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@
|
|||
notify('error', 'Please Attention', data.error_text);
|
||||
$("#RegisterPatient").prop("disabled", false);
|
||||
} else {
|
||||
load_form('<?= base_url('billing/patient/person_record') ?>/' + data.insertid);
|
||||
load_form('<?= base_url('billing/patient/person_record') ?>/' + data.insertid, 'Patient Record');
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
|
|
@ -273,7 +273,7 @@
|
|||
});
|
||||
|
||||
$("#input_city").autocomplete({
|
||||
source: "<?= base_url('Patient/city') ?>",
|
||||
source: "<?= base_url('billing/patient/city') ?>",
|
||||
minLength: 3,
|
||||
autofocus: true,
|
||||
select: function(event, ui) {
|
||||
|
|
@ -325,7 +325,7 @@
|
|||
})
|
||||
.autocomplete({
|
||||
source: function(request, response) {
|
||||
$.getJSON("<?= base_url('Patient/get_name') ?>", {
|
||||
$.getJSON("<?= base_url('billing/patient/get_name') ?>", {
|
||||
term: extractLast(request.term)
|
||||
}, response);
|
||||
},
|
||||
|
|
@ -363,7 +363,7 @@
|
|||
})
|
||||
.autocomplete({
|
||||
source: function(request, response) {
|
||||
$.getJSON("<?= base_url('Patient/get_name') ?>", {
|
||||
$.getJSON("<?= base_url('billing/patient/get_name') ?>", {
|
||||
term: extractLast(request.term)
|
||||
}, response);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -104,7 +104,11 @@
|
|||
$checkbox_checked="";
|
||||
$age_input_1='style="display: none;"';
|
||||
$age_input_2='';
|
||||
$DateofBirth=MysqlDate_to_str($data[0]->dob);
|
||||
$DateofBirth='';
|
||||
if (!empty($data[0]->dob)) {
|
||||
$ts = strtotime((string) $data[0]->dob);
|
||||
$DateofBirth = $ts ? date('Y-m-d', $ts) : '';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="col-md-3">
|
||||
|
|
@ -138,14 +142,8 @@
|
|||
<div id="age_input_2" <?=$age_input_2?>>
|
||||
<div class="col-md-4">
|
||||
<label> Date of Birth</label>
|
||||
<div class="input-group date input-sm">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
<input class="form-control pull-right datepicker input-sm" name="datepicker_dob"
|
||||
id="datepicker_dob" type="text" data-inputmask="'alias': 'dd/mm/yyyy'" data-mask
|
||||
value="<?=$DateofBirth ?>" />
|
||||
</div>
|
||||
<input class="form-control input-sm" name="datepicker_dob"
|
||||
id="datepicker_dob" type="date" value="<?=$DateofBirth ?>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,8 +3,17 @@ $ipd = $ipd_info ?? null;
|
|||
$person = $person_info ?? null;
|
||||
$ipdId = (int) ($ipd_id ?? 0);
|
||||
$previewHtml = (string) ($content ?? '');
|
||||
$renderedHtml = (string) ($rendered_content ?? $previewHtml);
|
||||
$noticeText = (string) ($notice ?? '');
|
||||
$noticeType = (string) ($notice_type ?? 'success');
|
||||
$templateRows = $template_rows ?? [];
|
||||
$selectedTemplateId = (int) ($selected_template_id ?? 0);
|
||||
$nabhAudit = is_array($nabh_audit ?? null) ? $nabh_audit : [];
|
||||
$nabhItems = is_array($nabhAudit['items'] ?? null) ? $nabhAudit['items'] : [];
|
||||
$nabhCriticalMissing = is_array($nabhAudit['critical_missing'] ?? null) ? $nabhAudit['critical_missing'] : [];
|
||||
$nabhCriticalMissingCount = (int) ($nabhAudit['critical_missing_count'] ?? 0);
|
||||
$nabhOkCount = (int) ($nabhAudit['ok_count'] ?? 0);
|
||||
$nabhTotalCount = (int) ($nabhAudit['total_count'] ?? count($nabhItems));
|
||||
|
||||
$patientName = trim((string) ($person->p_fname ?? ''));
|
||||
$patientCode = trim((string) (
|
||||
|
|
@ -46,21 +55,77 @@ if ($person) {
|
|||
<div class="col-md-3"><strong>Discharge Date:</strong> <?= esc($ipd->str_discharge_date ?? '') ?></div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="<?= site_url('Ipd_discharge/preview_discharge_report/' . $ipdId) ?>">
|
||||
<?= csrf_field() ?>
|
||||
<div class="border rounded p-2 bg-white">
|
||||
<textarea id="editor_Discharge_Preview" name="editor_Discharge_Preview" rows="16" class="form-control"><?= esc($previewHtml) ?></textarea>
|
||||
<form method="get" action="<?= site_url('Ipd_discharge/preview_discharge_report/' . $ipdId) ?>" class="row g-2 mb-3">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<label class="form-label small mb-1"><strong>Discharge Template</strong></label>
|
||||
<select class="form-select form-select-sm" name="tpl" id="tpl_selector">
|
||||
<?php foreach ($templateRows as $tpl): ?>
|
||||
<?php $tplId = (int) ($tpl['id'] ?? 0); ?>
|
||||
<option value="<?= $tplId ?>" <?= $tplId === $selectedTemplateId ? 'selected' : '' ?>>
|
||||
<?= esc((string) ($tpl['template_name'] ?? ('Template #' . $tplId))) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-outline-primary btn-sm">Apply Template</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (! empty($nabhItems)): ?>
|
||||
<div class="card border-warning mb-3">
|
||||
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||||
<strong>NABH Audit Mode</strong>
|
||||
<span class="small">Completed: <?= $nabhOkCount ?>/<?= $nabhTotalCount ?></span>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
<?php if ($nabhCriticalMissingCount > 0): ?>
|
||||
<div class="alert alert-danger py-2 mb-2">
|
||||
<strong>Critical sections missing (<?= $nabhCriticalMissingCount ?>):</strong>
|
||||
<?= esc(implode(' | ', $nabhCriticalMissing)) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-success py-2 mb-2">All critical NABH checklist sections are present.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:55%;">Checklist Item</th>
|
||||
<th style="width:15%;">Type</th>
|
||||
<th style="width:30%;">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($nabhItems as $item): ?>
|
||||
<?php
|
||||
$ok = ! empty($item['ok']);
|
||||
$critical = ! empty($item['critical']);
|
||||
?>
|
||||
<tr>
|
||||
<td><?= esc((string) ($item['label'] ?? '')) ?></td>
|
||||
<td><?= $critical ? 'Critical' : 'Recommended' ?></td>
|
||||
<td><?= $ok ? 'OK' : ($critical ? 'Missing' : 'Needs review') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="border rounded p-3 bg-white" style="min-height: 420px; overflow:auto;">
|
||||
<?= $renderedHtml ?>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<button type="submit" class="btn btn-success">Save Preview</button>
|
||||
<button type="button" class="btn btn-primary" id="btn_create">Create Discharge Summary</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="btn_regen">Regenerate Summary</button>
|
||||
<a class="btn btn-primary" id="btn_create" href="<?= site_url('Ipd_discharge/ipd_select/' . $ipdId) ?>">Back to Create Discharge Summary</a>
|
||||
<button type="button" class="btn btn-danger" id="btn_show">Make File and Print on Letter Head</button>
|
||||
<button type="button" class="btn btn-danger" id="btn_show2">Make File and Print On Plain Paper</button>
|
||||
<button type="button" class="btn btn-outline-danger" id="btn_show3">New Print</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -72,59 +137,88 @@ if ($person) {
|
|||
}
|
||||
|
||||
function loadOrRedirect(url, title) {
|
||||
if (typeof load_form === 'function') {
|
||||
load_form(url, title || 'Discharge');
|
||||
return;
|
||||
}
|
||||
if (typeof load_form_div === 'function') {
|
||||
load_form_div(url, 'maindiv', title || 'Discharge');
|
||||
return;
|
||||
}
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
if (window.CKEDITOR) {
|
||||
CKEDITOR.config.versionCheck = false;
|
||||
// Global layout removes notification plugins; reset here to avoid dependency errors.
|
||||
CKEDITOR.config.removePlugins = '';
|
||||
if (CKEDITOR.instances.editor_Discharge_Preview) {
|
||||
CKEDITOR.instances.editor_Discharge_Preview.destroy(true);
|
||||
}
|
||||
CKEDITOR.replace('editor_Discharge_Preview', {
|
||||
height: 360
|
||||
});
|
||||
window.location.assign(url);
|
||||
}
|
||||
|
||||
var ipdId = <?= (int) $ipdId ?>;
|
||||
var nabhCriticalMissingCount = <?= (int) $nabhCriticalMissingCount ?>;
|
||||
var nabhCriticalMissingItems = <?= json_encode(array_values($nabhCriticalMissing), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
var btnCreate = document.getElementById('btn_create');
|
||||
var btnRegen = document.getElementById('btn_regen');
|
||||
var btnShow = document.getElementById('btn_show');
|
||||
var btnShow2 = document.getElementById('btn_show2');
|
||||
var btnShow3 = document.getElementById('btn_show3');
|
||||
|
||||
if (btnCreate) {
|
||||
btnCreate.addEventListener('click', function() {
|
||||
loadOrRedirect('<?= site_url('Ipd_discharge/ipd_select') ?>/' + ipdId, 'Create Discharge Summary');
|
||||
});
|
||||
function confirmNabhPrint() {
|
||||
if (nabhCriticalMissingCount <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (btnRegen) {
|
||||
btnRegen.addEventListener('click', function() {
|
||||
loadOrRedirect('<?= site_url('Ipd_discharge/preview_discharge_report') ?>/' + ipdId + '?regen=1', 'Discharge Preview');
|
||||
var detail = Array.isArray(nabhCriticalMissingItems) ? nabhCriticalMissingItems.join(' | ') : '';
|
||||
var message = 'NABH Audit: critical sections are missing.\n\n' + detail + '\n\nDo you still want to print?';
|
||||
return window.confirm(message);
|
||||
}
|
||||
|
||||
if (btnCreate) {
|
||||
btnCreate.addEventListener('click', function(e) {
|
||||
if (typeof load_form !== 'function' && typeof load_form_div !== 'function') {
|
||||
return;
|
||||
}
|
||||
// Prevent default only when SPA loader exists; otherwise native link works.
|
||||
e.preventDefault();
|
||||
loadOrRedirect('<?= site_url('Ipd_discharge/ipd_select') ?>/' + ipdId, 'Create Discharge Summary');
|
||||
});
|
||||
}
|
||||
|
||||
if (btnShow) {
|
||||
btnShow.addEventListener('click', function() {
|
||||
openUrl('<?= site_url('Ipd_discharge/show_discharge') ?>/' + ipdId + '/1');
|
||||
if (!confirmNabhPrint()) {
|
||||
return;
|
||||
}
|
||||
var tpl = document.getElementById('tpl_selector');
|
||||
var tplId = tpl ? parseInt(tpl.value || '0', 10) : 0;
|
||||
var url = '<?= site_url('Ipd_discharge/show_discharge') ?>/' + ipdId + '/1';
|
||||
if (tplId > 0) {
|
||||
url += '?tpl=' + tplId;
|
||||
}
|
||||
openUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnShow2) {
|
||||
btnShow2.addEventListener('click', function() {
|
||||
openUrl('<?= site_url('Ipd_discharge/show_discharge') ?>/' + ipdId + '/0');
|
||||
if (!confirmNabhPrint()) {
|
||||
return;
|
||||
}
|
||||
var tpl = document.getElementById('tpl_selector');
|
||||
var tplId = tpl ? parseInt(tpl.value || '0', 10) : 0;
|
||||
var url = '<?= site_url('Ipd_discharge/show_discharge') ?>/' + ipdId + '/0';
|
||||
if (tplId > 0) {
|
||||
url += '?tpl=' + tplId;
|
||||
}
|
||||
openUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnShow3) {
|
||||
btnShow3.addEventListener('click', function() {
|
||||
openUrl('<?= site_url('Ipd_discharge/show_file3') ?>/' + ipdId);
|
||||
if (!confirmNabhPrint()) {
|
||||
return;
|
||||
}
|
||||
var tpl = document.getElementById('tpl_selector');
|
||||
var tplId = tpl ? parseInt(tpl.value || '0', 10) : 0;
|
||||
var url = '<?= site_url('Ipd_discharge/show_file3') ?>/' + ipdId;
|
||||
if (tplId > 0) {
|
||||
url += '?tpl=' + tplId;
|
||||
}
|
||||
openUrl(url);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ $ipdId = (int) ($ipd_id ?? 0);
|
|||
$printType = (int) ($print_type ?? 1);
|
||||
$previewHtml = (string) ($content ?? '');
|
||||
|
||||
$patientName = trim((string) (($person->p_fname ?? '') . ' ' . ($person->p_lname ?? '')));
|
||||
if ($patientName === '') {
|
||||
$patientName = (string) ($person->p_fname ?? '');
|
||||
}
|
||||
$patientName = trim((string) ($person->p_fname ?? ''));
|
||||
$patientCode = trim((string) (
|
||||
$person->uhid
|
||||
?? $person->UHID
|
||||
?? $person->patient_code
|
||||
?? $person->p_code
|
||||
?? $person->reg_no
|
||||
?? ''
|
||||
));
|
||||
|
||||
$title = 'IPD Discharge Preview';
|
||||
if ($printType === 0) {
|
||||
|
|
@ -72,7 +77,7 @@ if ($printType === 0) {
|
|||
<div class="meta">
|
||||
<div><strong>IPD:</strong> <?= esc($ipd->ipd_code ?? $ipdId) ?></div>
|
||||
<div><strong>Patient:</strong> <?= esc($patientName) ?></div>
|
||||
<div><strong>UHID:</strong> <?= esc($person->p_code ?? '') ?></div>
|
||||
<div><strong>UHID:</strong> <?= esc($patientCode) ?></div>
|
||||
<div><strong>Date:</strong> <?= esc(date('d-m-Y h:i A')) ?></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ $defaultPackageId = $hasPackage ? (int) ($packages[0]->id ?? 0) : 0;
|
|||
$csrfName = csrf_token();
|
||||
$csrfHash = csrf_hash();
|
||||
$today = date('Y-m-d');
|
||||
$bedsideItemsByCategory = $bedside_items_by_category ?? [];
|
||||
$doctorVisitFeeTypes = $doctor_visit_fee_types ?? [];
|
||||
$doctorVisitFeeMap = $doctor_visit_fee_map ?? [];
|
||||
?>
|
||||
|
||||
<style>
|
||||
|
|
@ -154,6 +157,134 @@ $today = date('Y-m-d');
|
|||
<strong>Add Charges</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card mb-3 border-info">
|
||||
<div class="card-header py-2"><strong>Bedside Clinical / Nursing Charge</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Item Type</label>
|
||||
<select class="form-select form-select-sm" id="bedside_item_type">
|
||||
<option value="">All</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Category</label>
|
||||
<select class="form-select form-select-sm" id="bedside_category">
|
||||
<option value="">Select</option>
|
||||
<?php foreach (array_keys($bedsideItemsByCategory) as $categoryName) : ?>
|
||||
<option value="<?= esc($categoryName) ?>"><?= esc($categoryName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Item</label>
|
||||
<select class="form-select form-select-sm" id="bedside_item_id">
|
||||
<option value="">Select</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Quick Search</label>
|
||||
<input type="text" class="form-control form-control-sm" id="bedside_item_search" placeholder="Search by code or name">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Rate</label>
|
||||
<input class="form-control form-control-sm" id="bedside_item_rate" value="0" type="number" step="0.01" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Qty</label>
|
||||
<input class="form-control form-control-sm" id="bedside_item_qty" value="1" type="number" step="0.01" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Date</label>
|
||||
<input class="form-control form-control-sm" id="bedside_item_date" value="<?= esc($today) ?>" type="date" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Doctor</label>
|
||||
<select class="form-select form-select-sm" id="bedside_doc_id">
|
||||
<option value="0">NONE</option>
|
||||
<?php foreach ($doc_list ?? [] as $doc) : ?>
|
||||
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Comment</label>
|
||||
<input class="form-control form-control-sm" id="bedside_comment" value="" type="text" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdBedsideChargeAdd()">Add Bedside Charge</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3 border-primary">
|
||||
<div class="card-header py-2"><strong>Doctor Visit Charge</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Doctor</label>
|
||||
<select class="form-select form-select-sm" id="doctor_visit_doc_id">
|
||||
<option value="">Select</option>
|
||||
<?php foreach ($doc_list ?? [] as $doc) : ?>
|
||||
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Visit Type</label>
|
||||
<select class="form-select form-select-sm" id="doctor_visit_fee_type">
|
||||
<option value="">Select</option>
|
||||
<?php foreach ($doctorVisitFeeTypes as $row) : ?>
|
||||
<option value="<?= esc($row['id'] ?? 0) ?>"><?= esc($row['fee_type'] ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Rate</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_rate" value="0.00" type="number" step="0.01" />
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Description</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_fee_desc" value="" type="text" readonly />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Qty</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_qty" value="1" type="number" step="0.01" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Date</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_date" value="<?= esc($today) ?>" type="date" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Time</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_time" type="time" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Duration (min)</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_duration" value="0" type="number" min="0" step="1" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Hospital/Clinic</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_hospital" value="" type="text" />
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="form-check mb-1">
|
||||
<input class="form-check-input" type="checkbox" id="doctor_visit_outside" value="1">
|
||||
<label class="form-check-label" for="doctor_visit_outside">Outside Doctor</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Comment</label>
|
||||
<input class="form-control form-control-sm" id="doctor_visit_comment" value="" type="text" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdDoctorVisitChargeAdd()">Add Doctor Visit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion ipd-charge-accordion" id="ipdChargeAccordion">
|
||||
<?php
|
||||
$typeIndex = 0;
|
||||
|
|
@ -232,7 +363,23 @@ $today = date('Y-m-d');
|
|||
var ipdChargeCsrfHash = '<?= esc($csrfHash) ?>';
|
||||
var ipdChargeBaseUrl = '<?= base_url() ?>';
|
||||
var ipdChargeIpdId = <?= (int) $ipdId ?>;
|
||||
var ipdChargeTabRefreshUrl = '<?= base_url('billing/ipd/panel/' . $ipdId . '/tab/ipd-charges') ?>';
|
||||
var ipdChargeDefaultPackageId = <?= (int) $defaultPackageId ?>;
|
||||
var bedsideItemsByCategory = <?= json_encode($bedsideItemsByCategory, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
var doctorVisitFeeTypes = <?= json_encode($doctorVisitFeeTypes, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
var doctorVisitFeeMap = <?= json_encode($doctorVisitFeeMap, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
|
||||
function getAllBedsideItems() {
|
||||
var all = [];
|
||||
Object.keys(bedsideItemsByCategory || {}).forEach(function (categoryName) {
|
||||
var rows = bedsideItemsByCategory[categoryName] || [];
|
||||
rows.forEach(function (row) {
|
||||
all.push(row);
|
||||
});
|
||||
});
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
function ipdChargeSetRate(typeId) {
|
||||
var select = document.getElementById('itype_name_id_' + typeId);
|
||||
|
|
@ -245,6 +392,100 @@ $today = date('Y-m-d');
|
|||
rateInput.value = rate || 0;
|
||||
}
|
||||
|
||||
function bedsideRefreshTypeOptions() {
|
||||
var allItems = getAllBedsideItems();
|
||||
var selectedType = $('#bedside_item_type').val() || '';
|
||||
var types = {};
|
||||
|
||||
allItems.forEach(function (item) {
|
||||
var typeName = String(item.item_type || '').trim();
|
||||
if (typeName !== '') {
|
||||
types[typeName] = true;
|
||||
}
|
||||
});
|
||||
|
||||
var typeSelect = $('#bedside_item_type');
|
||||
typeSelect.empty().append('<option value="">All</option>');
|
||||
Object.keys(types).sort().forEach(function (typeName) {
|
||||
typeSelect.append('<option value="' + typeName + '">' + typeName + '</option>');
|
||||
});
|
||||
|
||||
if (selectedType !== '' && types[selectedType]) {
|
||||
typeSelect.val(selectedType);
|
||||
}
|
||||
}
|
||||
|
||||
function bedsideRefreshCategories() {
|
||||
var selectedType = $('#bedside_item_type').val() || '';
|
||||
var selectedCategory = $('#bedside_category').val() || '';
|
||||
var allItems = getAllBedsideItems();
|
||||
var categories = {};
|
||||
|
||||
allItems.forEach(function (item) {
|
||||
var typeName = String(item.item_type || '').trim();
|
||||
if (selectedType !== '' && typeName !== selectedType) {
|
||||
return;
|
||||
}
|
||||
|
||||
var categoryName = String(item.category || '').trim();
|
||||
if (categoryName === '') {
|
||||
categoryName = 'General';
|
||||
}
|
||||
categories[categoryName] = true;
|
||||
});
|
||||
|
||||
var categorySelect = $('#bedside_category');
|
||||
categorySelect.empty().append('<option value="">Select</option>');
|
||||
Object.keys(categories).sort().forEach(function (categoryName) {
|
||||
categorySelect.append('<option value="' + categoryName + '">' + categoryName + '</option>');
|
||||
});
|
||||
|
||||
if (selectedCategory !== '' && categories[selectedCategory]) {
|
||||
categorySelect.val(selectedCategory);
|
||||
}
|
||||
}
|
||||
|
||||
function bedsideRefreshItems() {
|
||||
var selectedType = $('#bedside_item_type').val() || '';
|
||||
var category = $('#bedside_category').val() || '';
|
||||
var search = ($('#bedside_item_search').val() || '').toLowerCase().trim();
|
||||
var itemSelect = $('#bedside_item_id');
|
||||
itemSelect.empty().append('<option value="">Select</option>');
|
||||
|
||||
var items = (bedsideItemsByCategory[category] || []).filter(function (item) {
|
||||
var typeName = String(item.item_type || '').trim();
|
||||
var itemName = String(item.item_name || '').toLowerCase();
|
||||
var itemCode = String(item.item_code || '').toLowerCase();
|
||||
|
||||
if (search !== '' && itemName.indexOf(search) === -1 && itemCode.indexOf(search) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectedType === '' || typeName === selectedType;
|
||||
});
|
||||
items.forEach(function(item) {
|
||||
var rate = Number(item.default_rate || 0);
|
||||
var label = (item.item_name || '') + ' [' + rate.toFixed(2) + ']';
|
||||
var insuranceCode = String(item.insurance_code || '').trim();
|
||||
if (insuranceCode !== '') {
|
||||
label += ' {' + insuranceCode + '}';
|
||||
}
|
||||
var option = $('<option></option>')
|
||||
.attr('value', item.item_id)
|
||||
.attr('data-rate', rate)
|
||||
.text(label);
|
||||
itemSelect.append(option);
|
||||
});
|
||||
|
||||
$('#bedside_item_rate').val(items.length > 0 ? Number(items[0].default_rate || 0).toFixed(2) : '0.00');
|
||||
}
|
||||
|
||||
function bedsideSetRate() {
|
||||
var selected = $('#bedside_item_id option:selected');
|
||||
var rate = selected.data('rate');
|
||||
$('#bedside_item_rate').val(rate !== undefined ? Number(rate).toFixed(2) : '0.00');
|
||||
}
|
||||
|
||||
function ipdChargeAdd(typeId) {
|
||||
var itemId = document.getElementById('itype_name_id_' + typeId).value;
|
||||
var rate = document.getElementById('input_rate_' + typeId).value;
|
||||
|
|
@ -269,7 +510,122 @@ $today = date('Y-m-d');
|
|||
payload.doc_id = docId;
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/charge/add/' + ipdChargeIpdId, payload, function() {
|
||||
load_form_div('<?= base_url('IpdNew/show_ipd_items/' . $ipdId . '/1') ?>', 'tab_charges_content');
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
});
|
||||
}
|
||||
|
||||
function ipdBedsideChargeAdd() {
|
||||
var bedsideItemId = $('#bedside_item_id').val() || '';
|
||||
if (!bedsideItemId) {
|
||||
alert('Select bedside item.');
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = {};
|
||||
payload[ipdChargeCsrfName] = ipdChargeCsrfHash;
|
||||
payload.bedside_item_id = bedsideItemId;
|
||||
payload.item_rate = $('#bedside_item_rate').val() || 0;
|
||||
payload.item_qty = $('#bedside_item_qty').val() || 1;
|
||||
payload.item_date = $('#bedside_item_date').val() || '<?= esc($today) ?>';
|
||||
payload.comment = $('#bedside_comment').val() || '';
|
||||
payload.doc_id = $('#bedside_doc_id').val() || 0;
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/bedside-charge/add/' + ipdChargeIpdId, payload, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
return;
|
||||
}
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to add bedside charge');
|
||||
}, 'json').fail(function(xhr) {
|
||||
var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to add bedside charge';
|
||||
alert(msg);
|
||||
});
|
||||
}
|
||||
|
||||
function doctorVisitRefreshTypes() {
|
||||
var docId = parseInt($('#doctor_visit_doc_id').val() || '0', 10);
|
||||
var selectedFeeType = $('#doctor_visit_fee_type').val() || '';
|
||||
var feeTypeSelect = $('#doctor_visit_fee_type');
|
||||
feeTypeSelect.empty().append('<option value="">Select</option>');
|
||||
|
||||
if (docId > 0 && doctorVisitFeeMap[docId]) {
|
||||
var docFees = doctorVisitFeeMap[docId];
|
||||
Object.keys(docFees).forEach(function (feeTypeId) {
|
||||
var row = docFees[feeTypeId] || {};
|
||||
var label = String(row.fee_type || '').trim();
|
||||
if (label === '') {
|
||||
label = 'Visit Type ' + feeTypeId;
|
||||
}
|
||||
feeTypeSelect.append('<option value="' + feeTypeId + '">' + label + '</option>');
|
||||
});
|
||||
} else {
|
||||
(doctorVisitFeeTypes || []).forEach(function (row) {
|
||||
feeTypeSelect.append('<option value="' + row.id + '">' + (row.fee_type || '') + '</option>');
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedFeeType !== '' && feeTypeSelect.find('option[value="' + selectedFeeType + '"]').length > 0) {
|
||||
feeTypeSelect.val(selectedFeeType);
|
||||
}
|
||||
}
|
||||
|
||||
function doctorVisitRefreshRate() {
|
||||
var docId = parseInt($('#doctor_visit_doc_id').val() || '0', 10);
|
||||
var feeTypeId = parseInt($('#doctor_visit_fee_type').val() || '0', 10);
|
||||
var feeDetail = null;
|
||||
|
||||
if (docId > 0 && doctorVisitFeeMap[docId]) {
|
||||
var docFees = doctorVisitFeeMap[docId];
|
||||
if (feeTypeId > 0 && docFees[feeTypeId]) {
|
||||
feeDetail = docFees[feeTypeId];
|
||||
} else {
|
||||
var firstKey = Object.keys(docFees)[0] || null;
|
||||
if (firstKey && docFees[firstKey]) {
|
||||
feeDetail = docFees[firstKey];
|
||||
$('#doctor_visit_fee_type').val(String(firstKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (feeDetail) {
|
||||
$('#doctor_visit_rate').val(Number(feeDetail.amount || 0).toFixed(2));
|
||||
$('#doctor_visit_fee_desc').val(feeDetail.fee_desc || feeDetail.fee_type || '');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#doctor_visit_rate').val('0.00');
|
||||
$('#doctor_visit_fee_desc').val('');
|
||||
}
|
||||
|
||||
function ipdDoctorVisitChargeAdd() {
|
||||
var docId = $('#doctor_visit_doc_id').val() || '';
|
||||
if (!docId) {
|
||||
alert('Select doctor.');
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = {};
|
||||
payload[ipdChargeCsrfName] = ipdChargeCsrfHash;
|
||||
payload.doc_id = docId;
|
||||
payload.fee_type_id = $('#doctor_visit_fee_type').val() || 0;
|
||||
payload.item_rate = $('#doctor_visit_rate').val() || 0;
|
||||
payload.item_qty = $('#doctor_visit_qty').val() || 1;
|
||||
payload.item_date = $('#doctor_visit_date').val() || '<?= esc($today) ?>';
|
||||
payload.visit_time = $('#doctor_visit_time').val() || '';
|
||||
payload.duration = $('#doctor_visit_duration').val() || 0;
|
||||
payload.hospital_clinic = $('#doctor_visit_hospital').val() || '';
|
||||
payload.is_outside_doctor = $('#doctor_visit_outside').is(':checked') ? 1 : 0;
|
||||
payload.comment = $('#doctor_visit_comment').val() || '';
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/doctor-visit/add/' + ipdChargeIpdId, payload, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
return;
|
||||
}
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to add doctor visit charge');
|
||||
}, 'json').fail(function(xhr) {
|
||||
var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to add doctor visit charge';
|
||||
alert(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +639,7 @@ $today = date('Y-m-d');
|
|||
payload.item_rate = rate;
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/charge/update/' + itemId, payload, function() {
|
||||
load_form_div('<?= base_url('IpdNew/show_ipd_items/' . $ipdId . '/1') ?>', 'tab_charges_content');
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -295,8 +651,15 @@ $today = date('Y-m-d');
|
|||
var payload = {};
|
||||
payload[ipdChargeCsrfName] = ipdChargeCsrfHash;
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/charge/delete/' + itemId, payload, function() {
|
||||
load_form_div('<?= base_url('IpdNew/show_ipd_items/' . $ipdId . '/1') ?>', 'tab_charges_content');
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/charge/delete/' + itemId, payload, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
return;
|
||||
}
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to remove charge item');
|
||||
}, 'json').fail(function(xhr) {
|
||||
var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to remove charge item';
|
||||
alert(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +669,26 @@ $today = date('Y-m-d');
|
|||
payload.package_id = checkbox.checked ? ipdChargeDefaultPackageId : 0;
|
||||
|
||||
$.post(ipdChargeBaseUrl + 'billing/ipd/charge/package/' + itemId, payload, function() {
|
||||
load_form_div('<?= base_url('IpdNew/show_ipd_items/' . $ipdId . '/1') ?>', 'tab_charges_content');
|
||||
load_form_div(ipdChargeTabRefreshUrl, 'tab_charges_content');
|
||||
});
|
||||
}
|
||||
|
||||
$('#bedside_item_type').on('change', function () {
|
||||
bedsideRefreshCategories();
|
||||
bedsideRefreshItems();
|
||||
});
|
||||
$('#bedside_category').on('change', bedsideRefreshItems);
|
||||
$('#bedside_item_search').on('input', bedsideRefreshItems);
|
||||
$('#bedside_item_id').on('change', bedsideSetRate);
|
||||
$('#doctor_visit_doc_id').on('change', function () {
|
||||
doctorVisitRefreshTypes();
|
||||
doctorVisitRefreshRate();
|
||||
});
|
||||
$('#doctor_visit_fee_type').on('change', doctorVisitRefreshRate);
|
||||
|
||||
bedsideRefreshTypeOptions();
|
||||
bedsideRefreshCategories();
|
||||
bedsideRefreshItems();
|
||||
doctorVisitRefreshTypes();
|
||||
doctorVisitRefreshRate();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@
|
|||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body pt-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="medical-master-search" class="form-control" placeholder="Search links (e.g. exp, gst, stock, invoice, bank)" list="medical-master-suggestions" autocomplete="off">
|
||||
<datalist id="medical-master-suggestions"></datalist>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="medical-master-clear">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-2">Type a keyword to filter links. Matching links are highlighted.</small>
|
||||
<div id="medical-master-search-empty" class="alert alert-warning mt-2 mb-0 py-2" style="display:none;">No matching links found.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
|
|
@ -16,52 +32,52 @@
|
|||
<div class="card-body pt-3">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/purchase_invoice_report') ?>','medical-main','Purchase Invoice :Pharmacy');">Purchase Invoice</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/purchase_invoice_report') ?>','medical-main','Purchase Invoice :Pharmacy');">Purchase Invoice</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/supplier_account') ?>','medical-main','Supplier Account :Pharmacy');">Supplier Account</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/supplier_account') ?>','medical-main','Supplier Account :Pharmacy');">Supplier Account</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Payment_Medical') ?>','medical-main','Med. Payment Edit :Pharmacy');">Med. Payment Edit</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Payment_Medical') ?>','medical-main','Med. Payment Edit :Pharmacy');">Med. Payment Edit</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Payment_Medical/payment_log') ?>','medical-main','Med. Payment Edit Logs :Pharmacy');">Med. Payment Edit Logs</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Payment_Medical/payment_log') ?>','medical-main','Med. Payment Edit Logs :Pharmacy');">Med. Payment Edit Logs</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/invoice_item_log') ?>','medical-main','Med. Invoice Item Update Logs :Pharmacy');">Med. Invoice Item Update Logs</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/invoice_item_log') ?>','medical-main','Med. Invoice Item Update Logs :Pharmacy');">Med. Invoice Item Update Logs</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/store_stock') ?>','medical-main','Stock Report :Pharmacy');">Stock Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/store_stock') ?>','medical-main','Stock Report :Pharmacy');">Stock Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/stock_details') ?>','medical-main','Item Stock Statement :Pharmacy');">Item Stock Statement</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/stock_details') ?>','medical-main','Item Stock Statement :Pharmacy');">Item Stock Statement</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/ipd_discharge') ?>','medical-main','IPD Discharge Report :Pharmacy');">IPD Discharge Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/ipd_discharge') ?>','medical-main','IPD Discharge Report :Pharmacy');">IPD Discharge Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_daily_med_sale') ?>','medical-main','Day wise Medicine Sale :Pharmacy');">Day wise Medicine Sale</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_daily_med_sale') ?>','medical-main','Day wise Medicine Sale :Pharmacy');">Day wise Medicine Sale</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_daily_med_sale_doc') ?>','medical-main','Day wise & Doc Wise Medicine Sale :Pharmacy');">Day wise & Doc Wise Medicine Sale</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_daily_med_sale_doc') ?>','medical-main','Day wise & Doc Wise Medicine Sale :Pharmacy');">Day wise & Doc Wise Medicine Sale</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_company_med_sale') ?>','medical-main','Company Wise Medicine :Pharmacy');">Company Wise Medicine</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_company_med_sale') ?>','medical-main','Company Wise Medicine :Pharmacy');">Company Wise Medicine</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/expire_stock') ?>','medical-main','Expiry Medicine :Pharmacy');">Expiry Medicine</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/expire_stock') ?>','medical-main','Expiry Medicine :Pharmacy');">Expiry Medicine</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/master_report/lost-medicine') ?>','medical-main','Lost Medicine :Pharmacy');">Lost Medicine</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/master_report/lost-medicine') ?>','medical-main','Lost Medicine :Pharmacy');">Lost Medicine</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Short_medicine') ?>','medical-main','Short Medicine List :Pharmacy');">Short Medicine List</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Short_medicine') ?>','medical-main','Short Medicine List :Pharmacy');">Short Medicine List</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/stocktransfer') ?>','medical-main','Stock Transfer :Pharmacy');">Stock Transfer</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/stocktransfer') ?>','medical-main','Stock Transfer :Pharmacy');">Stock Transfer</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/merge_product') ?>','medical-main','Product Merge :Pharmacy');">Product Merge</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/merge_product') ?>','medical-main','Product Merge :Pharmacy');">Product Merge</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -74,22 +90,22 @@
|
|||
<div class="card-body pt-3">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/Invoice_Med_Draft') ?>','medical-main','Old Invoice :Pharmacy');">Old Invoice</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/Invoice_Med_Draft') ?>','medical-main','Old Invoice :Pharmacy');">Old Invoice</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_4') ?>','medical-main','IPD Sale Report :Pharmacy');">IPD Sale Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_4') ?>','medical-main','IPD Sale Report :Pharmacy');">IPD Sale Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_backpanel/Report_IPD_CreditBills') ?>','medical-main','IPD Invoice Bill Type :Pharmacy');">IPD Invoice Bill Type</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_backpanel/Report_IPD_CreditBills') ?>','medical-main','IPD Invoice Bill Type :Pharmacy');">IPD Invoice Bill Type</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_backpanel/Org_Bills') ?>','medical-main','OPD Org Report :Pharmacy');">OPD Org Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_backpanel/Org_Bills') ?>','medical-main','OPD Org Report :Pharmacy');">OPD Org Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/master_report/opd-cash-pending') ?>','medical-main','OPD CASH Pending Report :Pharmacy');">OPD CASH Pending Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/master_report/opd-cash-pending') ?>','medical-main','OPD CASH Pending Report :Pharmacy');">OPD CASH Pending Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/master_report/opd-old-balance-paid') ?>','medical-main','OPD Old Balance Paid Report :Pharmacy');">OPD Old Balance Paid Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/master_report/opd-old-balance-paid') ?>','medical-main','OPD Old Balance Paid Report :Pharmacy');">OPD Old Balance Paid Report</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -102,16 +118,16 @@
|
|||
<div class="card-body pt-3">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_5') ?>','medical-main','Invoice List for GST :Pharmacy');">Invoice List for GST</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_5') ?>','medical-main','Invoice List for GST :Pharmacy');">Invoice List for GST</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_3') ?>','medical-main','GST Report :Pharmacy');">GST Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_3') ?>','medical-main','GST Report :Pharmacy');">GST Report</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical/master_report/bank-ledger') ?>','medical-main','Bank Ledger :Pharmacy');">Bank Ledger</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/master_report/bank-ledger') ?>','medical-main','Bank Ledger :Pharmacy');">Bank Ledger</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a href="javascript:load_form_div('<?= base_url('Medical_Report/Report_gst_1') ?>','medical-main','Purchase GST Report :Pharmacy');">Purchase GST Report</a>
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical_Report/Report_gst_1') ?>','medical-main','Purchase GST Report :Pharmacy');">Purchase GST Report</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -121,3 +137,119 @@
|
|||
|
||||
<div id="medical-main" class="mt-3"></div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.medical-master-match {
|
||||
background-color: #fff8d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var searchInput = document.getElementById('medical-master-search');
|
||||
var clearBtn = document.getElementById('medical-master-clear');
|
||||
var emptyBox = document.getElementById('medical-master-search-empty');
|
||||
var suggestBox = document.getElementById('medical-master-suggestions');
|
||||
if (!searchInput || !clearBtn || !emptyBox || !suggestBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
var keywordMap = {
|
||||
'Purchase Invoice': 'purchase bill buy vendor supplier invoice pi',
|
||||
'Supplier Account': 'supplier vendor ledger account payable',
|
||||
'Med. Payment Edit': 'payment edit correction cash bank',
|
||||
'Med. Payment Edit Logs': 'payment logs audit history',
|
||||
'Med. Invoice Item Update Logs': 'invoice item logs update history',
|
||||
'Stock Report': 'stock inventory qty quantity balance',
|
||||
'Item Stock Statement': 'stock statement item inventory detail',
|
||||
'IPD Discharge Report': 'ipd discharge in patient report',
|
||||
'Day wise Medicine Sale': 'day daily date wise sale',
|
||||
'Day wise & Doc Wise Medicine Sale': 'doctor doc day wise sale',
|
||||
'Company Wise Medicine': 'company manufacturer medicine sale',
|
||||
'Expiry Medicine': 'expiry exp expired near expiry',
|
||||
'Lost Medicine': 'lost missing damage wastage',
|
||||
'Short Medicine List': 'short shortage out of stock low stock',
|
||||
'Stock Transfer': 'transfer move stock branch',
|
||||
'Product Merge': 'merge duplicate combine product',
|
||||
'Old Invoice': 'old previous invoice',
|
||||
'IPD Sale Report': 'ipd sale report in patient',
|
||||
'IPD Invoice Bill Type': 'ipd bill type credit cash',
|
||||
'OPD Org Report': 'opd org organisation corporate',
|
||||
'OPD CASH Pending Report': 'opd cash pending due',
|
||||
'OPD Old Balance Paid Report': 'opd old balance paid receipt',
|
||||
'Invoice List for GST': 'gst invoice tax',
|
||||
'GST Report': 'gst tax summary',
|
||||
'Bank Ledger': 'bank ledger statement transaction',
|
||||
'Purchase GST Report': 'purchase gst tax input'
|
||||
};
|
||||
|
||||
var links = Array.prototype.slice.call(document.querySelectorAll('.medical-master-link'));
|
||||
var cards = Array.prototype.slice.call(document.querySelectorAll('section.section .row.g-3 > .col-lg-4'));
|
||||
|
||||
function normalize(text) {
|
||||
return (text || '').toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function buildSuggestions() {
|
||||
var pool = {};
|
||||
links.forEach(function (link) {
|
||||
var title = link.textContent.trim();
|
||||
var keywords = keywordMap[title] || '';
|
||||
var hay = normalize(title + ' ' + keywords).split(' ');
|
||||
hay.forEach(function (word) {
|
||||
if (word.length >= 3) {
|
||||
pool[word] = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(pool).sort().slice(0, 120).forEach(function (term) {
|
||||
var option = document.createElement('option');
|
||||
option.value = term;
|
||||
suggestBox.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilter() {
|
||||
var q = normalize(searchInput.value);
|
||||
var hasAny = false;
|
||||
|
||||
links.forEach(function (link) {
|
||||
var title = normalize(link.textContent);
|
||||
var keywords = normalize(keywordMap[link.textContent.trim()] || '');
|
||||
var haystack = title + ' ' + keywords;
|
||||
var matched = q === '' || haystack.indexOf(q) !== -1;
|
||||
|
||||
var item = link.closest('li');
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.style.display = matched ? '' : 'none';
|
||||
item.classList.toggle('medical-master-match', matched && q !== '');
|
||||
|
||||
if (matched) {
|
||||
hasAny = true;
|
||||
}
|
||||
});
|
||||
|
||||
cards.forEach(function (cardCol) {
|
||||
var visibleItems = cardCol.querySelectorAll('li.list-group-item:not([style*="display: none"])');
|
||||
cardCol.style.display = visibleItems.length > 0 ? '' : 'none';
|
||||
});
|
||||
|
||||
emptyBox.style.display = (!hasAny && q !== '') ? '' : 'none';
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', applyFilter);
|
||||
clearBtn.addEventListener('click', function () {
|
||||
searchInput.value = '';
|
||||
applyFilter();
|
||||
searchInput.focus();
|
||||
});
|
||||
|
||||
buildSuggestions();
|
||||
applyFilter();
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -80,6 +80,12 @@ if (! $canPharmacy && $user && method_exists($user, 'inGroup')) {
|
|||
<span>IPD Patient List</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/index') ?>','Report Panel')">
|
||||
<i class="bi bi-grid-1x2"></i>
|
||||
<span>Report Panel</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-heading">Doctor's Work</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('/opd/appointment') ?>','OPD Appointment List')">
|
||||
|
|
@ -123,6 +129,7 @@ if (! $canPharmacy && $user && method_exists($user, 'inGroup')) {
|
|||
<?php } ?>
|
||||
|
||||
<li class="nav-heading">Reports</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/collection_report') ?>','Collection Report')">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
|
|
@ -141,6 +148,12 @@ if (! $canPharmacy && $user && method_exists($user, 'inGroup')) {
|
|||
<span>Insurance Credit </span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/nabh_audit_report') ?>','NABH Audit Report')">
|
||||
<i class="bi bi-clipboard2-check"></i>
|
||||
<span>NABH Audit Report</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('org-packing') ?>','Organization Packing')">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
|
|
|
|||
93
app/Views/report/billing_operations_report.php
Normal file
93
app/Views/report/billing_operations_report.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
$doctors = $doctors ?? [];
|
||||
?>
|
||||
<section class="content">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Billing Operations Report</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Date Range</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="datetime-local" class="form-control" id="billing_start">
|
||||
<input type="datetime-local" class="form-control" id="billing_end">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Doctor (OPD)</label>
|
||||
<select class="form-control select2" id="billing_doctor" data-placeholder="All Doctors">
|
||||
<option value="0">All Doctors</option>
|
||||
<?php foreach ($doctors as $row): ?>
|
||||
<option value="<?= esc((string) ($row->doc_name ?? '')) ?>"><?= esc((string) ($row->doc_name ?? '')) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary w-100" id="billing_show">Show</button>
|
||||
<button type="button" class="btn btn-outline-primary w-100" id="billing_export">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<div id="billing_report_result" class="table-responsive">Select filters and click Show.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function pad(v) {
|
||||
return v < 10 ? '0' + v : String(v);
|
||||
}
|
||||
|
||||
function toInputValue(date) {
|
||||
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
|
||||
+ 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
|
||||
function toRangeValue(value, fallback) {
|
||||
var finalValue = value || fallback;
|
||||
if (finalValue.length === 16) {
|
||||
return finalValue + ':00';
|
||||
}
|
||||
return finalValue;
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
var start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
var end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0);
|
||||
document.getElementById('billing_start').value = toInputValue(start);
|
||||
document.getElementById('billing_end').value = toInputValue(end);
|
||||
|
||||
function buildUrl(withExport) {
|
||||
var startVal = toRangeValue(document.getElementById('billing_start').value, toInputValue(start));
|
||||
var endVal = toRangeValue(document.getElementById('billing_end').value, toInputValue(end));
|
||||
var dateRange = startVal + 'S' + endVal;
|
||||
var doctor = document.getElementById('billing_doctor').value || '0';
|
||||
var url = '<?= base_url('Report/billing_operations_report_data') ?>/'
|
||||
+ encodeURIComponent(dateRange) + '/' + encodeURIComponent(doctor);
|
||||
if (withExport) {
|
||||
url += '/1';
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
document.getElementById('billing_show').addEventListener('click', function() {
|
||||
load_form_div(buildUrl(false), 'billing_report_result');
|
||||
});
|
||||
|
||||
document.getElementById('billing_export').addEventListener('click', function() {
|
||||
window.open(buildUrl(true), '_blank');
|
||||
});
|
||||
|
||||
$('.select2').select2({
|
||||
theme: 'bootstrap4',
|
||||
width: '100%'
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
178
app/Views/report/billing_operations_report_table.php
Normal file
178
app/Views/report/billing_operations_report_table.php
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
$opdRows = $opd_rows ?? [];
|
||||
$invoiceSummary = $invoice_summary ?? [];
|
||||
$invoiceRows = $invoice_rows ?? [];
|
||||
$ipdSummary = $ipd_summary ?? [];
|
||||
$ipdPaymentRows = $ipd_payment_rows ?? [];
|
||||
?>
|
||||
<div class="mb-3 small text-muted">
|
||||
Date: <strong><?= esc((string) ($min_range ?? '')) ?></strong> to <strong><?= esc((string) ($max_range ?? '')) ?></strong>
|
||||
<?php if (! empty($doctor_filter)): ?>
|
||||
| Doctor: <strong><?= esc((string) $doctor_filter) ?></strong>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<div class="small text-muted">OPD Doctors</div>
|
||||
<div class="h5 mb-0"><?= count($opdRows) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<div class="small text-muted">Total Invoices</div>
|
||||
<div class="h5 mb-0"><?= (int) ($invoiceSummary['invoice_count'] ?? 0) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<div class="small text-muted">Invoice Amount</div>
|
||||
<div class="h5 mb-0"><?= number_format((float) ($invoiceSummary['total_invoice_amount'] ?? 0), 2) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<div class="small text-muted">IPD Net Received</div>
|
||||
<div class="h5 mb-0"><?= number_format((float) ($ipdSummary['net_received'] ?? 0), 2) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6>1) OPD Done By Doctors</h6>
|
||||
<table class="table table-sm table-bordered align-middle mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:60px;">#</th>
|
||||
<th>Doctor</th>
|
||||
<th style="width:160px;">Total OPDs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($opdRows)): ?>
|
||||
<tr><td colspan="3" class="text-center text-muted">No OPD data found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($opdRows as $row): ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row->doc_name ?? '')) ?></td>
|
||||
<td><?= (int) ($row->total_opd ?? 0) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6>2) Charges Invoice / Tests / Amount / Received</h6>
|
||||
<div class="table-responsive mb-2">
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Total Tests</th>
|
||||
<th>Total Test Qty</th>
|
||||
<th>Total Test Amount</th>
|
||||
<th>Total Received</th>
|
||||
<th>Total Refund</th>
|
||||
<th>Net Received</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?= (int) ($invoiceSummary['total_test_count'] ?? 0) ?></td>
|
||||
<td><?= number_format((float) ($invoiceSummary['total_test_qty'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($invoiceSummary['total_test_amount'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($invoiceSummary['total_received'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($invoiceSummary['total_refund'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format(((float) ($invoiceSummary['total_received'] ?? 0)) - ((float) ($invoiceSummary['total_refund'] ?? 0)), 2) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-bordered align-middle mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Invoice</th>
|
||||
<th>Date</th>
|
||||
<th>Test Count</th>
|
||||
<th>Test Amount</th>
|
||||
<th>Invoice Amount</th>
|
||||
<th>Received</th>
|
||||
<th>Pending</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($invoiceRows)): ?>
|
||||
<tr><td colspan="8" class="text-center text-muted">No invoice data found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($invoiceRows as $row): ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row->inv_name ?? ('INV#' . ($row->id ?? '')))) ?></td>
|
||||
<td><?= esc((string) ($row->inv_date ?? '')) ?></td>
|
||||
<td><?= (int) ($row->test_count ?? 0) ?></td>
|
||||
<td><?= number_format((float) ($row->test_amount ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($row->invoice_amount ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($row->net_received ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($row->pending_amount ?? 0), 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h6>3) IPD Related Payment Received</h6>
|
||||
<div class="table-responsive mb-2">
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IPD Cases</th>
|
||||
<th>Total Received</th>
|
||||
<th>Total Refund</th>
|
||||
<th>Net Received</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?= (int) ($ipdSummary['ipd_count'] ?? 0) ?></td>
|
||||
<td><?= number_format((float) ($ipdSummary['total_received'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($ipdSummary['total_refund'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($ipdSummary['net_received'] ?? 0), 2) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>IPD Code</th>
|
||||
<th>Patient</th>
|
||||
<th>Received</th>
|
||||
<th>Refund</th>
|
||||
<th>Net</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($ipdPaymentRows)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted">No IPD payment data found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($ipdPaymentRows as $row): ?>
|
||||
<?php
|
||||
$received = (float) ($row->total_received ?? 0);
|
||||
$refund = (float) ($row->total_refund ?? 0);
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row->ipd_code ?? ('IPD#' . ($row->ipd_id ?? '')))) ?></td>
|
||||
<td><?= esc((string) ($row->patient_name ?? '')) ?></td>
|
||||
<td><?= number_format($received, 2) ?></td>
|
||||
<td><?= number_format($refund, 2) ?></td>
|
||||
<td><?= number_format($received - $refund, 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
194
app/Views/report/index.php
Normal file
194
app/Views/report/index.php
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<section class="content report-hub">
|
||||
<div class="report-hub-head mb-3">
|
||||
<h2 class="report-hub-title mb-1">Report Panel</h2>
|
||||
<p class="report-hub-sub mb-0">Quick access to billing, diagnosis, insurance, and operational report sections.</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card report-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="report-card-head">
|
||||
<span class="report-icon report-icon-blue"><i class="bi bi-bar-chart-line"></i></span>
|
||||
<h5 class="report-card-title mb-0">Reports</h5>
|
||||
</div>
|
||||
<ul class="report-links">
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/collection_report') ?>','Collection Report');"><i class="bi bi-arrow-right-short"></i> Total Payment (Cash and Bank)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/billing_operations_report') ?>','Billing Operations Report');"><i class="bi bi-arrow-right-short"></i> Billing Operations Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/nabh_audit_report') ?>','NABH Audit Report');"><i class="bi bi-arrow-right-short"></i> NABH Audit Report</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card report-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="report-card-head">
|
||||
<span class="report-icon report-icon-cyan"><i class="bi bi-clipboard2-pulse"></i></span>
|
||||
<h5 class="report-card-title mb-0">Reports Diagnosis</h5>
|
||||
</div>
|
||||
<ul class="report-links">
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/diagnosis_report') ?>','Diagnosis Report');"><i class="bi bi-arrow-right-short"></i> Diagnosis Report</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card report-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="report-card-head">
|
||||
<span class="report-icon report-icon-green"><i class="bi bi-hospital"></i></span>
|
||||
<h5 class="report-card-title mb-0">Reports IPD</h5>
|
||||
</div>
|
||||
<ul class="report-links">
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/insurance_ipd_report') ?>','Insurance Case IPD');"><i class="bi bi-arrow-right-short"></i> Insurance Case IPD</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card report-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="report-card-head">
|
||||
<span class="report-icon report-icon-indigo"><i class="bi bi-shield-check"></i></span>
|
||||
<h5 class="report-card-title mb-0">Reports Insurance Credit</h5>
|
||||
</div>
|
||||
<ul class="report-links">
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/insurance_ipd_report') ?>','Insurance Case IPD');"><i class="bi bi-arrow-right-short"></i> Insurance Case IPD</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/insurance_opd_report') ?>','Insurance Case OPD');"><i class="bi bi-arrow-right-short"></i> Insurance Case OPD</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('org-packing') ?>','Org. OPD Packing');"><i class="bi bi-arrow-right-short"></i> Or. OPD Packing</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="card report-card h-100 report-card-muted">
|
||||
<div class="card-body">
|
||||
<div class="report-card-head">
|
||||
<span class="report-icon report-icon-slate"><i class="bi bi-file-earmark-text"></i></span>
|
||||
<h5 class="report-card-title mb-0">Reports Document</h5>
|
||||
</div>
|
||||
<div class="report-empty">Document reports are not migrated yet.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.report-hub {
|
||||
--hub-bg: #f4f6fb;
|
||||
--hub-border: #e3e8f2;
|
||||
--hub-text: #2f3b52;
|
||||
--hub-sub: #69758c;
|
||||
--hub-link: #1f4d90;
|
||||
--hub-link-hover: #0d6efd;
|
||||
--hub-shadow: 0 10px 22px rgba(30, 48, 84, 0.08);
|
||||
background: linear-gradient(145deg, #f7f9fd 0%, #edf2f9 100%);
|
||||
border: 1px solid var(--hub-border);
|
||||
border-radius: 14px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.report-hub-title {
|
||||
color: #24334f;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.report-hub-sub {
|
||||
color: var(--hub-sub);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
border: 1px solid var(--hub-border);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 0 rgba(255, 255, 255, 0.9) inset;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.report-card:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: #cdd8ea;
|
||||
box-shadow: var(--hub-shadow);
|
||||
}
|
||||
|
||||
.report-card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.report-card-title {
|
||||
color: var(--hub-text);
|
||||
font-size: 1.03rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.report-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.report-icon-blue { background: #e7f0ff; color: #1f5dd0; }
|
||||
.report-icon-cyan { background: #e3f9fa; color: #0f8f9a; }
|
||||
.report-icon-green { background: #e7f9ef; color: #1d9960; }
|
||||
.report-icon-indigo { background: #ece9ff; color: #4b49b6; }
|
||||
.report-icon-slate { background: #eef1f6; color: #6b7280; }
|
||||
|
||||
.report-links {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.report-links li + li {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.report-links a {
|
||||
color: var(--hub-link);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.report-links a:hover {
|
||||
color: var(--hub-link-hover);
|
||||
}
|
||||
|
||||
.report-links i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.report-card-muted {
|
||||
background: #fcfdff;
|
||||
}
|
||||
|
||||
.report-empty {
|
||||
color: #7a8598;
|
||||
font-size: 0.94rem;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.report-hub {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
96
app/Views/report/nabh_audit_report.php
Normal file
96
app/Views/report/nabh_audit_report.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<section class="content">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">NABH Audit Report</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Date Range</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="datetime-local" class="form-control" id="report_start">
|
||||
<input type="datetime-local" class="form-control" id="report_end">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Module</label>
|
||||
<select class="form-control" id="module_filter">
|
||||
<option value="all">All</option>
|
||||
<option value="ipd">IPD Discharge</option>
|
||||
<option value="opd">OPD Consult</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Compliance Status</label>
|
||||
<select class="form-control" id="status_filter">
|
||||
<option value="all">All</option>
|
||||
<option value="critical-missing">Critical Missing</option>
|
||||
<option value="compliant">Compliant</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-grid gap-2">
|
||||
<button type="button" class="btn btn-primary" id="show_report">Show</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="export_report">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<div id="report_result" class="table-responsive">Select filters and click Show.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function pad(value) {
|
||||
return value < 10 ? '0' + value : value;
|
||||
}
|
||||
|
||||
function toInputValue(date) {
|
||||
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
|
||||
+ 'T' + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
|
||||
function toRangeValue(value, fallback) {
|
||||
if (!value) {
|
||||
value = fallback;
|
||||
}
|
||||
if (value.length === 16) {
|
||||
return value + ':00';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
var start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
var end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0);
|
||||
|
||||
document.getElementById('report_start').value = toInputValue(start);
|
||||
document.getElementById('report_end').value = toInputValue(end);
|
||||
|
||||
function buildQuery() {
|
||||
var startVal = toRangeValue(document.getElementById('report_start').value, toInputValue(start));
|
||||
var endVal = toRangeValue(document.getElementById('report_end').value, toInputValue(end));
|
||||
var dateRange = startVal + 'S' + endVal;
|
||||
|
||||
var module = document.getElementById('module_filter').value || 'all';
|
||||
var status = document.getElementById('status_filter').value || 'all';
|
||||
|
||||
return '<?= base_url('Report/nabh_audit_report_data') ?>/'
|
||||
+ encodeURIComponent(dateRange) + '/'
|
||||
+ encodeURIComponent(module) + '/'
|
||||
+ encodeURIComponent(status);
|
||||
}
|
||||
|
||||
document.getElementById('show_report').addEventListener('click', function() {
|
||||
load_form_div(buildQuery(), 'report_result');
|
||||
});
|
||||
|
||||
document.getElementById('export_report').addEventListener('click', function() {
|
||||
window.open(buildQuery() + '/1', '_blank');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
97
app/Views/report/nabh_audit_report_table.php
Normal file
97
app/Views/report/nabh_audit_report_table.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$summary = $summary ?? [];
|
||||
$minRange = $min_range ?? '';
|
||||
$maxRange = $max_range ?? '';
|
||||
$moduleFilter = $module_filter ?? 'all';
|
||||
$statusFilter = $status_filter ?? 'all';
|
||||
|
||||
$overall = $summary['all'] ?? ['total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0];
|
||||
$ipd = $summary['ipd'] ?? ['total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0];
|
||||
$opd = $summary['opd'] ?? ['total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0];
|
||||
?>
|
||||
|
||||
<div class="mb-3">
|
||||
<p><strong>Date Range:</strong> <?= esc($minRange) ?> to <?= esc($maxRange) ?></p>
|
||||
<p><strong>Module:</strong> <?= esc(strtoupper((string) $moduleFilter)) ?>, <strong>Status:</strong> <?= esc((string) $statusFilter) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="border rounded p-2">
|
||||
<div><strong>Overall</strong></div>
|
||||
<div>Total: <?= (int) ($overall['total'] ?? 0) ?></div>
|
||||
<div>Compliant: <?= (int) ($overall['compliant'] ?? 0) ?></div>
|
||||
<div>Critical Missing: <?= (int) ($overall['critical_missing'] ?? 0) ?></div>
|
||||
<div>Avg Completion: <?= number_format((float) ($overall['avg_completion'] ?? 0), 1) ?>%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="border rounded p-2">
|
||||
<div><strong>IPD Discharge</strong></div>
|
||||
<div>Total: <?= (int) ($ipd['total'] ?? 0) ?></div>
|
||||
<div>Compliant: <?= (int) ($ipd['compliant'] ?? 0) ?></div>
|
||||
<div>Critical Missing: <?= (int) ($ipd['critical_missing'] ?? 0) ?></div>
|
||||
<div>Avg Completion: <?= number_format((float) ($ipd['avg_completion'] ?? 0), 1) ?>%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="border rounded p-2">
|
||||
<div><strong>OPD Consult</strong></div>
|
||||
<div>Total: <?= (int) ($opd['total'] ?? 0) ?></div>
|
||||
<div>Compliant: <?= (int) ($opd['compliant'] ?? 0) ?></div>
|
||||
<div>Critical Missing: <?= (int) ($opd['critical_missing'] ?? 0) ?></div>
|
||||
<div>Avg Completion: <?= number_format((float) ($opd['avg_completion'] ?? 0), 1) ?>%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($rows)) : ?>
|
||||
<div class="alert alert-info">No audit data found for selected criteria.</div>
|
||||
<?php else : ?>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 50px;">#</th>
|
||||
<th>Module</th>
|
||||
<th>Encounter</th>
|
||||
<th>Patient</th>
|
||||
<th>Date Time</th>
|
||||
<th class="text-end">Completion</th>
|
||||
<th class="text-end">Critical Missing</th>
|
||||
<th>Missing Critical Items</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $index = 1; ?>
|
||||
<?php foreach ($rows as $row) : ?>
|
||||
<?php
|
||||
$criticalMissingCount = (int) ($row['critical_missing_count'] ?? 0);
|
||||
$rowClass = $criticalMissingCount > 0 ? 'table-warning' : 'table-success';
|
||||
$encounterLabel = trim((string) ($row['encounter_code'] ?? ''));
|
||||
if ($encounterLabel === '') {
|
||||
$encounterLabel = '#' . (int) ($row['encounter_id'] ?? 0);
|
||||
}
|
||||
$patientLabel = trim((string) ($row['patient_name'] ?? ''));
|
||||
$patientCode = trim((string) ($row['patient_code'] ?? ''));
|
||||
if ($patientCode !== '') {
|
||||
$patientLabel = $patientCode . ' - ' . $patientLabel;
|
||||
}
|
||||
?>
|
||||
<tr class="<?= esc($rowClass) ?>">
|
||||
<td><?= $index++ ?></td>
|
||||
<td><?= esc((string) ($row['module_label'] ?? '')) ?></td>
|
||||
<td><?= esc($encounterLabel) ?></td>
|
||||
<td><?= esc($patientLabel) ?></td>
|
||||
<td><?= esc((string) ($row['encounter_datetime'] ?? '')) ?></td>
|
||||
<td class="text-end">
|
||||
<?= (int) ($row['ok_count'] ?? 0) ?>/<?= (int) ($row['total_count'] ?? 0) ?>
|
||||
(<?= number_format((float) ($row['completion_percent'] ?? 0), 1) ?>%)
|
||||
</td>
|
||||
<td class="text-end"><?= $criticalMissingCount ?></td>
|
||||
<td><?= esc((string) ($row['critical_missing_labels'] ?? '-')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
111
public/index.php
111
public/index.php
|
|
@ -3,6 +3,117 @@
|
|||
use CodeIgniter\Boot;
|
||||
use Config\Paths;
|
||||
|
||||
/*
|
||||
*---------------------------------------------------------------
|
||||
* EMERGENCY FILESYSTEM PREPARE (PRE-BOOTSTRAP)
|
||||
*---------------------------------------------------------------
|
||||
* Usage: /?fs_prepare=1&key=YOUR_SETUP_KEY
|
||||
* Runs before CodeIgniter boot so it works even when cache init fails.
|
||||
*/
|
||||
if (isset($_GET['fs_prepare']) && (string) $_GET['fs_prepare'] === '1') {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$projectRoot = dirname(__DIR__);
|
||||
$envPath = $projectRoot . DIRECTORY_SEPARATOR . '.env';
|
||||
|
||||
$envKey = '';
|
||||
if (is_readable($envPath)) {
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if (is_array($lines)) {
|
||||
foreach ($lines as $line) {
|
||||
$line = trim((string) $line);
|
||||
if ($line === '' || strpos($line, '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stripos($line, 'setup.dbToolsKey') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode('=', $line, 2);
|
||||
$envKey = trim((string) ($parts[1] ?? ''), " \t\n\r\0\x0B\"'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$requestKey = trim((string) ($_GET['key'] ?? ''));
|
||||
if ($envKey === '') {
|
||||
http_response_code(403);
|
||||
echo "setup.dbToolsKey is not configured in .env\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($requestKey === '' || !hash_equals($envKey, $requestKey)) {
|
||||
http_response_code(403);
|
||||
echo "Invalid or missing key. Use /?fs_prepare=1&key=YOUR_KEY\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$paths = [
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'cache',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'logs',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'session',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'uploads',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'debugbar',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'tmp',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'uploads',
|
||||
];
|
||||
|
||||
$created = [];
|
||||
$writableOk = [];
|
||||
$failed = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (!is_dir($path)) {
|
||||
if (@mkdir($path, 0775, true)) {
|
||||
$created[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dir($path)) {
|
||||
@chmod($path, 0775);
|
||||
|
||||
$probe = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.write_test_' . str_replace('.', '', uniqid('', true)) . '.tmp';
|
||||
$written = @file_put_contents($probe, 'ok');
|
||||
if ($written !== false) {
|
||||
@unlink($probe);
|
||||
$writableOk[] = $path;
|
||||
} else {
|
||||
$failed[] = $path;
|
||||
}
|
||||
} else {
|
||||
$failed[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Filesystem preparation complete\n";
|
||||
echo "Project root: {$projectRoot}\n";
|
||||
echo "Created dirs: " . count($created) . "\n";
|
||||
echo "Writable OK: " . count($writableOk) . "/" . count($paths) . "\n\n";
|
||||
|
||||
if (!empty($failed)) {
|
||||
echo "FAILED paths (run on server shell):\n";
|
||||
foreach (array_values(array_unique($failed)) as $item) {
|
||||
echo " - {$item}\n";
|
||||
}
|
||||
|
||||
$writablePath = $projectRoot . '/writable';
|
||||
$uploadsPath = $projectRoot . '/public/uploads';
|
||||
|
||||
echo "\nCommands:\n";
|
||||
echo "sudo chown -R www-data:www-data \"{$writablePath}\" \"{$uploadsPath}\"\n";
|
||||
echo "sudo find \"{$writablePath}\" -type d -exec chmod 775 {} \\;\n";
|
||||
echo "sudo find \"{$writablePath}\" -type f -exec chmod 664 {} \\;\n";
|
||||
echo "sudo chmod -R 775 \"{$uploadsPath}\"\n";
|
||||
} else {
|
||||
echo "All required paths are writable.\n";
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
*---------------------------------------------------------------
|
||||
* CHECK PHP VERSION
|
||||
|
|
|
|||
119
public/setup_fs.php
Normal file
119
public/setup_fs.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
$projectRoot = dirname(__DIR__);
|
||||
$envPath = $projectRoot . DIRECTORY_SEPARATOR . '.env';
|
||||
|
||||
$envKey = '';
|
||||
if (is_readable($envPath)) {
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if (is_array($lines)) {
|
||||
foreach ($lines as $line) {
|
||||
$line = trim((string) $line);
|
||||
if ($line === '' || strpos($line, '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stripos($line, 'setup.dbToolsKey') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode('=', $line, 2);
|
||||
$envKey = trim((string) ($parts[1] ?? ''), " \t\n\r\0\x0B\"'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$requestKey = trim((string) ($_GET['key'] ?? ''));
|
||||
if ($envKey === '') {
|
||||
http_response_code(403);
|
||||
echo "setup.dbToolsKey is not configured in .env\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($requestKey === '' || !hash_equals($envKey, $requestKey)) {
|
||||
http_response_code(403);
|
||||
echo "Invalid or missing key. Use: /setup_fs.php?key=YOUR_KEY\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$paths = [
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'cache',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'logs',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'session',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'uploads',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'debugbar',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'writable' . DIRECTORY_SEPARATOR . 'tmp',
|
||||
$projectRoot . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'uploads',
|
||||
];
|
||||
|
||||
$created = [];
|
||||
$chmodOk = [];
|
||||
$writableOk = [];
|
||||
$failed = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (!is_dir($path)) {
|
||||
if (@mkdir($path, 0775, true)) {
|
||||
$created[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dir($path)) {
|
||||
if (@chmod($path, 0775)) {
|
||||
$chmodOk[] = $path;
|
||||
}
|
||||
|
||||
$probe = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.write_test_' . str_replace('.', '', uniqid('', true)) . '.tmp';
|
||||
$written = @file_put_contents($probe, 'ok');
|
||||
if ($written !== false) {
|
||||
@unlink($probe);
|
||||
$writableOk[] = $path;
|
||||
} else {
|
||||
$failed[] = $path;
|
||||
}
|
||||
} else {
|
||||
$failed[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Filesystem preparation complete\n";
|
||||
echo "Project root: {$projectRoot}\n";
|
||||
echo "Created dirs: " . count($created) . "\n";
|
||||
echo "chmod attempted: " . count($chmodOk) . "\n";
|
||||
echo "Writable OK: " . count($writableOk) . "/" . count($paths) . "\n\n";
|
||||
|
||||
if (!empty($created)) {
|
||||
echo "Created:\n";
|
||||
foreach ($created as $item) {
|
||||
echo " - {$item}\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
if (!empty($failed)) {
|
||||
echo "FAILED paths (need server shell fix):\n";
|
||||
foreach (array_values(array_unique($failed)) as $item) {
|
||||
echo " - {$item}\n";
|
||||
}
|
||||
|
||||
$writablePath = $projectRoot . '/writable';
|
||||
$uploadsPath = $projectRoot . '/public/uploads';
|
||||
|
||||
echo "\nRun these on server shell (example www-data):\n";
|
||||
echo "sudo chown -R www-data:www-data \"{$writablePath}\" \"{$uploadsPath}\"\n";
|
||||
echo "sudo find \"{$writablePath}\" -type d -exec chmod 775 {} \\;\n";
|
||||
echo "sudo find \"{$writablePath}\" -type f -exec chmod 664 {} \\;\n";
|
||||
echo "sudo chmod -R 775 \"{$uploadsPath}\"\n";
|
||||
} else {
|
||||
echo "All required paths are writable.\n";
|
||||
}
|
||||
|
||||
echo "\nNext:\n";
|
||||
echo "1) Open /setup/db-tools?key=... again\n";
|
||||
echo "2) Delete this file after success: public/setup_fs.php\n";
|
||||
Loading…
Reference in New Issue
Block a user