'reports.collection.view', 'operations' => 'reports.billing_operations.view', 'clinical' => 'diagnosis.report.view', 'ipd' => 'reports.ipd_census.view', 'surgery' => 'reports.ipd_surgery.view', 'insurance' => 'reports.insurance_credit.view', 'quality' => 'reports.nabh_audit.view', 'documents' => 'reports.document_issue.view', ]; public function index() { $panels = []; foreach (self::PANEL_PERMISSIONS as $panel => $permission) { $panels[$panel] = $this->canViewReportPanel($permission); } return view('report/index', ['panels' => $panels]); } private function canViewReportPanel(string $permission): bool { if (! function_exists('auth')) { return false; } $user = auth()->user(); return $user !== null && method_exists($user, 'can') && $user->can($permission); } private function requireReportPermission(string $permission) { if ($this->canViewReportPanel($permission)) { return null; } return $this->response->setStatusCode(403)->setBody('Access denied'); } public function collection_report() { $employees = $this->db->table('users') ->select('id, username') ->where('active', 1) ->orderBy('username', 'ASC') ->get() ->getResult(); $payModes = [ (object) ['id' => 1, 'mode_desc' => 'Cash'], (object) ['id' => 2, 'mode_desc' => 'Bank'], ]; return view('report/collection_report', [ 'employees' => $employees, 'pay_modes' => $payModes, ]); } public function ipd_census_report() { if ($response = $this->requireReportPermission(self::PANEL_PERMISSIONS['ipd'])) { return $response; } $doctors = $this->db->table('doctor_master') ->select('id, p_fname') ->where('active', 1) ->orderBy('p_fname', 'ASC') ->get() ->getResult(); $departments = $this->db->table('hc_department') ->select('iId, vName') ->orderBy('vName', 'ASC') ->get() ->getResult(); return view('report/ipd_census_report', [ 'doctors' => $doctors, 'departments' => $departments, ]); } public function ipd_census_data( string $dateRange, string $doctorId = '0', string $departmentId = '0', string $status = 'all', int $output = 0 ) { if ($response = $this->requireReportPermission(self::PANEL_PERMISSIONS['ipd'])) { return $response; } [$minRange, $maxRange] = $this->parseDateRangeDateOnly($dateRange); $doctorList = $this->db->table('ipd_master_doc_list') ->select('ipd_id, MIN(doc_id) AS doc_id', false) ->groupBy('ipd_id') ->getCompiledSelect(); $builder = $this->db->table('ipd_master i') ->select('i.id, i.ipd_code, i.register_date, i.discharge_date, i.ipd_status') ->select('p.p_code, p.p_fname') ->select("COALESCE(NULLIF(d.vName, ''), 'Unassigned') AS department_name", false) ->select("COALESCE(NULLIF(dm.p_fname, ''), NULLIF(i.r_doc_name, ''), 'Unassigned') AS doctor_name", false) ->select("CASE WHEN i.ipd_status = 1 THEN DATEDIFF(i.discharge_date, i.register_date) + 1 ELSE DATEDIFF('{$maxRange}', i.register_date) + 1 END AS stay_days", false) ->join('patient_master p', 'p.id = i.p_id', 'left') ->join('hc_department d', 'd.iId = i.dept_id', 'left') ->join("({$doctorList}) dl", 'dl.ipd_id = i.id', 'left', false) ->join('doctor_master dm', 'dm.id = dl.doc_id', 'left') ->where("(DATE(i.register_date) BETWEEN '{$minRange}' AND '{$maxRange}' OR DATE(i.discharge_date) BETWEEN '{$minRange}' AND '{$maxRange}')", null, false); if ((int) $doctorId > 0) { $builder->where('dl.doc_id', (int) $doctorId); } if ((int) $departmentId > 0) { $builder->where('i.dept_id', (int) $departmentId); } if ($status === 'active') { $builder->where('i.ipd_status', 0); } elseif ($status === 'discharged') { $builder->where('i.ipd_status', 1); } $rows = $builder->orderBy('i.register_date', 'DESC')->get()->getResult(); $summary = [ 'admissions' => 0, 'discharges' => 0, 'active_cases' => 0, 'average_stay' => 0.0, ]; $dischargedStayDays = 0; foreach ($rows as $row) { $registered = substr((string) ($row->register_date ?? ''), 0, 10); $discharged = substr((string) ($row->discharge_date ?? ''), 0, 10); if ($registered >= $minRange && $registered <= $maxRange) { $summary['admissions']++; } if ((int) ($row->ipd_status ?? 0) === 1 && $discharged >= $minRange && $discharged <= $maxRange) { $summary['discharges']++; $dischargedStayDays += max(0, (int) ($row->stay_days ?? 0)); } if ((int) ($row->ipd_status ?? 0) === 0) { $summary['active_cases']++; } } $summary['average_stay'] = $summary['discharges'] > 0 ? round($dischargedStayDays / $summary['discharges'], 1) : 0.0; $content = view('report/ipd_census_report_table', [ 'rows' => $rows, 'summary' => $summary, 'min_range' => $minRange, 'max_range' => $maxRange, ]); if ($output === 1) { ExportExcel($content, 'IPD_Census_' . date('YmdHis')); return $this->response->setBody(''); } return $this->response->setBody($content); } public function ipd_surgery_report() { if ($response = $this->requireReportPermission(self::PANEL_PERMISSIONS['surgery'])) { return $response; } return view('report/ipd_surgery_report', [ 'doctors' => $this->db->table('doctor_master')->select('id, p_fname')->where('active', 1)->orderBy('p_fname', 'ASC')->get()->getResult(), 'departments' => $this->db->table('hc_department')->select('iId, vName')->orderBy('vName', 'ASC')->get()->getResult(), ]); } public function ipd_surgery_data( string $dateRange, string $doctorId = '0', string $departmentId = '0', int $output = 0 ) { if ($response = $this->requireReportPermission(self::PANEL_PERMISSIONS['surgery'])) { return $response; } [$minRange, $maxRange] = $this->parseDateRangeDateOnly($dateRange); $diagnoses = null; if ($this->db->tableExists('ipd_discharge_diagnosis')) { $diagnosisFields = $this->db->getFieldNames('ipd_discharge_diagnosis'); $diagnosisValueCols = array_values(array_intersect(['snomed_term', 'comp_report', 'comp_remark'], $diagnosisFields)); if (in_array('ipd_id', $diagnosisFields, true) && $diagnosisValueCols !== []) { $diagnosisValue = count($diagnosisValueCols) === 1 ? $diagnosisValueCols[0] : 'COALESCE(' . implode(', ', $diagnosisValueCols) . ')'; $diagnoses = $this->db->table('ipd_discharge_diagnosis') ->select("ipd_id, GROUP_CONCAT(DISTINCT NULLIF(TRIM({$diagnosisValue}), '') ORDER BY id SEPARATOR '; ') AS final_diagnosis", false) ->groupBy('ipd_id') ->getCompiledSelect(); } } $billedProcedures = null; if ($this->db->tableExists('invoice_master') && $this->db->tableExists('invoice_item')) { $invoiceFields = $this->db->getFieldNames('invoice_master'); $itemFields = $this->db->getFieldNames('invoice_item'); if (array_intersect(['ipd_id', 'id'], $invoiceFields) === ['ipd_id', 'id'] && array_intersect(['inv_master_id', 'item_name', 'item_amount'], $itemFields) === ['inv_master_id', 'item_name', 'item_amount']) { $billedBuilder = $this->db->table('invoice_master im') ->select('im.ipd_id, CONVERT(LOWER(TRIM(ii.item_name)) USING utf8mb4) COLLATE utf8mb4_general_ci AS billed_name', false) ->select('SUM(COALESCE(ii.item_amount, 0)) AS actual_surgery_cost', false) ->join('invoice_item ii', 'ii.inv_master_id = im.id', 'inner') ->where('im.ipd_id >', 0); if (in_array('invoice_status', $invoiceFields, true)) { $billedBuilder->where('im.invoice_status', 1); } $billedProcedures = $billedBuilder ->groupBy('im.ipd_id, CONVERT(LOWER(TRIM(ii.item_name)) USING utf8mb4) COLLATE utf8mb4_general_ci') ->getCompiledSelect(); } } $builder = $this->db->table('ipd_master i') ->select('i.id AS ipd_id, i.ipd_code, i.register_date, i.reg_time, i.discharge_date, i.discharge_time') ->select('p.p_code AS mr_no, p.referby AS camp_code, p.p_fname AS patient_name, p.age, p.gender') ->select("CONCAT_WS(', ', NULLIF(TRIM(COALESCE(p.add1, i.add1)), ''), NULLIF(TRIM(COALESCE(p.add2, i.add2)), ''), NULLIF(TRIM(COALESCE(p.city, i.city)), ''), NULLIF(TRIM(COALESCE(p.district, '')), ''), NULLIF(TRIM(COALESCE(p.state, i.state)), ''), NULLIF(TRIM(p.zip), '')) AS address", false) ->select("CONCAT_WS(', ', NULLIF(IF(COALESCE(p.is_hypertesion, 0) = 1, 'Hypertension', ''), ''), NULLIF(IF(COALESCE(p.is_niddm, 0) = 1, 'Diabetes', ''), ''), NULLIF(IF(COALESCE(p.is_smoking, 0) = 1, 'Smoking', ''), ''), NULLIF(IF(COALESCE(p.is_alcohol, 0) = 1, 'Alcohol use', ''), ''), NULLIF(TRIM(p.Others), '')) AS systemic_history", false) ->select("COALESCE(NULLIF(sds.surgery_date, '0000-00-00'), NULLIF(i.surgery_date, '0000-00-00')) AS surgery_date", false) ->select("COALESCE(NULLIF(sds.surgery_name, ''), NULLIF(i.surgery_name, '')) AS surgery_name", false) ->select("'' AS surgery_code", false) ->select('sds.surgery_remark') ->select("COALESCE(NULLIF(dept.vName, ''), 'Unassigned') AS specialty", false) ->select("COALESCE(NULLIF(surgeon.p_fname, ''), NULLIF(i.r_doc_name, ''), 'Unassigned') AS doctor_name", false) ->select("'' AS final_diagnosis, NULL AS estimated_surgery_cost, NULL AS actual_surgery_cost", false) ->join('patient_master p', 'p.id = i.p_id', 'left') ->join('ipd_discharge_surgery sds', 'sds.ipd_id = i.id', 'left') ->join('hc_department dept', 'dept.iId = i.dept_id', 'left') ->join('doctor_master surgeon', 'surgeon.id = sds.surgery_by_doc_id', 'left') ->where("COALESCE(NULLIF(sds.surgery_date, '0000-00-00'), NULLIF(i.surgery_date, '0000-00-00')) BETWEEN '{$minRange}' AND '{$maxRange}'", null, false) ->where("(sds.id IS NOT NULL OR COALESCE(i.issurgery, 0) = 1)", null, false); if ($diagnoses !== null) { $builder->select('diagnosis.final_diagnosis')->join("({$diagnoses}) diagnosis", 'diagnosis.ipd_id = i.id', 'left', false); } if ($this->db->tableExists('ipd_discharge_surgery_master')) { $masterFields = $this->db->getFieldNames('ipd_discharge_surgery_master'); if (array_intersect(['id', 'term_name', 'term_code'], $masterFields) === ['id', 'term_name', 'term_code']) { $builder->select("COALESCE(NULLIF(sds.surgery_name, ''), NULLIF(i.surgery_name, ''), NULLIF(smaster.term_name, '')) AS surgery_name", false) ->select('smaster.term_code AS surgery_code') ->join('ipd_discharge_surgery_master smaster', 'smaster.id = sds.surgery_id', 'left'); } } if ($this->db->tableExists('hc_surgery') && in_array('surg_amount', $this->db->getFieldNames('hc_surgery'), true)) { $builder->select('sm.surg_amount AS estimated_surgery_cost')->join('hc_surgery sm', 'sm.id = sds.surgery_id', 'left'); } if ($billedProcedures !== null) { $builder->select('billed.actual_surgery_cost')->join("({$billedProcedures}) billed", "billed.ipd_id = i.id AND billed.billed_name = CONVERT(LOWER(TRIM(COALESCE(sds.surgery_name, i.surgery_name, ''))) USING utf8mb4) COLLATE utf8mb4_general_ci", 'left', false); } if ((int) $doctorId > 0) { $builder->groupStart()->where('sds.surgery_by_doc_id', (int) $doctorId)->orWhere('i.r_doc_id', (int) $doctorId)->groupEnd(); } if ((int) $departmentId > 0) { $builder->where('i.dept_id', (int) $departmentId); } if ($this->db->tableExists('ipd_ot_cases')) { $otCases = $this->db->table('ipd_ot_cases') ->select('ipd_id') ->select('MAX(scheduled_start_at) AS scheduled_start_at, MAX(actual_start_at) AS actual_start_at, MAX(actual_end_at) AS actual_end_at', false) ->select("GROUP_CONCAT(DISTINCT NULLIF(anesthetist_name_snapshot, '') SEPARATOR '; ') AS anesthetist_name", false) ->select("GROUP_CONCAT(DISTINCT NULLIF(procedure_side, '') SEPARATOR '; ') AS procedure_side", false) ->groupBy('ipd_id') ->getCompiledSelect(); $builder->select('ot.scheduled_start_at, ot.actual_start_at, ot.actual_end_at, ot.anesthetist_name, ot.procedure_side') ->join("({$otCases}) ot", 'ot.ipd_id = i.id', 'left', false); } else { $builder->select("NULL AS scheduled_start_at, NULL AS actual_start_at, NULL AS actual_end_at, '' AS anesthetist_name, '' AS procedure_side", false); } try { $rows = $builder->orderBy('surgery_date', 'ASC')->orderBy('i.id', 'ASC')->get()->getResultArray(); } catch (\Throwable $exception) { log_message('error', 'IPD surgery report query failed: {message}', ['message' => $exception->getMessage()]); return $this->response->setStatusCode(500)->setBody('
Unable to generate the surgery report. The technical details were logged for the administrator.
'); } foreach ($rows as &$row) { $start = ! empty($row['actual_start_at']) ? strtotime((string) $row['actual_start_at']) : false; $end = ! empty($row['actual_end_at']) ? strtotime((string) $row['actual_end_at']) : false; $row['actual_duration_minutes'] = $start !== false && $end !== false && $end >= $start ? (int) round(($end - $start) / 60) : null; } unset($row); $content = view('report/ipd_surgery_report_table', [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, ]); if ($output === 1) { ExportExcel($content, 'IPD_Surgery_Report_' . date('YmdHis')); return $this->response->setBody(''); } return $this->response->setBody($content); } public function report_opd_total() { $doctors = $this->db->table('doctor_master') ->select('id, p_fname') ->where('active', 1) ->orderBy('p_fname', 'ASC') ->get() ->getResult(); $feeTypes = $this->db->table('opd_master') ->select('opd_fee_id, opd_fee_desc, opd_fee_amount') ->where('opd_fee_id IS NOT NULL', null, false) ->where('opd_fee_id >', 0) ->where('opd_fee_desc IS NOT NULL', null, false) ->where("TRIM(opd_fee_desc) != ''", null, false) ->groupBy('opd_fee_id, opd_fee_desc, opd_fee_amount') ->orderBy('opd_fee_desc', 'ASC') ->orderBy('opd_fee_amount', 'ASC') ->get() ->getResult(); return view('report/opd_total_report', [ 'doctors' => $doctors, 'fee_types' => $feeTypes, ]); } public function report_opd_patient_list() { $doctors = $this->db->table('doctor_master') ->select('id, p_fname') ->where('active', 1) ->orderBy('p_fname', 'ASC') ->get() ->getResult(); $referBys = $this->db->table('patient_master') ->select('referby') ->where('referby IS NOT NULL', null, false) ->where("TRIM(referby) != ''", null, false) ->where("TRIM(referby) != '0'", null, false) ->groupBy('referby') ->orderBy('referby', 'ASC') ->get() ->getResult(); return view('report/opd_patient_list_report', [ 'doctors' => $doctors, 'refer_bys' => $referBys, ]); } public function opd_patient_list_data( string $dateRange, string $doctorId = '0', string $referBy = '0', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRangeDateOnly($dateRange); $builder = $this->db->table('opd_master o') ->select('o.opd_id, o.opd_code, o.P_name, p.p_code, o.apointment_date, o.doc_name, p.referby') ->select('o.opd_fee_desc, o.opd_fee_amount, o.payment_mode, o.running_opd') ->join('patient_master p', 'p.id = o.p_id', 'left') ->where("DATE(o.apointment_date) BETWEEN '{$minRange}' AND '{$maxRange}'", null, false) ->orderBy('o.apointment_date', 'ASC') ->orderBy('o.opd_id', 'ASC'); $doctorIdValue = (int) $doctorId; if ($doctorIdValue > 0) { $builder->where('o.doc_id', $doctorIdValue); } $referByValue = trim((string) urldecode($referBy)); if ($referByValue !== '' && $referByValue !== '0' && strtolower($referByValue) !== 'all') { $builder->where('p.referby', $referByValue); } $rows = $builder->get()->getResult(); $content = view('report/opd_patient_list_report_table', [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, ]); if ($output === 1) { ExportExcel($content, 'OPD_Patient_List_' . date('YmdHis')); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

OPD Patient List

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'OPD_Patient_List_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } public function opd_total_data( string $dateRange, string $doctorId = '0', string $opdType = '0', string $feeId = '0', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRangeDateOnly($dateRange); $runningExpr = "(COALESCE(o.running_opd,0)=1 OR o.opd_fee_type=3 OR UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%RUNNING%')"; $newExpr = "(o.opd_fee_type=1 OR UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%NEW%')"; $emergencyExpr = "(UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%EMERG%')"; $regularExpr = "(NOT {$runningExpr} AND NOT {$newExpr} AND NOT {$emergencyExpr})"; $builder = $this->db->table('opd_master o') ->select("COALESCE(NULLIF(TRIM(o.doc_name),''), CONCAT('Doctor #', o.doc_id)) as doctor_name", false) ->select('o.doc_id, o.opd_fee_id, o.opd_fee_desc, o.opd_fee_amount') ->select('SUM(CASE WHEN o.opd_status IN (1,2) THEN 1 ELSE 0 END) as no_of_opd', false) ->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$runningExpr} THEN 1 ELSE 0 END) as running_count", false) ->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$regularExpr} THEN 1 ELSE 0 END) as regular_count", false) ->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$newExpr} THEN 1 ELSE 0 END) as new_count", false) ->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$emergencyExpr} THEN 1 ELSE 0 END) as emergency_count", false) ->select('SUM(CASE WHEN o.opd_status IN (1,2) THEN o.opd_fee_amount ELSE 0 END) as total_amount', false) ->where("DATE(o.apointment_date) BETWEEN '{$minRange}' AND '{$maxRange}'", null, false) ->whereIn('o.opd_status', [1, 2]) ->where('o.payment_status', 1) ->where('(o.payment_mode > 0 OR COALESCE(o.running_opd,0)=1)', null, false); $doctorIdValue = (int) $doctorId; if ($doctorIdValue > 0) { $builder->where('o.doc_id', $doctorIdValue); } $feeIdValue = (int) $feeId; if ($feeIdValue > 0) { $builder->where('o.opd_fee_id', $feeIdValue); } $opdTypeKey = strtolower(trim((string) urldecode($opdType))); if ($opdTypeKey !== '' && $opdTypeKey !== '0' && $opdTypeKey !== 'all') { if ($opdTypeKey === 'running') { $builder->where($runningExpr, null, false); } elseif ($opdTypeKey === 'regular') { $builder->where($regularExpr, null, false); } elseif ($opdTypeKey === 'new') { $builder->where($newExpr, null, false); } elseif ($opdTypeKey === 'emergency') { $builder->where($emergencyExpr, null, false); } } $rows = $builder ->groupBy('o.doc_id, o.doc_name, o.opd_fee_id, o.opd_fee_desc, o.opd_fee_amount') ->orderBy('o.doc_name', 'ASC') ->orderBy('o.opd_fee_desc', 'ASC') ->orderBy('o.opd_fee_amount', 'ASC') ->get() ->getResult(); $summary = [ 'no_of_opd' => 0, 'running_count' => 0, 'regular_count' => 0, 'new_count' => 0, 'emergency_count' => 0, 'total_amount' => 0.0, ]; foreach ($rows as $row) { $summary['no_of_opd'] += (int) ($row->no_of_opd ?? 0); $summary['running_count'] += (int) ($row->running_count ?? 0); $summary['regular_count'] += (int) ($row->regular_count ?? 0); $summary['new_count'] += (int) ($row->new_count ?? 0); $summary['emergency_count'] += (int) ($row->emergency_count ?? 0); $summary['total_amount'] += (float) ($row->total_amount ?? 0); } $content = view('report/opd_total_report_table', [ 'rows' => $rows, 'summary' => $summary, 'min_range' => $minRange, 'max_range' => $maxRange, ]); if ($output === 1) { ExportExcel($content, 'OPD_Total_Report_' . date('YmdHis')); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

