Add OPD Total Report with doctor/type/fee filters
This commit is contained in:
parent
ccbc5d7b72
commit
aa2e092758
|
|
@ -717,6 +717,9 @@ $routes->get('report/index', 'Report::index');
|
|||
|
||||
$reportsCollectionFilter = 'permission:reports.collection.view,reports.access';
|
||||
$routes->get('Report/collection_report', 'Report::collection_report', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/report_opd_total', 'Report::report_opd_total', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/opd_total_data/(:segment)/(:segment)/(:segment)/(:segment)', 'Report::opd_total_data/$1/$2/$3/$4', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/opd_total_data/(:segment)/(:segment)/(:segment)/(:segment)/(:num)', 'Report::opd_total_data/$1/$2/$3/$4/$5', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)', 'Report::report_total_payment_app_show/$1/$2/$3', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)/(:num)', 'Report::report_total_payment_app_show/$1/$2/$3/$4', ['filter' => $reportsCollectionFilter]);
|
||||
$routes->get('Report/report_total_payment_app_show/(:segment)/(:segment)/(:segment)/(:segment)', 'Report::report_total_payment_app_show/$1/$2/$3/$4', ['filter' => $reportsCollectionFilter]);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,162 @@ class Report extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
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 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('o.apointment_date >=', $minRange)
|
||||
->where('o.apointment_date <=', $maxRange)
|
||||
->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 = '<style>'
|
||||
. 'body{font-family:Arial,sans-serif;color:#111;font-size:12px;}'
|
||||
. 'table{width:100%;border-collapse:collapse;}'
|
||||
. 'th,td{border:1px solid #9aa4ad;padding:6px 8px;}'
|
||||
. 'th{text-align:left;background:#f5f6f7;}'
|
||||
. '.text-end{text-align:right;}'
|
||||
. '.table-warning td,.table-warning th{background:#fff6cc !important;}'
|
||||
. '</style>'
|
||||
. '<h3 style="margin:0 0 10px 0;">OPD Total Report</h3>'
|
||||
. $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,
|
||||
|
|
@ -274,6 +430,26 @@ class Report extends BaseController
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -386,6 +386,12 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
|
|||
<span>Collection Report</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/report_opd_total') ?>','OPD Total Report')">
|
||||
<i class="bi bi-bar-chart"></i>
|
||||
<span>OPD Total Report</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($canDiagnosis) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Report/diagnosis_report') ?>','Diagnosis Report')">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
</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/report_opd_total') ?>','OPD Total Report');"><i class="bi bi-arrow-right-short"></i> OPD Total Report</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('Finance/bank_deposits') ?>','Bank Deposit Register');"><i class="bi bi-arrow-right-short"></i> Bank Deposit Register</a></li>
|
||||
</ul>
|
||||
|
|
@ -29,6 +30,7 @@
|
|||
</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/report_opd_total') ?>','OPD Total Report');"><i class="bi bi-arrow-right-short"></i> OPD Total Report</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('Finance/index') ?>','Finance & Accounting');"><i class="bi bi-arrow-right-short"></i> Finance & Accounting (Phase 1)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/phase2') ?>','Finance & Accounting - Phase 2');"><i class="bi bi-arrow-right-short"></i> Finance & Accounting (Phase 2)</a></li>
|
||||
|
|
|
|||
116
app/Views/report/opd_total_report.php
Normal file
116
app/Views/report/opd_total_report.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
$doctors = $doctors ?? [];
|
||||
$feeTypes = $fee_types ?? [];
|
||||
?>
|
||||
<section class="content">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">OPD Total Report</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">OPD Date Range</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="date" class="form-control" id="opd_report_start">
|
||||
<input type="date" class="form-control" id="opd_report_end">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Doctor Name</label>
|
||||
<select class="form-control select2" id="doc_name_id" name="doc_name_id">
|
||||
<option value="0">All Doctors</option>
|
||||
<?php foreach ($doctors as $row) : ?>
|
||||
<option value="<?= esc($row->id ?? '') ?>"><?= esc($row->p_fname ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">OPD Type</label>
|
||||
<select class="form-control select2" id="opd_type_id" name="opd_type_id">
|
||||
<option value="0">All Types</option>
|
||||
<option value="running">Running OPD</option>
|
||||
<option value="regular">Regular OPD</option>
|
||||
<option value="new">New Patient OPD</option>
|
||||
<option value="emergency">Emergency OPD</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Specific Fee</label>
|
||||
<select class="form-control select2" id="fee_id" name="fee_id">
|
||||
<option value="0">All Fee Types</option>
|
||||
<?php foreach ($feeTypes as $row) : ?>
|
||||
<option value="<?= esc($row->opd_fee_id ?? '') ?>">
|
||||
<?= esc(($row->opd_fee_desc ?? '') . ' (Fee: ' . number_format((float) ($row->opd_fee_amount ?? 0), 2) . ')') ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-primary" id="show_opd_total_report">Show</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="export_opd_total_report">Export</button>
|
||||
<button type="button" class="btn btn-outline-danger" id="pdf_opd_total_report">PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-body">
|
||||
<div id="opd_total_report_result" class="table-responsive">Select filters and click Show.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function toDateValue(date) {
|
||||
var y = date.getFullYear();
|
||||
var m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
var d = String(date.getDate()).padStart(2, '0');
|
||||
return y + '-' + m + '-' + d;
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
var firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
var lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
document.getElementById('opd_report_start').value = toDateValue(firstDay);
|
||||
document.getElementById('opd_report_end').value = toDateValue(lastDay);
|
||||
|
||||
function buildQuery() {
|
||||
var startVal = document.getElementById('opd_report_start').value;
|
||||
var endVal = document.getElementById('opd_report_end').value;
|
||||
|
||||
if (!startVal || !endVal) {
|
||||
startVal = toDateValue(firstDay);
|
||||
endVal = toDateValue(lastDay);
|
||||
}
|
||||
|
||||
var dateRange = encodeURIComponent(startVal + 'S' + endVal);
|
||||
var doctorId = document.getElementById('doc_name_id').value || '0';
|
||||
var opdType = encodeURIComponent(document.getElementById('opd_type_id').value || '0');
|
||||
var feeId = document.getElementById('fee_id').value || '0';
|
||||
|
||||
return '<?= base_url('Report/opd_total_data') ?>/' + dateRange + '/' + doctorId + '/' + opdType + '/' + feeId;
|
||||
}
|
||||
|
||||
document.getElementById('show_opd_total_report').addEventListener('click', function() {
|
||||
load_form_div(buildQuery(), 'opd_total_report_result');
|
||||
});
|
||||
|
||||
document.getElementById('export_opd_total_report').addEventListener('click', function() {
|
||||
window.open(buildQuery() + '/1', '_blank');
|
||||
});
|
||||
|
||||
document.getElementById('pdf_opd_total_report').addEventListener('click', function() {
|
||||
window.open(buildQuery() + '/2', '_blank');
|
||||
});
|
||||
|
||||
$('.select2').select2({
|
||||
theme: 'bootstrap4',
|
||||
width: '100%'
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
78
app/Views/report/opd_total_report_table.php
Normal file
78
app/Views/report/opd_total_report_table.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$summary = $summary ?? [];
|
||||
$minRange = $min_range ?? '';
|
||||
$maxRange = $max_range ?? '';
|
||||
|
||||
$totalOpd = (int) ($summary['no_of_opd'] ?? 0);
|
||||
$totalRunning = (int) ($summary['running_count'] ?? 0);
|
||||
$totalRegular = (int) ($summary['regular_count'] ?? 0);
|
||||
$totalNew = (int) ($summary['new_count'] ?? 0);
|
||||
$totalEmergency = (int) ($summary['emergency_count'] ?? 0);
|
||||
$totalAmount = (float) ($summary['total_amount'] ?? 0);
|
||||
|
||||
$formatDate = static function ($value): string {
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return date('d-m-Y', $timestamp);
|
||||
};
|
||||
?>
|
||||
|
||||
<p class="mb-2"><strong>OPD Date Range:</strong> <?= esc($formatDate($minRange)) ?> to <?= esc($formatDate($maxRange)) ?></p>
|
||||
|
||||
<?php if (empty($rows)) : ?>
|
||||
<div class="text-muted">No OPD records found for the selected filters.</div>
|
||||
<?php else : ?>
|
||||
<table class="table table-bordered table-sm align-middle">
|
||||
<thead class="table-warning">
|
||||
<tr>
|
||||
<th style="width: 60px;">#</th>
|
||||
<th>Doctor Name</th>
|
||||
<th>OPD Type / Fee Type</th>
|
||||
<th class="text-end" style="width: 95px;">OPD Fee</th>
|
||||
<th class="text-end" style="width: 95px;">No. of OPD</th>
|
||||
<th class="text-end" style="width: 95px;">Running</th>
|
||||
<th class="text-end" style="width: 95px;">Regular</th>
|
||||
<th class="text-end" style="width: 95px;">New</th>
|
||||
<th class="text-end" style="width: 95px;">Emergency</th>
|
||||
<th class="text-end" style="width: 110px;">Total Amt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $srNo = 1; ?>
|
||||
<?php foreach ($rows as $row) : ?>
|
||||
<tr>
|
||||
<td><?= esc($srNo++) ?></td>
|
||||
<td><?= esc('Dr. ' . ($row->doctor_name ?? '')) ?></td>
|
||||
<td><?= esc($row->opd_fee_desc ?? '') ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row->opd_fee_amount ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc((int) ($row->no_of_opd ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc((int) ($row->running_count ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc((int) ($row->regular_count ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc((int) ($row->new_count ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc((int) ($row->emergency_count ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row->total_amount ?? 0), 2)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-warning">
|
||||
<th colspan="4">Total</th>
|
||||
<th class="text-end"><?= esc($totalOpd) ?></th>
|
||||
<th class="text-end"><?= esc($totalRunning) ?></th>
|
||||
<th class="text-end"><?= esc($totalRegular) ?></th>
|
||||
<th class="text-end"><?= esc($totalNew) ?></th>
|
||||
<th class="text-end"><?= esc($totalEmergency) ?></th>
|
||||
<th class="text-end"><?= esc(number_format($totalAmount, 2)) ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
Loading…
Reference in New Issue
Block a user