fix financial issues
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<div class="container-fluid">
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<h2 class="text-center mt-4 mb-3">PayPal Transactions</h2>
|
||||
<?= $this->include('partials/academic_filter') ?>
|
||||
<!-- Export Button -->
|
||||
<div class="d-flex gap-2 mb-3 justify-content-end">
|
||||
<a href="<?= base_url('admin/paypal-transactions/export') . ($keyword ? '?q=' . urlencode($keyword) : '') ?>"
|
||||
class="btn btn-success">
|
||||
Export CSV
|
||||
</a>
|
||||
</div>
|
||||
<!-- Search Form -->
|
||||
<!--
|
||||
<form method="get" action="<?= base_url('admin/paypal-transactions') ?>" class="mb-3">
|
||||
<?= csrf_field(); ?>
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" value="<?= esc($keyword) ?>" class="form-control"
|
||||
placeholder="Search by transaction ID, email, order ID, or event type">
|
||||
<button type="submit" class="btn btn-primary">Search</button>
|
||||
<?php if ($keyword): ?>
|
||||
<a href="<?= base_url('admin/paypal-transactions') ?>" class="btn btn-secondary">Clear</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
-->
|
||||
|
||||
<!-- Transactions Table -->
|
||||
<table id="myTable" class="display table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Transaction ID</th>
|
||||
<th>Order ID</th>
|
||||
<th>Parent School ID</th>
|
||||
<th>Email</th>
|
||||
<th>Amount</th>
|
||||
<th>Net Amount</th>
|
||||
<th>Currency</th>
|
||||
<th>Status</th>
|
||||
<th>Event Type</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transactions as $t): ?>
|
||||
<tr>
|
||||
<td><?= esc($t['id']) ?></td>
|
||||
<td><?= esc($t['transaction_id']) ?></td>
|
||||
<td><?= esc($t['order_id']) ?></td>
|
||||
<td><?= esc($t['parent_school_id']) ?></td>
|
||||
<td><?= esc($t['payer_email']) ?></td>
|
||||
<td>$<?= esc(number_format($t['amount'], 2)) ?></td>
|
||||
<td>$<?= esc(number_format($t['net_amount'], 2)) ?></td>
|
||||
<td><?= esc($t['currency']) ?></td>
|
||||
<td><?= esc($t['status']) ?></td>
|
||||
<td><?= esc($t['event_type']) ?></td>
|
||||
<td><?= esc(local_datetime($t['created_at'], 'm-d-Y H:i')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Pagination -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<?= $pager->links() ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#myTable').DataTable({
|
||||
"order": [
|
||||
[0, "desc"]
|
||||
],
|
||||
"pageLength": 10
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -15,23 +15,65 @@ $totalSurprise = array_sum(array_column($classResults, 'surprises'));
|
||||
$totalMissed = array_sum(array_column($classResults, 'missed'));
|
||||
$overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted * 100) : ($totalActual === 0 ? 100 : 0);
|
||||
|
||||
$pct = static function (int $count, int $total): string {
|
||||
return $total > 0 ? number_format(($count / $total) * 100, 1) . '%' : '0.0%';
|
||||
};
|
||||
|
||||
$genderKey = static function (?string $gender): string {
|
||||
$value = strtolower(trim((string)$gender));
|
||||
|
||||
return match (true) {
|
||||
in_array($value, ['male', 'm', 'boy', 'boys'], true) => 'boys',
|
||||
in_array($value, ['female', 'f', 'girl', 'girls'], true) => 'girls',
|
||||
default => 'other',
|
||||
};
|
||||
};
|
||||
|
||||
$genderStats = static function (array $items) use ($genderKey): array {
|
||||
$stats = ['boys' => 0, 'girls' => 0, 'other' => 0];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$stats[$genderKey($item['gender'] ?? '')]++;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
};
|
||||
|
||||
// Winner gender breakdown.
|
||||
// "Winner" means actual year-end trophy winner.
|
||||
$totalWinnerBoys = 0;
|
||||
$totalWinnerGirls = 0;
|
||||
$totalWinnerOther = 0;
|
||||
$allStudentsFlat = [];
|
||||
$passStudents = [];
|
||||
$trophyStudents = [];
|
||||
|
||||
// Sticker names.
|
||||
// 2 columns x 10 rows = 20 stickers per page.
|
||||
// Stickers print NAME ONLY.
|
||||
$winnerStickerNames = [];
|
||||
// 2 columns x 7 rows = 14 stickers per page.
|
||||
// Stickers print name and class section.
|
||||
$stickerColumns = 2;
|
||||
$stickerRows = 7;
|
||||
$stickerWidthInches = 4.0;
|
||||
$stickerHeightInches = 1.33;
|
||||
$stickerPrintablePageWidthInches = 8.0;
|
||||
$stickerPrintablePageHeightInches = 10.5;
|
||||
$stickersPerPage = $stickerColumns * $stickerRows;
|
||||
$winnerStickers = [];
|
||||
|
||||
foreach ($classResults as $cls) {
|
||||
foreach (($cls['students'] ?? []) as $s) {
|
||||
$allStudentsFlat[] = $s;
|
||||
|
||||
if (isset($s['year_score']) && is_numeric($s['year_score']) && (float)$s['year_score'] >= 60) {
|
||||
$passStudents[] = $s;
|
||||
}
|
||||
|
||||
if (empty($s['actual'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$trophyStudents[] = $s;
|
||||
|
||||
$gender = strtolower(trim((string)($s['gender'] ?? '')));
|
||||
|
||||
if (in_array($gender, ['male', 'm', 'boy', 'boys'], true)) {
|
||||
@@ -43,9 +85,13 @@ foreach ($classResults as $cls) {
|
||||
}
|
||||
|
||||
$name = trim((string)($s['name'] ?? ''));
|
||||
$sectionName = trim((string)($cls['section_name'] ?? ''));
|
||||
|
||||
if ($name !== '') {
|
||||
$winnerStickerNames[] = $name;
|
||||
$winnerStickers[] = [
|
||||
'name' => $name,
|
||||
'section' => $sectionName,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +101,10 @@ $totalWinners = $totalWinnerBoys + $totalWinnerGirls + $totalWinnerOther;
|
||||
$winnerBoysPct = $totalWinners > 0 ? round(($totalWinnerBoys / $totalWinners) * 100, 1) : 0;
|
||||
$winnerGirlsPct = $totalWinners > 0 ? round(($totalWinnerGirls / $totalWinners) * 100, 1) : 0;
|
||||
$winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners) * 100, 1) : 0;
|
||||
$allGenderStats = $genderStats($allStudentsFlat);
|
||||
$passGenderStats = $genderStats($passStudents);
|
||||
$trophyGenderStats = $genderStats($trophyStudents);
|
||||
$totalPass = count($passStudents);
|
||||
?>
|
||||
|
||||
<style>
|
||||
@@ -68,7 +118,7 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
|
||||
@page {
|
||||
size: Letter portrait;
|
||||
margin: 0.35in;
|
||||
margin: 0.25in;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@@ -162,11 +212,18 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
|
||||
body.print-stickers-mode .sticker-page {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(10, 1fr);
|
||||
grid-template-columns: repeat(<?= $stickerColumns ?>, <?= rtrim(rtrim(number_format($stickerWidthInches, 2, '.', ''), '0'), '.') ?>in);
|
||||
grid-template-rows: repeat(<?= $stickerRows ?>, <?= rtrim(rtrim(number_format($stickerHeightInches, 2, '.', ''), '0'), '.') ?>in);
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
height: 10.3in;
|
||||
width: <?= rtrim(rtrim(number_format($stickerPrintablePageWidthInches, 2, '.', ''), '0'), '.') ?>in;
|
||||
height: <?= rtrim(rtrim(number_format($stickerPrintablePageHeightInches, 2, '.', ''), '0'), '.') ?>in;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
align-content: start;
|
||||
justify-content: center;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid-page;
|
||||
page-break-after: always;
|
||||
break-after: page;
|
||||
}
|
||||
@@ -186,16 +243,36 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
width: <?= rtrim(rtrim(number_format($stickerWidthInches, 2, '.', ''), '0'), '.') ?>in;
|
||||
height: <?= rtrim(rtrim(number_format($stickerHeightInches, 2, '.', ''), '0'), '.') ?>in;
|
||||
}
|
||||
|
||||
body.print-stickers-mode .sticker-name {
|
||||
display: block !important;
|
||||
font-size: 20pt;
|
||||
font-size: 18pt;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.print-stickers-mode .sticker-content {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
transform: translateY(0.22in);
|
||||
}
|
||||
|
||||
body.print-stickers-mode .sticker-section {
|
||||
display: block !important;
|
||||
margin-top: 0.08in;
|
||||
font-size: 10pt;
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.print-stickers-mode .sticker-empty {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
@@ -219,7 +296,7 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button onclick="printWithCharts()" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-printer-fill me-1"></i>Print
|
||||
<i class="bi bi-printer-fill me-1"></i>Print Stats
|
||||
</button>
|
||||
|
||||
<button onclick="printWinnerStickers()" class="btn btn-warning btn-sm">
|
||||
@@ -548,13 +625,6 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-3">
|
||||
<div class="border rounded p-3 text-center h-100">
|
||||
<div class="text-muted small mb-1">Unknown / Other</div>
|
||||
<div class="display-6 fw-bold text-secondary"><?= (int)$totalWinnerOther ?></div>
|
||||
<div class="fw-semibold"><?= number_format($winnerOtherPct, 1) ?>%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
@@ -577,17 +647,6 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
aria-valuemax="100">
|
||||
Girls <?= number_format($winnerGirlsPct, 1) ?>%
|
||||
</div>
|
||||
|
||||
<?php if ($totalWinnerOther > 0): ?>
|
||||
<div class="progress-bar bg-secondary"
|
||||
role="progressbar"
|
||||
style="width: <?= $winnerOtherPct ?>%;"
|
||||
aria-valuenow="<?= $winnerOtherPct ?>"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
Other <?= number_format($winnerOtherPct, 1) ?>%
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<div class="progress-bar bg-secondary"
|
||||
role="progressbar"
|
||||
@@ -619,6 +678,50 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-bottom:6px;">Print Stats</h3>
|
||||
<table data-no-mgmt-sticky style="margin-bottom:12px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th style="text-align:center;">Count</th>
|
||||
<th style="text-align:center;">Percent</th>
|
||||
<th style="text-align:center;">Boys</th>
|
||||
<th style="text-align:center;">Boys %</th>
|
||||
<th style="text-align:center;">Girls</th>
|
||||
<th style="text-align:center;">Girls %</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Students</td>
|
||||
<td style="text-align:center;"><?= $totalStudents ?></td>
|
||||
<td style="text-align:center;">100.0%</td>
|
||||
<td style="text-align:center;"><?= $allGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($allGenderStats['boys'], $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $allGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($allGenderStats['girls'], $totalStudents) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pass</td>
|
||||
<td style="text-align:center;"><?= $totalPass ?></td>
|
||||
<td style="text-align:center;"><?= $pct($totalPass, $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $passGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($passGenderStats['boys'], $totalPass) ?></td>
|
||||
<td style="text-align:center;"><?= $passGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($passGenderStats['girls'], $totalPass) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trophies</td>
|
||||
<td style="text-align:center;"><?= $totalWinners ?></td>
|
||||
<td style="text-align:center;"><?= $pct($totalWinners, $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $trophyGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($trophyGenderStats['boys'], $totalWinners) ?></td>
|
||||
<td style="text-align:center;"><?= $trophyGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($trophyGenderStats['girls'], $totalWinners) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 style="margin-bottom:4px;">Student Detail</h3>
|
||||
<table data-no-mgmt-sticky>
|
||||
<thead>
|
||||
@@ -638,11 +741,15 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
|
||||
<tbody>
|
||||
<?php
|
||||
$rank = 0;
|
||||
foreach ($classResults as $cls):
|
||||
foreach ($cls['students'] as $s):
|
||||
if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue;
|
||||
$rank++;
|
||||
$classPrintRows = array_values(array_filter(
|
||||
$cls['students'],
|
||||
static fn (array $student): bool => in_array($student['status'], ['confirmed', 'surprise'], true)
|
||||
));
|
||||
$classPrintRows = array_reverse($classPrintRows);
|
||||
|
||||
foreach ($classPrintRows as $idx => $s):
|
||||
$rank = count($classPrintRows) - $idx;
|
||||
$genderNorm = strtolower(trim((string)($s['gender'] ?? '')));
|
||||
$isMale = in_array($genderNorm, ['male', 'm', 'boy'], true);
|
||||
$statusLabel = match ($s['status']) {
|
||||
@@ -761,13 +868,6 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
<td style="text-align:center;"><?= (int)$totalWinnerGirls ?></td>
|
||||
<td style="text-align:center;"><?= number_format($winnerGirlsPct, 1) ?>%</td>
|
||||
</tr>
|
||||
<?php if ($totalWinnerOther > 0): ?>
|
||||
<tr>
|
||||
<td>Unknown / Other</td>
|
||||
<td style="text-align:center;"><?= (int)$totalWinnerOther ?></td>
|
||||
<td style="text-align:center;"><?= number_format($winnerOtherPct, 1) ?>%</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
@@ -784,16 +884,19 @@ $winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners)
|
||||
|
||||
<!-- Winner sticker print area: names only, no header, no score, no class -->
|
||||
<div id="winnerStickerPrintArea" class="winner-sticker-print-area">
|
||||
<?php if (!empty($winnerStickerNames)): ?>
|
||||
<?php foreach (array_chunk($winnerStickerNames, 20) as $chunk): ?>
|
||||
<?php if (!empty($winnerStickers)): ?>
|
||||
<?php foreach (array_chunk($winnerStickers, $stickersPerPage) as $chunk): ?>
|
||||
<div class="sticker-page">
|
||||
<?php foreach ($chunk as $winnerName): ?>
|
||||
<?php foreach ($chunk as $winnerSticker): ?>
|
||||
<div class="sticker-cell">
|
||||
<div class="sticker-name"><?= esc($winnerName) ?></div>
|
||||
<div class="sticker-content">
|
||||
<div class="sticker-name"><?= esc($winnerSticker['name']) ?></div>
|
||||
<div class="sticker-section"><?= esc($winnerSticker['section']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php for ($i = 0, $remaining = 20 - count($chunk); $i < $remaining; $i++): ?>
|
||||
<?php for ($i = 0, $remaining = $stickersPerPage - count($chunk); $i < $remaining; $i++): ?>
|
||||
<div class="sticker-cell sticker-empty"></div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
@@ -971,4 +1074,4 @@ window.addEventListener('beforeprint', function () {
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<?php
|
||||
$filters = $filters ?? [];
|
||||
$result = $result ?? null;
|
||||
$summary = $result['summary'] ?? [];
|
||||
$mode = $filters['calculator_mode'] ?? 'compare';
|
||||
$fmtMoney = static fn($value): string => '$' . number_format((float) $value, 2);
|
||||
$warningText = static function (array $warnings): string {
|
||||
$warnings = array_values(array_filter(array_map('trim', $warnings), 'strlen'));
|
||||
return $warnings === [] ? 'None' : implode(' | ', $warnings);
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-4">
|
||||
<div>
|
||||
<h2 class="mt-4 mb-1">Tuition Collection Forecast</h2>
|
||||
<p class="text-muted mb-0">Uses actual enrollment families from the selected school year to compare old and new projected tuition income.</p>
|
||||
</div>
|
||||
<?php if ($result): ?>
|
||||
<?php
|
||||
$exportQuery = http_build_query([
|
||||
'school_year' => $result['school_year'] ?? '',
|
||||
'semester' => $result['semester'] ?? '',
|
||||
'calculator_mode' => $result['calculator_mode'] ?? 'compare',
|
||||
'include_withdrawn_mode' => $filters['include_withdrawn_mode'] ?? 'refund_deadline',
|
||||
'include_payment_pending' => !empty($filters['include_payment_pending']) ? '1' : '0',
|
||||
'include_event_only' => !empty($filters['include_event_only']) ? '1' : '0',
|
||||
'include_paid_invoices' => !empty($filters['include_paid_invoices']) ? '1' : '0',
|
||||
'unit_price' => $filters['unit_price'] ?? '',
|
||||
]);
|
||||
?>
|
||||
<a href="<?= site_url('administrator/tuition-forecast/export?' . $exportQuery) ?>" class="btn btn-success">
|
||||
Export CSV
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" action="<?= site_url('administrator/tuition-forecast') ?>" class="row g-3 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label for="school_year" class="form-label">School Year</label>
|
||||
<select id="school_year" name="school_year" class="form-select">
|
||||
<?php foreach (($schoolYears ?? []) as $schoolYear): ?>
|
||||
<option value="<?= esc($schoolYear) ?>" <?= ($filters['school_year'] ?? '') === $schoolYear ? 'selected' : '' ?>>
|
||||
<?= esc($schoolYear) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="semester" class="form-label">Semester Filter</label>
|
||||
<select id="semester" name="semester" class="form-select">
|
||||
<option value="" <?= ($filters['semester'] ?? '') === '' ? 'selected' : '' ?>>All Year</option>
|
||||
<?php foreach (($semesters ?? []) as $semester): ?>
|
||||
<option value="<?= esc($semester) ?>" <?= ($filters['semester'] ?? '') === $semester ? 'selected' : '' ?>>
|
||||
<?= esc($semester) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="calculator_mode" class="form-label">Calculator Mode</label>
|
||||
<select id="calculator_mode" name="calculator_mode" class="form-select">
|
||||
<option value="compare" <?= $mode === 'compare' ? 'selected' : '' ?>>Compare Both</option>
|
||||
<option value="old" <?= $mode === 'old' ? 'selected' : '' ?>>Old Only</option>
|
||||
<option value="new" <?= $mode === 'new' ? 'selected' : '' ?>>New Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="include_withdrawn_mode" class="form-label">Withdrawn Students</label>
|
||||
<select id="include_withdrawn_mode" name="include_withdrawn_mode" class="form-select">
|
||||
<option value="refund_deadline" <?= ($filters['include_withdrawn_mode'] ?? 'refund_deadline') === 'refund_deadline' ? 'selected' : '' ?>>Use Refund Deadline Rule</option>
|
||||
<option value="include" <?= ($filters['include_withdrawn_mode'] ?? '') === 'include' ? 'selected' : '' ?>>Always Include</option>
|
||||
<option value="exclude" <?= ($filters['include_withdrawn_mode'] ?? '') === 'exclude' ? 'selected' : '' ?>>Always Exclude</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="unit_price" class="form-label">Unit Price</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
id="unit_price"
|
||||
name="unit_price"
|
||||
class="form-control"
|
||||
value="<?= esc((string) ($filters['unit_price'] ?? ($summary['unit_price'] ?? ''))) ?>"
|
||||
placeholder="New tuition unit price">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include_payment_pending" name="include_payment_pending" value="1" <?= !empty($filters['include_payment_pending']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="include_payment_pending">Include payment-pending students</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include_event_only" name="include_event_only" value="1" <?= !empty($filters['include_event_only']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="include_event_only">Show event-only students in warnings</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Run Forecast</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($result): ?>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Total Parents</div>
|
||||
<div class="fs-4 fw-semibold"><?= esc((string) ($summary['family_count'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Total Students</div>
|
||||
<div class="fs-4 fw-semibold"><?= esc((string) ($summary['student_count'] ?? 0)) ?></div>
|
||||
<div class="small text-muted">Billable: <?= esc((string) ($summary['billable_student_count'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Old Projected Income</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['old_projected_income'] ?? ($summary['old_projected_tuition'] ?? 0))) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">New Projected Income</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['new_projected_income'] ?? ($summary['new_projected_tuition'] ?? 0))) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Unit Price</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['unit_price'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Difference</div>
|
||||
<div class="fs-5 fw-semibold <?= ((float) ($summary['difference'] ?? 0)) >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
<?= esc($fmtMoney($summary['difference'] ?? 0)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Parent / Family</th>
|
||||
<th>Student Count</th>
|
||||
<th>Billable Student Count</th>
|
||||
<th>Old Tuition Total</th>
|
||||
<th>New Tuition Total</th>
|
||||
<th>Difference</th>
|
||||
<th>Warnings</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($result['families'])): ?>
|
||||
<?php foreach ($result['families'] as $family): ?>
|
||||
<tr>
|
||||
<td><?= esc($family['parent_name'] ?? '') ?></td>
|
||||
<td><?= esc((string) ($family['student_count'] ?? 0)) ?></td>
|
||||
<td><?= esc((string) ($family['billable_student_count'] ?? 0)) ?></td>
|
||||
<td><?= esc($fmtMoney($family['old_total'] ?? 0)) ?></td>
|
||||
<td><?= esc($fmtMoney($family['new_total'] ?? 0)) ?></td>
|
||||
<td class="<?= ((float) ($family['difference'] ?? 0)) >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
<?= esc($fmtMoney($family['difference'] ?? 0)) ?>
|
||||
</td>
|
||||
<td class="small"><?= esc($warningText($family['warnings'] ?? [])) ?></td>
|
||||
<td>
|
||||
<details>
|
||||
<summary>Students</summary>
|
||||
<div class="table-responsive mt-2">
|
||||
<table class="table table-sm table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<th>Grade</th>
|
||||
<th>Billable</th>
|
||||
<th>Excluded Reason</th>
|
||||
<th>Old Rule</th>
|
||||
<th>Old Amount</th>
|
||||
<th>New Rule</th>
|
||||
<th>New Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($family['student_details'] ?? []) as $detail): ?>
|
||||
<tr>
|
||||
<td><?= esc($detail['student_name'] ?? '') ?></td>
|
||||
<td><?= esc($detail['grade_level'] ?? '') ?></td>
|
||||
<td><?= !empty($detail['billable']) ? 'Yes' : 'No' ?></td>
|
||||
<td><?= esc((string) ($detail['excluded_reason'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($detail['old_rule'] ?? '')) ?></td>
|
||||
<td><?= esc($fmtMoney($detail['old_amount'] ?? 0)) ?></td>
|
||||
<td><?= esc((string) ($detail['new_rule'] ?? '')) ?></td>
|
||||
<td><?= esc($fmtMoney($detail['new_amount'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted">No forecastable families matched the selected filters.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
@@ -6,7 +6,7 @@
|
||||
<h2 class="text-center mt-4 mb-4">Student Decisions — All Students</h2>
|
||||
|
||||
<!-- School Year filter only -->
|
||||
<form method="get" class="row g-2 align-items-center justify-content-center mb-3">
|
||||
<form method="get" class="row g-2 align-items-center justify-content-center mb-3 no-print">
|
||||
<div class="col-auto"><label class="form-label mb-0">School year</label></div>
|
||||
<div class="col-auto">
|
||||
<select name="school_year" class="form-select form-select-sm" style="min-width: 180px;">
|
||||
@@ -35,7 +35,10 @@
|
||||
<span class="badge bg-warning text-dark ms-2">Not yet generated</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<div class="d-flex gap-2 flex-wrap no-print">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">
|
||||
Print Stats
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('grading') ?>">
|
||||
Grading
|
||||
</a>
|
||||
@@ -56,7 +59,7 @@
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Generate / Regenerate button -->
|
||||
<form method="post" action="<?= site_url('grading/decisions/generate') ?>" class="mb-3">
|
||||
<form method="post" action="<?= site_url('grading/decisions/generate') ?>" class="mb-3 no-print">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear ?? '') ?>">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@@ -93,10 +96,42 @@
|
||||
elseif ($r['decision'] === '' || $r['source'] === 'pending') $stats['Pending']++;
|
||||
else $stats['Other']++;
|
||||
}
|
||||
|
||||
$pct = static function (int $count, int $total): string {
|
||||
return $total > 0 ? number_format(($count / $total) * 100, 1) . '%' : '0.0%';
|
||||
};
|
||||
|
||||
$genderKey = static function (?string $gender): string {
|
||||
$value = strtolower(trim((string)$gender));
|
||||
|
||||
return match (true) {
|
||||
in_array($value, ['male', 'm', 'boy', 'boys'], true) => 'boys',
|
||||
in_array($value, ['female', 'f', 'girl', 'girls'], true) => 'girls',
|
||||
default => 'other',
|
||||
};
|
||||
};
|
||||
|
||||
$genderStats = static function (array $items) use ($genderKey): array {
|
||||
$stats = ['boys' => 0, 'girls' => 0, 'other' => 0];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$stats[$genderKey($item['gender'] ?? '')]++;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
};
|
||||
|
||||
$totalStudents = count($rows);
|
||||
$passRows = array_values(array_filter($rows, static fn (array $row): bool => (string)($row['decision'] ?? '') === 'Pass'));
|
||||
$trophyRows = array_values(array_filter($rows, static fn (array $row): bool => !empty($row['is_trophy'])));
|
||||
|
||||
$allGenderStats = $genderStats($rows);
|
||||
$passGenderStats = $genderStats($passRows);
|
||||
$trophyGenderStats = $genderStats($trophyRows);
|
||||
?>
|
||||
|
||||
<!-- Summary cards -->
|
||||
<div class="d-flex gap-3 mb-3 flex-wrap">
|
||||
<div class="d-flex gap-3 mb-3 flex-wrap no-print">
|
||||
<div class="card text-center px-4 py-2 border-success">
|
||||
<div class="fs-4 fw-bold text-success"><?= $stats['Pass'] ?></div>
|
||||
<div class="text-muted small">Pass</div>
|
||||
@@ -115,7 +150,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<div class="print-only" style="margin-bottom:14px;">
|
||||
<div style="text-align:center;margin-bottom:10px;border-bottom:2px solid #333;padding-bottom:6px;">
|
||||
<h2 style="margin:0;">Student Decisions Summary — <?= esc($schoolYear ?? '') ?></h2>
|
||||
<p style="margin:3px 0 0;font-size:10px;color:#555;">
|
||||
Trophy counts use the same year-score rule as the trophy final page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-bottom:6px;">Print Stats</h3>
|
||||
<table data-no-mgmt-sticky style="margin-bottom:12px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th style="text-align:center;">Count</th>
|
||||
<th style="text-align:center;">Percent</th>
|
||||
<th style="text-align:center;">Boys</th>
|
||||
<th style="text-align:center;">Boys %</th>
|
||||
<th style="text-align:center;">Girls</th>
|
||||
<th style="text-align:center;">Girls %</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Students</td>
|
||||
<td style="text-align:center;"><?= $totalStudents ?></td>
|
||||
<td style="text-align:center;">100.0%</td>
|
||||
<td style="text-align:center;"><?= $allGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($allGenderStats['boys'], $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $allGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($allGenderStats['girls'], $totalStudents) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pass</td>
|
||||
<td style="text-align:center;"><?= count($passRows) ?></td>
|
||||
<td style="text-align:center;"><?= $pct(count($passRows), $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $passGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($passGenderStats['boys'], count($passRows)) ?></td>
|
||||
<td style="text-align:center;"><?= $passGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($passGenderStats['girls'], count($passRows)) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trophies</td>
|
||||
<td style="text-align:center;"><?= count($trophyRows) ?></td>
|
||||
<td style="text-align:center;"><?= $pct(count($trophyRows), $totalStudents) ?></td>
|
||||
<td style="text-align:center;"><?= $trophyGenderStats['boys'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($trophyGenderStats['boys'], count($trophyRows)) ?></td>
|
||||
<td style="text-align:center;"><?= $trophyGenderStats['girls'] ?></td>
|
||||
<td style="text-align:center;"><?= $pct($trophyGenderStats['girls'], count($trophyRows)) ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive no-print">
|
||||
<table class="table table-bordered table-striped align-middle w-100 all-decisions-dt"
|
||||
data-no-mgmt-sticky data-no-dt-fixedheader>
|
||||
<thead class="table-light">
|
||||
@@ -174,7 +262,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="mt-2 mb-4 d-flex gap-2 flex-wrap align-items-center">
|
||||
<div class="mt-2 mb-4 d-flex gap-2 flex-wrap align-items-center no-print">
|
||||
<?php foreach ($decisionBadge as $label => $color): ?>
|
||||
<span class="badge bg-<?= esc($color) ?> px-2 py-1"><?= esc($label) ?></span>
|
||||
<?php endforeach; ?>
|
||||
@@ -189,8 +277,29 @@
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<style>
|
||||
.print-only { display: none !important; }
|
||||
.grade-orange td { background: #fff3cd !important; color: #8a5d00; }
|
||||
.grade-red td { background: #f8d7da !important; color: #842029; }
|
||||
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
.print-only { display: block !important; }
|
||||
body { font-size: 11px; }
|
||||
.container-fluid { padding: 0 !important; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 10px; }
|
||||
table th, table td { border: 1px solid #bbb; padding: 3px 5px; }
|
||||
table thead {
|
||||
background: #333 !important;
|
||||
color: #fff !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
table thead th {
|
||||
position: static !important;
|
||||
top: auto !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
@@ -147,6 +147,7 @@ if (empty($schoolYears) && $schoolYear !== '') {
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="min-width:160px">Student Name</th>
|
||||
<th class="text-center">Age</th>
|
||||
<th>Class-Section</th>
|
||||
<th class="text-center">Year Score</th>
|
||||
<th style="min-width:300px">Comments / Rationale & Decision</th>
|
||||
@@ -177,9 +178,11 @@ if (empty($schoolYears) && $schoolYear !== '') {
|
||||
<tr class="<?= esc($rowClass) ?>">
|
||||
<td><?= esc($studentLabel) ?></td>
|
||||
|
||||
<td class="text-center"><?= esc((string)($row['age'] ?? '—')) ?></td>
|
||||
|
||||
<td><?= esc($row['class_section_name'] ?? '—') ?></td>
|
||||
|
||||
<td class="text-center">
|
||||
<td class="text-center" data-order="<?= esc($scoreVal !== null ? (string)$scoreVal : '-1') ?>">
|
||||
<div class="fw-semibold"><?= $displayScore($scoreRaw) ?></div>
|
||||
|
||||
<button type="button"
|
||||
@@ -400,7 +403,7 @@ if (empty($schoolYears) && $schoolYear !== '') {
|
||||
pageLength: 100,
|
||||
lengthMenu: [10, 25, 50, 100, 200],
|
||||
columnDefs: [
|
||||
{ orderable: false, targets: [3] }
|
||||
{ orderable: false, targets: [4] }
|
||||
]
|
||||
});
|
||||
} catch (_) {}
|
||||
@@ -695,4 +698,4 @@ if (empty($schoolYears) && $schoolYear !== '') {
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -40,12 +40,7 @@
|
||||
|
||||
<div class="col-md-6">
|
||||
<h4>Payment Options</h4>
|
||||
<form method="post" action="<?= base_url('payments/createPaypalPayment/' . $payment['id']) ?>">
|
||||
<?= csrf_field(); ?>
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
Pay via PayPal
|
||||
</button>
|
||||
</form>
|
||||
<p class="text-muted mb-0">Online payment is currently unavailable. Please contact the school office to complete payment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -133,9 +133,9 @@ $role = strtolower(session()->get('role') ?? 'guest');
|
||||
<a class="dropdown-item" href="/discounts/list">Discount Management</a>
|
||||
<a class="dropdown-item" href="/expenses/index">Expenses Management</a>
|
||||
<a class="dropdown-item" href="/payment/financial_report">Financial Report</a>
|
||||
<a class="dropdown-item" href="/administrator/tuition-forecast">Tuition Forecast</a>
|
||||
<a class="dropdown-item" href="/invoice_payment/invoice_management">Invoices Management</a>
|
||||
<a class="dropdown-item" href="/payment/manual_pay">Manual Payment</a>
|
||||
<a class="dropdown-item" href="/administrator/paypal_transactions">PaypalTransactions</a>
|
||||
<a class="dropdown-item" href="/refunds/list">Refund Management</a>
|
||||
<a class="dropdown-item" href="/reimbursements/index">Reimbursement Management</a>
|
||||
<a class="dropdown-item" href="/payment/notification_management">Payment Notification Management</a>
|
||||
@@ -233,9 +233,9 @@ $role = strtolower(session()->get('role') ?? 'guest');
|
||||
<a class="dropdown-item" href="/discounts/list">Discount Management</a>
|
||||
<a class="dropdown-item" href="/expenses/index">Expenses Management</a>
|
||||
<a class="dropdown-item" href="/payment/financial_report">Financial Report</a>
|
||||
<a class="dropdown-item" href="/administrator/tuition-forecast">Tuition Forecast</a>
|
||||
<a class="dropdown-item" href="/invoice_payment/invoice_management">Invoices Management</a>
|
||||
<a class="dropdown-item" href="/payment/manual_pay">Manual Payment</a>
|
||||
<a class="dropdown-item" href="/administrator/paypal_transactions">PaypalTransactions</a>
|
||||
<a class="dropdown-item" href="/refunds/list">Refund Management</a>
|
||||
<a class="dropdown-item" href="/reimbursements/index">Reimbursement Management</a>
|
||||
</div>
|
||||
@@ -323,9 +323,9 @@ $role = strtolower(session()->get('role') ?? 'guest');
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="/expenses/index">Expenses Management</a>
|
||||
<a class="dropdown-item" href="/payment/financial_report">Financial Report</a>
|
||||
<a class="dropdown-item" href="/administrator/tuition-forecast">Tuition Forecast</a>
|
||||
<a class="dropdown-item" href="/invoice_payment/invoice_management">Invoices Management</a>
|
||||
<a class="dropdown-item" href="/payment/manual_pay">Manual Payment</a>
|
||||
<a class="dropdown-item" href="/administrator/paypal_transactions">PaypalTransactions</a>
|
||||
<a class="dropdown-item" href="/refunds/list">Refund Management</a>
|
||||
<a class="dropdown-item" href="/reimbursements/index">Reimbursement Management</a>
|
||||
</div>
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<h3 class="h5">Expense Breakdown</h3>
|
||||
<canvas id="expenseChart" style="max-height:320px; width:100%;"></canvas>
|
||||
<h3 class="h5">Expenses out of Net Amount</h3>
|
||||
<canvas id="netExpenseChart" style="max-height:320px; width:100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,10 +77,49 @@
|
||||
|
||||
<script>
|
||||
function fmt(n){ return '$' + Number(n||0).toFixed(2); }
|
||||
const initialSummaryData = {
|
||||
schoolYear: <?= json_encode((string)($schoolYear ?? '')) ?>,
|
||||
totalCharges: <?= json_encode((float)($totalCharges ?? 0)) ?>,
|
||||
totalEventFees: <?= json_encode((float)($totalEventFees ?? 0)) ?>,
|
||||
totalExtraCharges: <?= json_encode((float)($totalExtraCharges ?? 0)) ?>,
|
||||
totalDiscounts: <?= json_encode((float)($totalDiscounts ?? 0)) ?>,
|
||||
totalRefunds: <?= json_encode((float)($totalRefunds ?? 0)) ?>,
|
||||
totalExpenses: <?= json_encode((float)($totalExpenses ?? 0)) ?>,
|
||||
totalReimbursements: <?= json_encode((float)($totalReimbursements ?? 0)) ?>,
|
||||
donationToSchool: <?= json_encode((float)($donationToSchool ?? 0)) ?>,
|
||||
totalPaid: <?= json_encode((float)($totalPaid ?? 0)) ?>,
|
||||
amountCollected: <?= json_encode((float)($amountCollected ?? 0)) ?>,
|
||||
totalUnpaid: <?= json_encode((float)($totalUnpaid ?? 0)) ?>,
|
||||
netAmount: <?= json_encode((float)($netAmount ?? 0)) ?>
|
||||
};
|
||||
|
||||
function applySummaryData(d) {
|
||||
document.getElementById('summaryPeriod').textContent = 'Report for School Year: ' + (d.schoolYear || '');
|
||||
document.getElementById('sumCharges').textContent = fmt(d.totalCharges);
|
||||
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('sumDiscounts').textContent = fmt(d.totalDiscounts);
|
||||
document.getElementById('sumRefunds').textContent = fmt(d.totalRefunds);
|
||||
document.getElementById('sumExpenses').textContent = fmt(d.totalExpenses);
|
||||
document.getElementById('sumReimb').textContent = fmt(d.totalReimbursements);
|
||||
if (document.getElementById('sumDonationToSchool')) {
|
||||
document.getElementById('sumDonationToSchool').textContent = fmt(d.donationToSchool);
|
||||
}
|
||||
document.getElementById('sumNet').textContent = fmt(d.netAmount);
|
||||
document.getElementById('sumCollected').textContent = fmt(d.amountCollected || d.totalPaid || 0);
|
||||
document.getElementById('sumUnpaid').textContent = fmt(d.totalUnpaid);
|
||||
}
|
||||
|
||||
// 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(); });
|
||||
applySummaryData(initialSummaryData);
|
||||
renderCharts(initialSummaryData);
|
||||
loadSummary();
|
||||
});
|
||||
|
||||
@@ -97,26 +136,7 @@ function loadSummary(){
|
||||
.then(d => {
|
||||
if (mySeq !== __sumSeq) return; // stale response
|
||||
if (!d || d.ok !== true) return;
|
||||
document.getElementById('summaryPeriod').textContent = 'Report for School Year: ' + (d.schoolYear||'');
|
||||
document.getElementById('sumCharges').textContent = fmt(d.totalCharges);
|
||||
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('sumDiscounts').textContent = fmt(d.totalDiscounts);
|
||||
document.getElementById('sumRefunds').textContent = fmt(d.totalRefunds);
|
||||
document.getElementById('sumExpenses').textContent = fmt(d.totalExpenses);
|
||||
document.getElementById('sumReimb').textContent = fmt(d.totalReimbursements);
|
||||
if (document.getElementById('sumDonationToSchool')) {
|
||||
document.getElementById('sumDonationToSchool').textContent = fmt(d.donationToSchool);
|
||||
}
|
||||
document.getElementById('sumNet').textContent = fmt(d.netAmount);
|
||||
document.getElementById('sumCollected').textContent = fmt(d.amountCollected);
|
||||
document.getElementById('sumUnpaid').textContent = fmt(d.totalUnpaid);
|
||||
|
||||
// Refresh charts with new data
|
||||
applySummaryData(d);
|
||||
renderCharts(d);
|
||||
})
|
||||
.catch(()=>{});
|
||||
@@ -127,7 +147,7 @@ function renderCharts(d){
|
||||
// Destroy existing if present
|
||||
if (window._summaryChart) { window._summaryChart.destroy(); }
|
||||
if (window._collectedChart) { window._collectedChart.destroy(); }
|
||||
if (window._expenseChart) { window._expenseChart.destroy(); }
|
||||
if (window._netExpenseChart) { window._netExpenseChart.destroy(); }
|
||||
const summaryCtx = document.getElementById('summaryChart').getContext('2d');
|
||||
window._summaryChart = new Chart(summaryCtx, {
|
||||
type: 'bar',
|
||||
@@ -152,11 +172,44 @@ function renderCharts(d){
|
||||
options: { responsive:true, plugins:{ legend:{ position:'bottom' } } }
|
||||
});
|
||||
|
||||
const expenseCtx = document.getElementById('expenseChart').getContext('2d');
|
||||
window._expenseChart = new Chart(expenseCtx, {
|
||||
const netExpenseCtx = document.getElementById('netExpenseChart').getContext('2d');
|
||||
const netAmount = Number(d.netAmount || 0);
|
||||
const totalExpenses = Number(d.totalExpenses || 0);
|
||||
const expensePortion = Math.max(0, Math.min(totalExpenses, netAmount));
|
||||
const remainingNet = Math.max(0, netAmount - expensePortion);
|
||||
const expensePercent = netAmount > 0 ? ((totalExpenses / netAmount) * 100).toFixed(1) : '0.0';
|
||||
const remainingPercent = netAmount > 0 ? ((remainingNet / netAmount) * 100).toFixed(1) : '0.0';
|
||||
const netExpenseValues = [expensePortion, remainingNet];
|
||||
const netExpenseLabels = [
|
||||
'Expenses (' + expensePercent + '%)',
|
||||
'Remaining Net (' + remainingPercent + '%)'
|
||||
];
|
||||
window._netExpenseChart = new Chart(netExpenseCtx, {
|
||||
type: 'pie',
|
||||
data: { labels: ['Expenses','Reimbursements'], datasets: [{ data:[ d.totalExpenses||0, d.totalReimbursements||0 ], backgroundColor: ['#dc3545','#6f42c1']}]},
|
||||
options: { responsive:true }
|
||||
data: {
|
||||
labels: netExpenseLabels,
|
||||
datasets: [{
|
||||
data: netExpenseValues,
|
||||
backgroundColor: ['#20c997', '#dc3545'],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
if (context.dataIndex === 0) {
|
||||
return 'Expenses: ' + fmt(totalExpenses) + ' (' + expensePercent + '% of net amount)';
|
||||
}
|
||||
return 'Remaining Net: ' + fmt(remainingNet) + ' (' + remainingPercent + '%)';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -174,82 +227,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Bar chart summary
|
||||
const summaryCtx = document.getElementById('summaryChart').getContext('2d');
|
||||
window._summaryChart = new Chart(summaryCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Charges', 'Event Fees', 'Paid', 'Unpaid', 'Discounts', 'Refunds', 'Expenses', 'Reimbursements', 'Net'],
|
||||
datasets: [{
|
||||
label: 'Amount (USD)',
|
||||
data: [
|
||||
<?= (float)$totalCharges ?>,
|
||||
<?= (float)($totalEventFees ?? 0) ?>,
|
||||
<?= (float)$totalPaid ?>,
|
||||
<?= (float)$totalUnpaid ?>,
|
||||
<?= (float)$totalDiscounts ?>,
|
||||
<?= (float)$totalRefunds ?>,
|
||||
<?= (float)$totalExpenses ?>,
|
||||
<?= (float)$totalReimbursements ?>,
|
||||
<?= (float)$netAmount ?>
|
||||
],
|
||||
backgroundColor: [
|
||||
'#007bff', '#6610f2', '#28a745', '#ffc107', '#17a2b8', '#ffc107', '#dc3545', '#6f42c1', '#20c997'
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Collected vs Outstanding pie
|
||||
const collectedCtx = document.getElementById('collectedChart').getContext('2d');
|
||||
window._collectedChart = new Chart(collectedCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Amount Collected (Paid)', 'Amount Outstanding (Unpaid)'],
|
||||
datasets: [{
|
||||
data: [
|
||||
<?= (float)$amountCollected ?>,
|
||||
<?= (float)$totalUnpaid ?>
|
||||
],
|
||||
backgroundColor: ['#28a745', '#ffc107']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: { legend: { position: 'bottom' } }
|
||||
}
|
||||
});
|
||||
|
||||
// Pie chart expenses
|
||||
const expenseCtx = document.getElementById('expenseChart').getContext('2d');
|
||||
window._expenseChart = new Chart(expenseCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Expenses', 'Reimbursements'],
|
||||
datasets: [{
|
||||
data: [
|
||||
<?= (float)$totalExpenses ?>,
|
||||
<?= (float)$totalReimbursements ?>
|
||||
],
|
||||
backgroundColor: ['#dc3545', '#6f42c1']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
<td>
|
||||
<?php if (!empty($payment['check_file'])): ?>
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#checkModal<?= (int)$payment['id'] ?>">View</a> /
|
||||
<a href="<?= base_url('payment/serveCheckFile/' . esc($payment['check_file']) . '/download') ?>">Download</a>
|
||||
<a href="<?= base_url('payment/file/' . (int) $payment['id'] . '/download') ?>">Download</a>
|
||||
|
||||
<!-- File Preview Modal -->
|
||||
<div class="modal fade" id="checkModal<?= (int)$payment['id'] ?>" tabindex="-1" aria-hidden="true">
|
||||
@@ -271,7 +271,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<iframe src="<?= base_url('payment/serveCheckFile/' . esc($payment['check_file']) . '/inline') ?>" width="100%" height="600" style="border:none;"></iframe>
|
||||
<iframe src="<?= base_url('payment/file/' . (int) $payment['id'] . '/inline') ?>" width="100%" height="600" style="border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,10 +91,9 @@
|
||||
<td><?= esc($payment['status']) ?></td>
|
||||
<td>
|
||||
<?php if (!empty($payment['check_file'])): ?>
|
||||
<?php $file = rawurlencode((string)$payment['check_file']); ?>
|
||||
<!-- Trigger Modal -->
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#checkModal<?= (int)$payment['id'] ?>">View</a> /
|
||||
<a href="<?= base_url('payment/serveCheckFile/' . $file . '/download') ?>">Download</a>
|
||||
<a href="<?= base_url('payment/file/' . (int) $payment['id'] . '/download') ?>">Download</a>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="checkModal<?= (int)$payment['id'] ?>" tabindex="-1" aria-hidden="true">
|
||||
@@ -105,7 +104,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<iframe src="<?= base_url('payment/serveCheckFile/' . $file . '/inline') ?>"
|
||||
<iframe src="<?= base_url('payment/file/' . (int) $payment['id'] . '/inline') ?>"
|
||||
width="100%" height="600px" style="border:none;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
<!-- This snippet supports rendering separate standalone buttons
|
||||
in different parts of your page with unique payment methods.
|
||||
Test EMAIL: sb-cbmre43313068@business.example.com
|
||||
Test PASSWORD: 9JC?].qM
|
||||
-->
|
||||
|
||||
<?= $this->extend('layout/register_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="container d-flex justify-content-center align-items-start" style="min-height: 100vh; padding-top: 80px;">
|
||||
<div class="registration-form text-center p-4 rounded-4 shadow"
|
||||
style="background-color: white; max-width: 600px; width: 300%;">
|
||||
|
||||
<!-- Logo -->
|
||||
<div class="mb-4">
|
||||
<a href="<?= base_url('/parent_dashboard') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Alrahma Logo"
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Page Title -->
|
||||
<h3 class="text-success" style="font-family: Arial, sans-serif;">Payment</h3>
|
||||
<br>
|
||||
|
||||
<!-- PayPal Button -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
<p class="text-success">
|
||||
<br><strong>Your payment information, including credit card and bank account details, is securely stored only by PayPal and Venmo. </strong>
|
||||
</p>
|
||||
<p class="text-danger">
|
||||
We do not collect or save any financial data on our website.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script src="https://www.paypal.com/sdk/js?client-id=AYsiOekZUvrjgx9C5c554ZeSQ4W6yd0ZX-OHE93_D0fWoa4YXrMmroEeLiAjjdCKkELH8EVZR_yGMLPS¤cy=USD&components=buttons,funding-eligibility&enable-funding=venmo&disable-funding=card,paylater">
|
||||
</script>
|
||||
<script>
|
||||
const parentName = <?= json_encode($parentName) ?>;
|
||||
const totalAmount = <?= json_encode(number_format($totalAmount, 2, '.', '')) ?>;
|
||||
const schoolId = <?= json_encode($schoolId) ?>;
|
||||
|
||||
paypal.Buttons({
|
||||
style: {
|
||||
layout: 'vertical',
|
||||
color: 'blue',
|
||||
shape: 'rect',
|
||||
label: 'paypal'
|
||||
},
|
||||
createOrder: function(data, actions) {
|
||||
return actions.order.create({
|
||||
purchase_units: [{
|
||||
amount: {
|
||||
value: totalAmount
|
||||
},
|
||||
custom_id: `${schoolId}`,
|
||||
description: `Payment by ${parentName}`
|
||||
}]
|
||||
});
|
||||
},
|
||||
|
||||
onApprove: function(data, actions) {
|
||||
return actions.order.capture().then(function(details) {
|
||||
alert('Transaction completed by ' + details.payer.name.given_name + ' for <?= esc($parentName) ?>');
|
||||
});
|
||||
},
|
||||
onError: function(err) {
|
||||
console.error('An error occurred during the transaction', err);
|
||||
}
|
||||
}).render('#paypal-button-container');
|
||||
</script>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
@@ -113,7 +113,7 @@
|
||||
<td><?= esc($r['check_nbr'] ?? '-') ?></td>
|
||||
<td>
|
||||
<?php if (!empty($r['check_file'])): ?>
|
||||
<a href="<?= base_url('uploads/checks/' . $r['check_file']) ?>" target="_blank">View</a>
|
||||
<a href="<?= base_url('refunds/file/' . (int) $r['id'] . '/inline') ?>" target="_blank">View</a>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
@@ -200,7 +200,7 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="checkFile" class="form-label">Upload Check Image</label>
|
||||
<input type="file" class="form-control" name="check_file" id="checkFile" accept="image/*">
|
||||
<input type="file" class="form-control" name="check_file" id="checkFile" accept=".jpg,.jpeg,.png,.pdf">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user