OPD Total Report

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'OPD_Total_Report_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } public function report_total_payment_app_show( string $dateRange, string $employeeIds, string $payModeId, string $orderFirst = '0', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $builder = $this->db->table('payment_history p'); $builder->select("p.id as pay_id, p.payment_date,") ->select("date_format(p.payment_date,'%d-%m-%Y %H:%i') as str_payment_date", false) ->select("case p.payof_type when 1 then 'OPD' when 2 then 'Charges' when 3 then 'ORG' when 4 then 'IPD' else 'Other' end as pay_type", false) ->select('p.payof_id, p.payof_code') ->select("if(p.credit_debit=0,p.amount,0) as cr_amount", false) ->select("if(p.credit_debit=1,p.amount,0) as dr_amount", false) ->select('p.remark, p.update_by') ->select("case p.payof_type when 1 then o.P_name when 2 then inv.inv_name when 3 then oc.insurance_company_name when 4 then pat.p_fname else '' end as patient_name", false) ->select("case p.payment_mode when 1 then 'Cash' when 2 then 'Bank' else 'Other' end as pay_mode", false) ->select('b.bank_name as bank_name, s.pay_type as bank_type') ->join('opd_master o', 'p.payof_type = 1 and o.opd_id = p.payof_id', 'left') ->join('invoice_master inv', 'p.payof_type = 2 and inv.id = p.payof_id', 'left') ->join('organization_case_master oc', 'p.payof_type = 3 and oc.id = p.payof_id', 'left') ->join('ipd_master im', 'p.payof_type = 4 and im.id = p.payof_id', 'left') ->join('patient_master pat', 'pat.id = im.p_id', 'left') ->join('hospital_bank_payment_source s', 's.id = p.pay_bank_id', 'left') ->join('hospital_bank b', 'b.id = s.bank_id', 'left') ->where('p.payment_date >=', $minRange) ->where('p.payment_date <=', $maxRange); $empList = $this->normalizeIdList($employeeIds); if (! empty($empList)) { $builder->whereIn('p.update_by_id', $empList); } $payModeId = (int) $payModeId; if ($payModeId < 1) { $builder->whereIn('p.payment_mode', [1, 2]); } else { $builder->where('p.payment_mode', $payModeId); } $builder->orderBy('p.id', 'ASC'); $orderFirst = (int) $orderFirst; if ($orderFirst === 1) { $builder->orderBy('p.update_by', 'ASC'); } elseif ($orderFirst === 2) { $builder->orderBy('p.payment_mode', 'ASC'); } $rows = $builder->get()->getResult(); $totalCr = 0.0; $totalDr = 0.0; foreach ($rows as $row) { $totalCr += (float) ($row->cr_amount ?? 0); $totalDr += (float) ($row->dr_amount ?? 0); } $data = [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, 'total_cr' => $totalCr, 'total_dr' => $totalDr, 'net_total' => $totalCr - $totalDr, ]; $content = view('report/collection_report_table', $data); if ($output === 1) { ExportExcel($content, 'Payment'); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

