financial fix
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
<div class="d-flex align-items-center gap-2 my-3 flex-nowrap w-100 overflow-auto" style="white-space: nowrap;">
|
||||
<!-- School year filter -->
|
||||
<form id="summaryYearFilter" class="d-flex align-items-center gap-2 flex-nowrap me-auto" method="get" action="<?= site_url('financial-report/financialReportSummary') ?>" style="white-space: nowrap;">
|
||||
<input type="hidden" name="date_from" value="<?= esc((string)($dateFrom ?? '')) ?>">
|
||||
<input type="hidden" name="date_to" value="<?= esc((string)($dateTo ?? '')) ?>">
|
||||
<label class="form-label mb-0 me-2">School year</label>
|
||||
<select name="school_year" class="form-select form-select-sm rounded-pill px-3" style="min-width: 180px;">
|
||||
<?php foreach (($schoolYears ?? []) as $sy): ?>
|
||||
@@ -16,7 +18,8 @@
|
||||
</form>
|
||||
<!-- Action buttons -->
|
||||
<div class="d-flex align-items-center gap-2 flex-nowrap flex-shrink-0" style="white-space: nowrap;">
|
||||
<a href="<?= base_url('reports/downloadFinancialReport') ?>" class="btn btn-primary">Download PDF</a>
|
||||
<a id="downloadSummaryCsvLink" href="<?= base_url('financial-report/downloadSummaryCsv') ?>" class="btn btn-success">Download CSV</a>
|
||||
<a id="downloadSummaryAllDetailsCsvLink" href="<?= base_url('financial-report/downloadSummaryAllDetailsCsv') ?>" class="btn btn-primary">Download All Details CSV</a>
|
||||
<a href="<?= base_url('/payment/financial_report') ?>" class="btn btn-secondary">Back To Detailed Report</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,21 +34,31 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="summaryBody">
|
||||
<tr><td>Total Charges</td><td class="text-right" id="sumCharges">$0.00</td></tr>
|
||||
<tr><td>Event Fees</td><td class="text-right" id="sumEventFees">$0.00</td></tr>
|
||||
<tr><td>Total Extra Charges</td><td class="text-right" id="sumExtraCharges">$0.00</td></tr>
|
||||
<tr><td>Tuition Charges</td><td class="text-right" id="sumTuitionCharges">$0.00</td></tr>
|
||||
<tr><td>Event Fee Charges</td><td class="text-right" id="sumEventFees">$0.00</td></tr>
|
||||
<tr><td>Prior-Year Tuition Carryover</td><td class="text-right" id="sumExtraCharges">$0.00</td></tr>
|
||||
<tr><td>Gross Charges</td><td class="text-right" id="sumGrossCharges">$0.00</td></tr>
|
||||
<tr><td>Total Discounts</td><td class="text-right" id="sumDiscounts">$0.00</td></tr>
|
||||
<tr><td>Total Refunds</td><td class="text-right" id="sumRefunds">$0.00</td></tr>
|
||||
<tr><td>Total Expenses</td><td class="text-right" id="sumExpenses">$0.00</td></tr>
|
||||
<tr><td>Total Reimbursements</td><td class="text-right" id="sumReimb">$0.00</td></tr>
|
||||
<tr><td>Donation to School (Masjid & Donation)</td><td class="text-right" id="sumDonationToSchool">$0.00</td></tr>
|
||||
<tr class="table-success"><td>Net Amount (Earned Income)</td><td class="text-right font-weight-bold" id="sumNet">$0.00</td></tr>
|
||||
<tr><td>Donation to School (included in expenses)</td><td class="text-right" id="sumDonationToSchool">$0.00</td></tr>
|
||||
<tr class="table-success"><td>Net Charges After Discounts/Refunds</td><td class="text-right font-weight-bold" id="sumNet">$0.00</td></tr>
|
||||
<tr class="table-info"><td>Amount Collected (Paid)</td><td class="text-right font-weight-bold" id="sumCollected">$0.00</td></tr>
|
||||
<tr class="table-warning"><td>Amount Unpaid (Outstanding)</td><td class="text-right font-weight-bold" id="sumUnpaid">$0.00</td></tr>
|
||||
<tr><td>Overpayment Credits</td><td class="text-right" id="sumOverpaid">$0.00</td></tr>
|
||||
<tr><td>Gross Outstanding Before Credits</td><td class="text-right" id="sumUnpaid">$0.00</td></tr>
|
||||
<tr class="table-warning"><td>Amount Unpaid (Net of Credits)</td><td class="text-right font-weight-bold" id="sumNetReceivable">$0.00</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="small text-muted mb-3" id="summaryNetFormula"></p>
|
||||
|
||||
<div class="alert alert-light border my-3" id="summaryReconciliationBox">
|
||||
<div class="fw-semibold">Reconciliation</div>
|
||||
<div id="summaryReconciliationText" class="small text-muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h3>Summary Graph</h3>
|
||||
<canvas id="summaryChart" style="max-height:300px;"></canvas>
|
||||
@@ -55,13 +68,13 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<h3 class="h5">Collected vs Outstanding</h3>
|
||||
<h3 class="h5">Collected vs Net Outstanding</h3>
|
||||
<canvas id="collectedChart" style="max-height:320px; width:100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<h3 class="h5">Expenses out of Net Amount</h3>
|
||||
<h3 class="h5">Expenses out of Net Charges</h3>
|
||||
<canvas id="netExpenseChart" style="max-height:320px; width:100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,6 +92,10 @@
|
||||
function fmt(n){ return '$' + Number(n||0).toFixed(2); }
|
||||
const initialSummaryData = {
|
||||
schoolYear: <?= json_encode((string)($schoolYear ?? '')) ?>,
|
||||
dateFrom: <?= json_encode((string)($dateFrom ?? '')) ?>,
|
||||
dateTo: <?= json_encode((string)($dateTo ?? '')) ?>,
|
||||
grossCharges: <?= json_encode((float)($grossCharges ?? $totalCharges ?? 0)) ?>,
|
||||
tuitionCharges: <?= json_encode((float)($tuitionCharges ?? 0)) ?>,
|
||||
totalCharges: <?= json_encode((float)($totalCharges ?? 0)) ?>,
|
||||
totalEventFees: <?= json_encode((float)($totalEventFees ?? 0)) ?>,
|
||||
totalExtraCharges: <?= json_encode((float)($totalExtraCharges ?? 0)) ?>,
|
||||
@@ -90,18 +107,47 @@ const initialSummaryData = {
|
||||
totalPaid: <?= json_encode((float)($totalPaid ?? 0)) ?>,
|
||||
amountCollected: <?= json_encode((float)($amountCollected ?? 0)) ?>,
|
||||
totalUnpaid: <?= json_encode((float)($totalUnpaid ?? 0)) ?>,
|
||||
totalOverpaid: <?= json_encode((float)($totalOverpaid ?? 0)) ?>,
|
||||
overpaymentDetails: <?= json_encode($overpaymentDetails ?? []) ?>,
|
||||
netReceivable: <?= json_encode((float)($netReceivable ?? 0)) ?>,
|
||||
netAmount: <?= json_encode((float)($netAmount ?? 0)) ?>
|
||||
};
|
||||
|
||||
function buildSummaryPeriod(d) {
|
||||
const parts = ['Report for School Year: ' + (d.schoolYear || '')];
|
||||
if (d.dateFrom) parts.push('Date From: ' + d.dateFrom);
|
||||
if (d.dateTo) parts.push('Date To: ' + d.dateTo);
|
||||
return parts.join(' | ');
|
||||
}
|
||||
|
||||
function buildReconciliationText(d) {
|
||||
const collected = Number(d.amountCollected || d.totalPaid || 0);
|
||||
const unpaid = Number(d.totalUnpaid || 0);
|
||||
const overpaid = Number(d.totalOverpaid || 0);
|
||||
const net = Number(d.netAmount || 0);
|
||||
return fmt(collected) + ' + ' + fmt(unpaid) + ' - ' + fmt(overpaid) + ' = ' + fmt(net)
|
||||
+ ' (Collected + Gross Outstanding - Overpayment Credits = Net Charges After Discounts/Refunds)';
|
||||
}
|
||||
|
||||
function buildNetFormulaText(d) {
|
||||
const gross = Number(d.grossCharges || d.totalCharges || 0);
|
||||
const discounts = Number(d.totalDiscounts || 0);
|
||||
const refunds = Number(d.totalRefunds || 0);
|
||||
const net = Number(d.netAmount || 0);
|
||||
return 'Net Charges After Discounts/Refunds = Gross Charges - Total Discounts - Total Refunds = '
|
||||
+ fmt(gross) + ' - ' + fmt(discounts) + ' - ' + fmt(refunds) + ' = ' + fmt(net);
|
||||
}
|
||||
|
||||
function applySummaryData(d) {
|
||||
document.getElementById('summaryPeriod').textContent = 'Report for School Year: ' + (d.schoolYear || '');
|
||||
document.getElementById('sumCharges').textContent = fmt(d.totalCharges);
|
||||
document.getElementById('summaryPeriod').textContent = buildSummaryPeriod(d);
|
||||
document.getElementById('sumTuitionCharges').textContent = fmt(d.tuitionCharges || 0);
|
||||
if (document.getElementById('sumEventFees')) {
|
||||
document.getElementById('sumEventFees').textContent = fmt(d.totalEventFees || 0);
|
||||
}
|
||||
if (document.getElementById('sumExtraCharges')) {
|
||||
document.getElementById('sumExtraCharges').textContent = fmt(d.totalExtraCharges || 0);
|
||||
}
|
||||
document.getElementById('sumGrossCharges').textContent = fmt(d.grossCharges || d.totalCharges || 0);
|
||||
document.getElementById('sumDiscounts').textContent = fmt(d.totalDiscounts);
|
||||
document.getElementById('sumRefunds').textContent = fmt(d.totalRefunds);
|
||||
document.getElementById('sumExpenses').textContent = fmt(d.totalExpenses);
|
||||
@@ -111,14 +157,43 @@ function applySummaryData(d) {
|
||||
}
|
||||
document.getElementById('sumNet').textContent = fmt(d.netAmount);
|
||||
document.getElementById('sumCollected').textContent = fmt(d.amountCollected || d.totalPaid || 0);
|
||||
document.getElementById('sumOverpaid').textContent = fmt(d.totalOverpaid || 0);
|
||||
document.getElementById('sumUnpaid').textContent = fmt(d.totalUnpaid);
|
||||
document.getElementById('sumNetReceivable').textContent = fmt(d.netReceivable || 0);
|
||||
document.getElementById('summaryNetFormula').textContent = buildNetFormulaText(d);
|
||||
document.getElementById('summaryReconciliationText').textContent = buildReconciliationText(d);
|
||||
}
|
||||
|
||||
function buildSummaryParams() {
|
||||
const params = new URLSearchParams();
|
||||
const form = document.getElementById('summaryYearFilter');
|
||||
if (form) {
|
||||
new FormData(form).forEach((value, key) => {
|
||||
params.set(key, value);
|
||||
});
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function updateSummaryActionLinks() {
|
||||
const params = buildSummaryParams().toString();
|
||||
const summaryHref = '<?= base_url('financial-report/downloadSummaryCsv') ?>' + (params ? ('?' + params) : '');
|
||||
const allDetailsHref = '<?= base_url('financial-report/downloadSummaryAllDetailsCsv') ?>' + (params ? ('?' + params) : '');
|
||||
const summaryLink = document.getElementById('downloadSummaryCsvLink');
|
||||
const allDetailsLink = document.getElementById('downloadSummaryAllDetailsCsvLink');
|
||||
if (summaryLink) summaryLink.href = summaryHref;
|
||||
if (allDetailsLink) allDetailsLink.href = allDetailsHref;
|
||||
}
|
||||
|
||||
// Auto-submit school year on change and hydrate via API
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sel = document.querySelector('#summaryYearFilter select[name="school_year"]');
|
||||
if (sel) sel.addEventListener('change', function(){ loadSummary(); });
|
||||
if (sel) sel.addEventListener('change', function(){
|
||||
updateSummaryActionLinks();
|
||||
loadSummary();
|
||||
});
|
||||
applySummaryData(initialSummaryData);
|
||||
updateSummaryActionLinks();
|
||||
renderCharts(initialSummaryData);
|
||||
loadSummary();
|
||||
});
|
||||
@@ -126,9 +201,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let __sumSeq = 0;
|
||||
function loadSummary(){
|
||||
const mySeq = ++__sumSeq;
|
||||
const sel = document.querySelector('#summaryYearFilter select[name="school_year"]');
|
||||
const params = new URLSearchParams();
|
||||
if (sel && sel.value) params.append('school_year', sel.value);
|
||||
const params = buildSummaryParams();
|
||||
const baseUrl = '<?= site_url('financial-report/financialReportSummary') ?>';
|
||||
const url = baseUrl + (params.toString() ? ('?' + params.toString() + '&format=json') : '?format=json');
|
||||
fetch(url, { headers: { 'Accept': 'application/json' }})
|
||||
@@ -148,13 +221,39 @@ function renderCharts(d){
|
||||
if (window._summaryChart) { window._summaryChart.destroy(); }
|
||||
if (window._collectedChart) { window._collectedChart.destroy(); }
|
||||
if (window._netExpenseChart) { window._netExpenseChart.destroy(); }
|
||||
const chartColors = {
|
||||
tuition: '#1f77b4',
|
||||
eventFees: '#ff7f0e',
|
||||
carryover: '#2ca02c',
|
||||
grossCharges: '#d62728',
|
||||
discounts: '#9467bd',
|
||||
expenses: '#20c997',
|
||||
netCharges: '#e377c2',
|
||||
collected: '#28a745',
|
||||
overpaymentCredits: '#dc3545',
|
||||
outstanding: '#ffc107',
|
||||
netReceivable: '#f4a261'
|
||||
};
|
||||
const summaryCtx = document.getElementById('summaryChart').getContext('2d');
|
||||
const summaryBarColors = [
|
||||
chartColors.tuition,
|
||||
chartColors.eventFees,
|
||||
chartColors.carryover,
|
||||
chartColors.grossCharges,
|
||||
chartColors.discounts,
|
||||
chartColors.expenses,
|
||||
chartColors.netCharges,
|
||||
chartColors.collected,
|
||||
chartColors.overpaymentCredits,
|
||||
chartColors.outstanding,
|
||||
chartColors.netReceivable
|
||||
];
|
||||
window._summaryChart = new Chart(summaryCtx, {
|
||||
type: 'bar',
|
||||
data: { labels: ['Charges','Event Fees','Paid','Unpaid','Discounts','Refunds','Expenses','Reimbursements','Net'],
|
||||
data: { labels: ['Tuition','Event Fees','Prior-Year Carryover','Gross Charges','Discounts','Expenses','Net Charges','Collected','Overpayment Credits','Outstanding','Net Receivable'],
|
||||
datasets: [{ label:'Amount (USD)', data:[
|
||||
d.totalCharges||0, d.totalEventFees||0, d.totalPaid||0, d.totalUnpaid||0, d.totalDiscounts||0, d.totalRefunds||0, d.totalExpenses||0, d.totalReimbursements||0, d.netAmount||0
|
||||
], backgroundColor: ['#007bff','#6610f2','#28a745','#ffc107','#17a2b8','#ffc107','#dc3545','#6f42c1','#20c997']}] },
|
||||
d.tuitionCharges||0, d.totalEventFees||0, d.totalExtraCharges||0, d.grossCharges||d.totalCharges||0, d.totalDiscounts||0, d.totalExpenses||0, d.netAmount||0, d.amountCollected||d.totalPaid||0, d.totalOverpaid||0, d.totalUnpaid||0, d.netReceivable||0
|
||||
], backgroundColor: summaryBarColors}] },
|
||||
options: { responsive:true, scales:{ y:{ beginAtZero:true }}}
|
||||
});
|
||||
|
||||
@@ -162,10 +261,10 @@ function renderCharts(d){
|
||||
window._collectedChart = new Chart(collectedCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Amount Collected (Paid)', 'Amount Outstanding (Unpaid)'],
|
||||
labels: ['Amount Collected (Paid)', 'Amount Unpaid (Net of Credits)', 'Overpayment Credits'],
|
||||
datasets: [{
|
||||
data: [ d.amountCollected || d.totalPaid || 0, d.totalUnpaid || 0 ],
|
||||
backgroundColor: ['#28a745', '#ffc107'],
|
||||
data: [ d.amountCollected || d.totalPaid || 0, d.netReceivable || 0, d.totalOverpaid || 0 ],
|
||||
backgroundColor: [chartColors.collected, chartColors.outstanding, chartColors.overpaymentCredits],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
@@ -190,7 +289,7 @@ function renderCharts(d){
|
||||
labels: netExpenseLabels,
|
||||
datasets: [{
|
||||
data: netExpenseValues,
|
||||
backgroundColor: ['#20c997', '#dc3545'],
|
||||
backgroundColor: [chartColors.expenses, chartColors.netCharges],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
@@ -202,7 +301,7 @@ function renderCharts(d){
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
if (context.dataIndex === 0) {
|
||||
return 'Expenses: ' + fmt(totalExpenses) + ' (' + expensePercent + '% of net amount)';
|
||||
return 'Expenses: ' + fmt(totalExpenses) + ' (' + expensePercent + '% of net charges)';
|
||||
}
|
||||
return 'Remaining Net: ' + fmt(remainingNet) + ' (' + remainingPercent + '%)';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user