Collection Report

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'Collection_Report_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } public function report_total_payment_total_amount_show( string $dateRange, string $employeeIds, string $payModeId, int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $builder = $this->db->table('payment_history p'); $builder->select('p.update_by_id as user_id') ->select('COALESCE(u.username, MAX(p.update_by)) as user_name', false) ->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payof_type = 1 THEN p.amount ELSE 0 END) as opd_amount', false) ->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payof_type = 2 THEN p.amount ELSE 0 END) as charge_amount', false) ->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payof_type = 4 THEN p.amount ELSE 0 END) as ipd_amount', false) ->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payof_type = 3 THEN p.amount ELSE 0 END) as org_amount', false) ->select('SUM(CASE WHEN p.credit_debit = 1 THEN p.amount ELSE 0 END) as return_amount', false) ->select('SUM(CASE WHEN p.credit_debit = 0 THEN p.amount ELSE 0 END) - SUM(CASE WHEN p.credit_debit = 1 THEN p.amount ELSE 0 END) as total_amount', false) ->join('users u', 'u.id = p.update_by_id', 'left') ->where('p.payment_date >=', $minRange) ->where('p.payment_date <=', $maxRange); $empList = $this->normalizeIdList($employeeIds); if (! empty($empList)) { $builder->whereIn('p.update_by_id', $empList); } $payMode = (int) $payModeId; if ($payMode < 1) { $builder->whereIn('p.payment_mode', [1, 2]); } else { $builder->where('p.payment_mode', $payMode); } $rows = $builder ->groupBy(['p.update_by_id', 'u.username']) ->orderBy('u.username', 'ASC') ->orderBy('p.update_by_id', 'ASC') ->get() ->getResult(); $totals = [ 'opd_amount' => 0.0, 'charge_amount' => 0.0, 'ipd_amount' => 0.0, 'org_amount' => 0.0, 'return_amount' => 0.0, 'total_amount' => 0.0, ]; foreach ($rows as $row) { $totals['opd_amount'] += (float) ($row->opd_amount ?? 0); $totals['charge_amount'] += (float) ($row->charge_amount ?? 0); $totals['ipd_amount'] += (float) ($row->ipd_amount ?? 0); $totals['org_amount'] += (float) ($row->org_amount ?? 0); $totals['return_amount'] += (float) ($row->return_amount ?? 0); $totals['total_amount'] += (float) ($row->total_amount ?? 0); } $content = view('report/collection_report_total_table', [ 'rows' => $rows, 'totals' => $totals, 'min_range' => $minRange, 'max_range' => $maxRange, ]); if ($output === 1) { ExportExcel($content, 'Collection_Report_Total_' . date('YmdHis')); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

Collection Report - Total Amount

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'Collection_Report_Total_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } private function parseDateRange(string $dateRange): array { $rangeParts = explode('S', $dateRange); $startRaw = $rangeParts[0] ?? ''; $endRaw = $rangeParts[1] ?? ''; $startRaw = str_replace('T', ' ', $startRaw); $endRaw = str_replace('T', ' ', $endRaw); $start = trim($startRaw); $end = trim($endRaw); if ($start === '' || $end === '') { $today = date('Y-m-d'); $start = $today . ' 00:00:00'; $end = $today . ' 23:59:59'; } return [$start, $end]; } private function parseDateRangeDateOnly(string $dateRange): array { $rangeParts = explode('S', $dateRange); $startRaw = (string) ($rangeParts[0] ?? ''); $endRaw = (string) ($rangeParts[1] ?? ''); $startRaw = str_replace('T', ' ', $startRaw); $endRaw = str_replace('T', ' ', $endRaw); $start = substr(trim($startRaw), 0, 10); $end = substr(trim($endRaw), 0, 10); if ($start === '' || $end === '') { $today = date('Y-m-d'); return [$today, $today]; } return [$start, $end]; } private function normalizeIdList(string $idList): array { $idList = trim($idList); if ($idList === '' || $idList === '0') { return []; } $idList = str_replace('S', ',', $idList); $parts = array_filter(array_map('trim', explode(',', $idList))); $ids = []; foreach ($parts as $part) { $value = (int) $part; if ($value > 0) { $ids[] = $value; } } 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(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

Billing Operations Report

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'Billing_Operations_Report_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } public function diagnosis_report() { $itemTypes = $this->db->table('hc_item_type') ->select('itype_id, group_desc') ->whereIn('is_ipd_opd', [0, 1]) ->orderBy('group_desc', 'ASC') ->get() ->getResult(); $referDoctors = $this->db->table('doctor_master') ->select('id, p_fname') ->where('active', 1) ->orderBy('p_fname', 'ASC') ->get() ->getResult(); return view('report/diagnosis_report', [ 'item_types' => $itemTypes, 'refer_doctors' => $referDoctors, ]); } public function document_list() { $doctors = $this->db->table('doctor_master') ->select('id, p_fname') ->where('active', 1) ->orderBy('p_fname', 'ASC') ->get() ->getResult(); return view('report/document_list', [ 'doclist' => $doctors, ]); } public function document_list_data(string $dateRange, int $drId, int $output = 0) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $uhidFilter = trim((string) $this->request->getGet('uhid')); if (! $this->db->tableExists('patient_doc') || ! $this->db->tableExists('doctor_master') || ! $this->db->tableExists('doc_format_master')) { $content = '
Document report tables are not available in this database.
'; return $this->response->setBody($content); } $patientTable = null; if ($this->db->tableExists('patient_master_exten')) { $patientTable = 'patient_master_exten'; } elseif ($this->db->tableExists('patient_master')) { $patientTable = 'patient_master'; } if ($patientTable === null) { $content = '
Patient master table not found for Document Issue Report.
'; return $this->response->setBody($content); } $docFields = $this->db->getFieldNames('patient_doc') ?? []; $patientFields = $this->db->getFieldNames($patientTable) ?? []; $doctorFields = $this->db->getFieldNames('doctor_master') ?? []; $formatFields = $this->db->getFieldNames('doc_format_master') ?? []; $dateIssueCol = $this->resolveExistingColumn($docFields, ['date_issue', 'issue_date', 'created_at']); if ($dateIssueCol === null) { $content = '
Issue date column was not found in patient_doc table.
'; return $this->response->setBody($content); } $docPatientFkCol = $this->resolveExistingColumn($docFields, ['p_id', 'patient_id']); $docDoctorFkCol = $this->resolveExistingColumn($docFields, ['dr_id', 'doc_id', 'doctor_id']); $docFormatFkCol = $this->resolveExistingColumn($docFields, ['doc_format_id', 'format_id', 'df_id']); if ($docPatientFkCol === null || $docDoctorFkCol === null || $docFormatFkCol === null) { $content = '
Document link columns are missing in patient_doc table.
'; return $this->response->setBody($content); } $patientPkCol = $this->resolveExistingColumn($patientFields, ['id']); $patientNameCol = $this->resolveExistingColumn($patientFields, ['p_fname', 'patient_name', 'name']); $patientRelativeCol = $this->resolveExistingColumn($patientFields, ['p_relative']); $patientRelativeNameCol = $this->resolveExistingColumn($patientFields, ['p_rname', 'relative_name']); $patientCodeCol = $this->resolveExistingColumn($patientFields, ['p_code', 'patient_code', 'uhid_no']); $doctorPkCol = $this->resolveExistingColumn($doctorFields, ['id']); $doctorNameCol = $this->resolveExistingColumn($doctorFields, ['p_fname', 'name', 'doc_name']); $formatPkCol = $this->resolveExistingColumn($formatFields, ['df_id', 'id']); $formatNameCol = $this->resolveExistingColumn($formatFields, ['doc_name', 'name', 'format_name']); if ($patientPkCol === null || $patientNameCol === null || $doctorPkCol === null || $doctorNameCol === null || $formatPkCol === null || $formatNameCol === null) { $content = '
Required patient columns are missing for Document Issue Report.
'; return $this->response->setBody($content); } try { $builder = $this->db->table('patient_doc d'); $builder->select('d.id AS doc_id') ->select('f.' . $formatNameCol . ' AS doc_name', false) ->select('doc.' . $doctorNameCol . ' AS dr_name', false) ->select('doc.' . $doctorPkCol . ' AS dr_id', false) ->select('p.' . $patientNameCol . ' AS p_fname', false) ->select('p.' . $patientPkCol . ' AS id', false) ->select($patientRelativeNameCol !== null ? ('p.' . $patientRelativeNameCol . ' AS p_rname') : "'' AS p_rname", false) ->select($patientRelativeCol !== null ? ('p.' . $patientRelativeCol . ' AS p_relative') : "'' AS p_relative", false) ->select($patientCodeCol !== null ? ('p.' . $patientCodeCol . ' AS p_code') : "'' AS p_code", false) ->select('d.' . $dateIssueCol . ' AS date_issue', false) ->select("DATE_FORMAT(d." . $dateIssueCol . ", '%d-%m-%Y') AS str_date_issue", false) ->join($patientTable . ' p', 'p.' . $patientPkCol . '=d.' . $docPatientFkCol, 'inner') ->join('doctor_master doc', 'd.' . $docDoctorFkCol . '=doc.' . $doctorPkCol, 'inner') ->join('doc_format_master f', 'd.' . $docFormatFkCol . '=f.' . $formatPkCol, 'inner') ->where('d.' . $dateIssueCol . ' >=', $minRange) ->where('d.' . $dateIssueCol . ' <=', $maxRange) ->orderBy('d.' . $dateIssueCol, 'DESC') ->orderBy('d.id', 'DESC'); if ($drId > 0) { $builder->where('doc.id', $drId); } if ($uhidFilter !== '' && $patientCodeCol !== null) { $builder->like('p.' . $patientCodeCol, $uhidFilter); } $rows = $builder->get()->getResult(); } catch (\Throwable $e) { log_message('error', 'Document Issue Report query failed: {message}', ['message' => $e->getMessage()]); $content = '
Unable to load Document Issue Report data. Please check database schema.
'; return $this->response->setBody($content); } $data = [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, 'doctor_id' => $drId, 'uhid_filter' => $uhidFilter, ]; $content = view('report/document_list_table', $data); if ($output === 1) { ExportExcel($content, 'document_List_' . date('YmdHis')); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

Document Issue Report

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'Document_Issue_Report_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } public function diagnosis_report_data( string $dateRange, string $invoiceType = '0', string $diagnosisIds = '0', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $referDoctorId = (int) ($this->request->getGet('refer_doctor_id') ?? 0); $referDoctorLabel = 'All Refer Doctors'; $netAmountExpr = "ROUND(IF(m.invoice_status=1, (t.item_amount - (t.item_amount * m.discount_amount / NULLIF(m.total_amount, 0))), 0), 2)"; $invoiceFields = $this->db->getFieldNames('invoice_master') ?? []; $referByIdCol = $this->resolveExistingColumn($invoiceFields, ['refer_by_id', 'doctor_id', 'doc_id']); $referByNameCol = $this->resolveExistingColumn($invoiceFields, ['refer_by_other', 'doc_name', 'doctor_name']); if ($referDoctorId > 0 && $this->db->tableExists('doctor_master')) { $doctorRow = $this->db->table('doctor_master') ->select('p_fname') ->where('id', $referDoctorId) ->get() ->getRow(); if ($doctorRow !== null && trim((string) ($doctorRow->p_fname ?? '')) !== '') { $referDoctorLabel = (string) $doctorRow->p_fname; } } $builder = $this->db->table('invoice_master m'); $builder->select('y.group_desc, t.item_name') ->select('COUNT(t.id) as no_test', false) ->select('SUM(t.item_qty) as no_qty', false) ->select('SUM(' . $netAmountExpr . ') as total_amount', false) ->select('SUM(IF(m.invoice_status=1, t.item_amount, 0)) as total_act_amount', false) ->select('SUM(CASE WHEN m.ipd_id = 0 AND IFNULL(m.insurance_case_id, 0) = 0 THEN ' . $netAmountExpr . ' ELSE 0 END) as direct_patient_amount', false) ->select('SUM(CASE WHEN m.ipd_id > 0 AND IFNULL(m.insurance_case_id, 0) = 0 THEN ' . $netAmountExpr . ' ELSE 0 END) as ipd_direct_amount', false) ->select('SUM(CASE WHEN m.ipd_id > 0 AND IFNULL(m.insurance_case_id, 0) > 0 THEN ' . $netAmountExpr . ' ELSE 0 END) as ipd_tpa_amount', false) ->select('SUM(CASE WHEN m.ipd_id = 0 AND IFNULL(m.insurance_case_id, 0) > 0 THEN ' . $netAmountExpr . ' ELSE 0 END) as org_credit_amount', false) ->join('invoice_item t', 'm.id = t.inv_master_id', 'inner') ->join('hc_item_type y', 't.item_type = y.itype_id', 'inner') ->where('y.is_ipd_opd IN (0, 1)', null, false) ->where('m.invoice_status', 1) ->where('m.inv_date >=', $minRange) ->where('m.inv_date <=', $maxRange); if ($referDoctorId > 0) { if ($referByIdCol !== null) { $builder->where('m.' . $referByIdCol, $referDoctorId); } elseif ($referByNameCol !== null && $referDoctorLabel !== 'All Refer Doctors') { $builder->where('m.' . $referByNameCol, $referDoctorLabel); } } // Invoice type filter $invoiceTypeInt = (int) $invoiceType; if ($invoiceTypeInt === 1) { // IPD $builder->where('m.ipd_id >', 0); } elseif ($invoiceTypeInt === 2) { // OPD $builder->where('m.ipd_id', 0) ->where('m.insurance_case_id', 0); } elseif ($invoiceTypeInt === 3) { // Organization $builder->where('m.insurance_case_id >', 0); } $diagList = $this->normalizeIdList($diagnosisIds); if (!empty($diagList)) { $builder->whereIn('t.item_type', $diagList); } $builder->groupBy(['y.group_desc', 't.item_id', 't.item_name']) ->orderBy('y.group_desc', 'ASC') ->orderBy('t.item_name', 'ASC'); $rows = $builder->get()->getResult(); $invoiceTypeLabel = ['All Types', 'IPD', 'OPD', 'Organization'][$invoiceTypeInt] ?? 'All Types'; $data = [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, 'invoice_type_label' => $invoiceTypeLabel, 'refer_doctor_label' => $referDoctorLabel, ]; $content = view('report/diagnosis_report_table', $data); if ($output === 1) { ExportExcel($content, 'Diagnosis_Report'); return $this->response->setBody(''); } if ($output === 2) { $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; if (! is_dir($mpdfTempDir)) { mkdir($mpdfTempDir, 0755, true); } $mpdf = new Mpdf([ 'tempDir' => $mpdfTempDir, 'format' => 'A4-L', 'margin_top' => 8, 'margin_bottom' => 8, 'margin_left' => 8, 'margin_right' => 8, ]); $pdfHtml = '' . '

Diagnosis Report

' . $content; $mpdf->WriteHTML($pdfHtml); $fileName = 'Diagnosis_Report_' . date('Ymd_His') . '.pdf'; return $this->response ->setHeader('Content-Type', 'application/pdf') ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->setBody($mpdf->Output($fileName, 'S')); } return $this->response->setBody($content); } // ==================== Insurance Credit Reports ==================== public function insurance_credit_main() { 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') ->orderBy('tpa_group', 'ASC') ->get() ->getResult(); $insuranceCompanies = $this->db->table('hc_insurance') ->orderBy('short_name', 'ASC') ->get() ->getResult(); return view('report/insurance_opd_report', [ 'insurance_groups' => $insuranceGroups, 'insurance_companies' => $insuranceCompanies, ]); } public function insurance_ipd_report() { $insuranceGroups = $this->db->table('h_insurance_group') ->orderBy('tpa_group', 'ASC') ->get() ->getResult(); $insuranceCompanies = $this->db->table('hc_insurance') ->orderBy('short_name', 'ASC') ->get() ->getResult(); return view('report/insurance_ipd_report', [ 'insurance_groups' => $insuranceGroups, 'insurance_companies' => $insuranceCompanies, ]); } public function insurance_combined_report() { $insuranceGroups = $this->db->table('h_insurance_group') ->orderBy('tpa_group', 'ASC') ->get() ->getResult(); $insuranceCompanies = $this->db->table('hc_insurance') ->orderBy('short_name', 'ASC') ->get() ->getResult(); return view('report/insurance_combined_report', [ 'insurance_groups' => $insuranceGroups, 'insurance_companies' => $insuranceCompanies, ]); } public function ayushman_unmapped_report() { $specialities = []; if ($this->db->tableExists('ayushman_package_master')) { $specialities = $this->db->table('ayushman_package_master') ->select('speciality_code, speciality_name') ->groupBy('speciality_code, speciality_name') ->orderBy('speciality_name', 'ASC') ->get() ->getResult(); } return view('report/ayushman_unmapped_report', [ 'specialities' => $specialities, ]); } public function ayushman_unmapped_report_data(string $specialityCode = '0', int $output = 0) { if (! $this->db->tableExists('ayushman_package_master')) { return $this->response->setBody('
Ayushman package master table is not available.
'); } $builder = $this->db->table('ayushman_package_master a') ->select('a.speciality_code, a.speciality_name, a.procedure_code, a.procedure_name, a.package_amount, a.preauth_required, a.pre_investigations, a.post_investigations') ->where('(a.linked_package_id IS NULL OR a.linked_package_id = 0)', null, false) ->orderBy('a.speciality_name', 'ASC') ->orderBy('a.procedure_name', 'ASC'); if ($specialityCode !== '0') { $builder->where('a.speciality_code', $specialityCode); } $rows = $builder->get()->getResult(); $data = [ 'rows' => $rows, 'speciality_code' => $specialityCode, ]; $content = view('report/ayushman_unmapped_report_table', $data); if ($output === 1) { ExportExcel($content, 'Ayushman_Unmapped_Procedures_' . date('YmdHis')); return $this->response->setBody(''); } return $this->response->setBody($content); } public function ayushman_case_dashboard() { return view('report/ayushman_case_dashboard'); } public function ayushman_case_dashboard_data(string $dateRange, string $alertType = 'all', int $output = 0) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $builder = $this->db->table('organization_case_master o') ->select('o.id as case_id, o.case_id_code, o.preauth_send, o.doc_recd, o.final_bill_send, o.org_approved_status, o.Org_insurance_comp') ->select('i.id as ipd_id, i.ipd_code') ->select("DATE_FORMAT(i.register_date, '%d-%m-%Y') as str_register_date", false) ->select('p.p_code, p.p_fname') ->select('ins.short_name as insurance_company') ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id) as ayushman_proc_count', false) ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id AND (a.linked_package_id IS NULL OR a.linked_package_id = 0)) as unmapped_count', false) ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id AND a.preauth_required = 1) as preauth_required_count', false) ->join('ipd_master i', 'o.id = i.case_id', 'inner') ->join('patient_master p', 'i.p_id = p.id', 'inner') ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'left') ->where('o.case_type', 1) ->where('i.register_date >=', $minRange) ->where('i.register_date <=', $maxRange) ->groupStart() ->like('o.Org_insurance_comp', 'ayushman') ->orLike('o.Org_insurance_comp', 'pmjay') ->orLike('ins.short_name', 'ayushman') ->orLike('ins.ins_company_name', 'ayushman') ->groupEnd() ->orderBy('i.id', 'DESC'); $rows = $builder->get()->getResultArray(); $alertType = strtolower(trim($alertType)); if (! in_array($alertType, ['all', 'preauth-pending', 'docs-pending', 'mapping-gap'], true)) { $alertType = 'all'; } $rows = array_values(array_filter($rows, static function (array $row) use ($alertType): bool { $preauthPending = (int) ($row['preauth_required_count'] ?? 0) > 0 && (int) ($row['preauth_send'] ?? 0) !== 1; $docsPending = (int) ($row['doc_recd'] ?? 0) !== 1; $mappingGap = (int) ($row['unmapped_count'] ?? 0) > 0; if ($alertType === 'preauth-pending') { return $preauthPending; } if ($alertType === 'docs-pending') { return $docsPending; } if ($alertType === 'mapping-gap') { return $mappingGap; } return true; })); $summary = [ 'total_cases' => count($rows), 'preauth_pending' => 0, 'docs_pending' => 0, 'mapping_gaps' => 0, ]; foreach ($rows as $row) { if ((int) ($row['preauth_required_count'] ?? 0) > 0 && (int) ($row['preauth_send'] ?? 0) !== 1) { $summary['preauth_pending']++; } if ((int) ($row['doc_recd'] ?? 0) !== 1) { $summary['docs_pending']++; } if ((int) ($row['unmapped_count'] ?? 0) > 0) { $summary['mapping_gaps']++; } } $data = [ 'rows' => $rows, 'summary' => $summary, 'min_range' => $minRange, 'max_range' => $maxRange, 'alert_type' => $alertType, ]; $content = view('report/ayushman_case_dashboard_table', $data); if ($output === 1) { ExportExcel($content, 'Ayushman_Case_Dashboard_' . date('YmdHis')); return $this->response->setBody(''); } return $this->response->setBody($content); } public function insurance_opd_report_data( string $dateRange, string $insuranceId = '0', string $caseStatus = '-1', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $builder = $this->db->table('organization_case_master o'); $builder->select('o.id, o.case_id_code') ->select("CONCAT(o.insurance_no, '/', o.insurance_no_1) as case_info", false) ->select('p.p_code, p.p_fname') ->select('ins.short_name as insurance_company') ->select("DATE_FORMAT(o.date_registration, '%d-%m-%Y') as str_date_registration", false) ->select("(CASE o.status WHEN 0 THEN 'Pending' WHEN 1 THEN 'Bill Complete' WHEN 2 THEN 'Submitted' ELSE 'Unknown' END) as org_submit_status", false) ->select('(o.inv_opd_amt + o.inv_opd_charge_amt) as charge_amount', false) ->select('o.inv_opd_med_amt as med_amount') ->select('((o.inv_opd_amt + o.inv_opd_charge_amt) + o.inv_opd_med_amt) as net_amount', false) ->join('patient_master p', 'o.p_id = p.id', 'inner') ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'inner') ->where('o.case_type', 0) ->where('o.date_registration >=', $minRange) ->where('o.date_registration <=', $maxRange) ->where('((o.inv_opd_amt + o.inv_opd_charge_amt) + o.inv_opd_med_amt) >', 0); // Insurance filter if ($insuranceId !== '0') { if (strpos($insuranceId, 'G') === 0) { // Insurance Group $groupId = (int) substr($insuranceId, 1); $builder->where('ins.group_ins', $groupId); } else { // Individual Insurance $builder->where('o.insurance_id', (int) $insuranceId); } } // Status filter $statusInt = (int) $caseStatus; if ($statusInt >= 0) { $builder->where('o.status', $statusInt); } $builder->orderBy('o.id', 'ASC'); $rows = $builder->get()->getResult(); $data = [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, 'case_type' => 'OPD', ]; $content = view('report/insurance_opd_report_table', $data); if ($output === 1) { ExportExcel($content, 'Insurance_OPD_Report'); return $this->response->setBody(''); } return $this->response->setBody($content); } public function insurance_ipd_report_data( string $dateRange, string $insuranceId = '0', string $caseStatus = '-1', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $builder = $this->db->table('organization_case_master o'); $builder->select('o.id, o.case_id_code') ->select("CONCAT(o.insurance_no, '/', o.insurance_no_1) as case_info", false) ->select('i.ipd_code, p.p_code, p.p_fname') ->select('ins.short_name as insurance_company') ->select("DATE_FORMAT(i.register_date, '%d-%m-%Y') as str_register_date", false) ->select("DATE_FORMAT(i.discharge_date, '%d-%m-%Y') as str_discharge_date", false) ->select("(CASE o.status WHEN 0 THEN 'Pending' WHEN 1 THEN 'Bill Complete' WHEN 2 THEN 'Submitted' ELSE 'Unknown' END) as org_submit_status", false) ->select('(i.charge_amount + i.chargeamount1 + i.chargeamount2) as charge_amount', false) ->select('i.med_amount') ->select('(i.Discount + i.Discount2 + i.Discount3) as discount', false) ->select('((i.charge_amount + i.chargeamount1 + i.chargeamount2 + i.med_amount) - (i.Discount + i.Discount2 + i.Discount3)) as net_amount', false) ->select('o.amount_recived, o.amount_deduction') ->join('ipd_master i', 'o.id = i.case_id', 'inner') ->join('patient_master p', 'i.p_id = p.id', 'inner') ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'inner') ->where('o.case_type', 1) ->where('i.ipd_status', 1) ->where('i.register_date >=', $minRange) ->where('i.register_date <=', $maxRange); // Insurance filter if ($insuranceId !== '0') { if (strpos($insuranceId, 'G') === 0) { // Insurance Group $groupId = (int) substr($insuranceId, 1); $builder->where('ins.group_ins', $groupId); } else { // Individual Insurance $builder->where('o.insurance_id', (int) $insuranceId); } } // Status filter $statusInt = (int) $caseStatus; if ($statusInt >= 0) { $builder->where('o.status', $statusInt); } $builder->orderBy('i.id', 'ASC'); $rows = $builder->get()->getResult(); $data = [ 'rows' => $rows, 'min_range' => $minRange, 'max_range' => $maxRange, 'case_type' => 'IPD', ]; $content = view('report/insurance_ipd_report_table', $data); if ($output === 1) { ExportExcel($content, 'Insurance_IPD_Report'); return $this->response->setBody(''); } return $this->response->setBody($content); } public function insurance_combined_report_data( string $dateRange, string $caseType = '-1', string $insuranceId = '0', int $output = 0 ) { [$minRange, $maxRange] = $this->parseDateRange($dateRange); $caseTypeInt = (int) $caseType; $data = [ 'min_range' => $minRange, 'max_range' => $maxRange, 'opd_rows' => [], 'ipd_rows' => [], ]; // Get OPD data if needed if ($caseTypeInt === -1 || $caseTypeInt === 0) { $builder = $this->db->table('organization_case_master o'); $builder->select('ins.short_name as insurance_company') ->select('COUNT(o.id) as total_cases', false) ->select('SUM(o.inv_opd_amt + o.inv_opd_charge_amt) as total_charge', false) ->select('SUM(o.inv_opd_med_amt) as total_med', false) ->select('SUM((o.inv_opd_amt + o.inv_opd_charge_amt) + o.inv_opd_med_amt) as total_net', false) ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'inner') ->where('o.case_type', 0) ->where('o.date_registration >=', $minRange) ->where('o.date_registration <=', $maxRange); if ($insuranceId !== '0') { if (strpos($insuranceId, 'G') === 0) { $groupId = (int) substr($insuranceId, 1); $builder->where('ins.group_ins', $groupId); } else { $builder->where('o.insurance_id', (int) $insuranceId); } } $builder->groupBy('ins.short_name')->orderBy('ins.short_name', 'ASC'); $data['opd_rows'] = $builder->get()->getResult(); } // Get IPD data if needed if ($caseTypeInt === -1 || $caseTypeInt === 1) { $builder = $this->db->table('organization_case_master o'); $builder->select('ins.short_name as insurance_company') ->select('COUNT(i.id) as total_cases', false) ->select('SUM(i.charge_amount + i.chargeamount1 + i.chargeamount2) as total_charge', false) ->select('SUM(i.med_amount) as total_med', false) ->select('SUM(i.Discount + i.Discount2 + i.Discount3) as total_discount', false) ->select('SUM((i.charge_amount + i.chargeamount1 + i.chargeamount2 + i.med_amount) - (i.Discount + i.Discount2 + i.Discount3)) as total_net', false) ->select('SUM(o.amount_recived) as total_received', false) ->select('SUM(o.amount_deduction) as total_deducted', false) ->join('ipd_master i', 'o.id = i.case_id', 'inner') ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'inner') ->where('o.case_type', 1) ->where('i.ipd_status', 1) ->where('i.register_date >=', $minRange) ->where('i.register_date <=', $maxRange); if ($insuranceId !== '0') { if (strpos($insuranceId, 'G') === 0) { $groupId = (int) substr($insuranceId, 1); $builder->where('ins.group_ins', $groupId); } else { $builder->where('o.insurance_id', (int) $insuranceId); } } $builder->groupBy('ins.short_name')->orderBy('ins.short_name', 'ASC'); $data['ipd_rows'] = $builder->get()->getResult(); } $content = view('report/insurance_combined_report_table', $data); if ($output === 1) { ExportExcel($content, 'Insurance_Combined_Report'); return $this->response->setBody(''); } 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', 'radiology'], 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); } $radiologyRows = []; if ($module === 'all' || $module === 'radiology') { $radiologyRows = $this->getRadiologyNabhAuditRows($minRange, $maxRange); } $rows = array_merge($ipdRows, $opdRows, $radiologyRows); 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], 'radiology' => ['label' => 'Radiology Edit', 'total' => 0, 'compliant' => 0, 'critical_missing' => 0, 'avg_completion' => 0.0], ]; $completionAccumulator = [ 'all' => 0.0, 'ipd' => 0.0, 'opd' => 0.0, 'radiology' => 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 getRadiologyNabhAuditRows(string $minRange, string $maxRange): array { if (! $this->db->tableExists('lab_log')) { return []; } $logFields = $this->db->getFieldNames('lab_log') ?? []; foreach (['lab_repo_id', 'log_insert_time', 'log_type', 'log_Faults', 'comments'] as $requiredField) { if (! in_array($requiredField, $logFields, true)) { return []; } } $builder = $this->db->table('lab_log lg') ->select('lg.lab_repo_id as encounter_id') ->select('lg.log_insert_time as encounter_datetime') ->select('lg.log_by as edited_by') ->select('lg.comments as comments') ->where('lg.log_type', 'Report Edit') ->where('lg.log_Faults', 'Radiology') ->where('lg.log_insert_time >=', $minRange) ->where('lg.log_insert_time <=', $maxRange) ->orderBy('lg.id', 'DESC'); if ($this->db->tableExists('lab_request')) { $labReqFields = $this->db->getFieldNames('lab_request') ?? []; if (in_array('id', $labReqFields, true)) { $builder->join('lab_request lr', 'lr.id = lg.lab_repo_id', 'left'); if (in_array('report_name', $labReqFields, true)) { $builder->select('lr.report_name as report_name'); } else { $builder->select("'' as report_name", false); } if (in_array('patient_id', $labReqFields, true) && $this->db->tableExists('patient_master')) { $patientFields = $this->db->getFieldNames('patient_master') ?? []; if (in_array('id', $patientFields, true)) { $builder->join('patient_master p', 'p.id = lr.patient_id', 'left'); if (in_array('p_code', $patientFields, true)) { $builder->select('p.p_code as patient_code'); } else { $builder->select("'' as patient_code", false); } if (in_array('p_fname', $patientFields, true)) { $builder->select('p.p_fname as patient_name'); } else { $builder->select("'' as patient_name", false); } } } else { $builder->select("'' as patient_code", false); $builder->select("'' as patient_name", false); } } } $resultRows = $builder->get()->getResultArray(); $finalRows = []; foreach ($resultRows as $row) { $reason = $this->extractEditReasonFromLabLogComment((string) ($row['comments'] ?? '')); $hasReason = $reason !== '' && strtolower($reason) !== 'na'; $encounterCode = trim((string) ($row['report_name'] ?? '')); if ($encounterCode === '') { $encounterCode = 'REQ#' . (int) ($row['encounter_id'] ?? 0); } $finalRows[] = [ 'module_key' => 'radiology', 'module_label' => 'Radiology Edit', 'encounter_id' => (int) ($row['encounter_id'] ?? 0), 'encounter_code' => $encounterCode, 'patient_code' => (string) ($row['patient_code'] ?? ''), 'patient_name' => (string) ($row['patient_name'] ?? ''), 'encounter_datetime' => (string) ($row['encounter_datetime'] ?? ''), 'ok_count' => $hasReason ? 1 : 0, 'total_count' => 1, 'completion_percent' => $hasReason ? 100.0 : 0.0, 'critical_missing_count' => $hasReason ? 0 : 1, 'critical_missing_labels' => $hasReason ? '-' : 'Edit reason missing', ]; } return $finalRows; } private function extractEditReasonFromLabLogComment(string $comment): string { $comment = trim($comment); if ($comment === '') { return ''; } if (preg_match('/\[reason:(.*?)(?:\]\s*\[|\]$)/i', $comment, $matches) === 1) { return trim((string) ($matches[1] ?? '')); } $startPos = stripos($comment, '[reason:'); if ($startPos === false) { return ''; } $raw = substr($comment, $startPos + 8); $beforePos = stripos($raw, '[before:'); if ($beforePos !== false) { $raw = substr($raw, 0, $beforePos); } return trim(str_replace(']', '', $raw)); } 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 : []; } }