This commit is contained in:
@@ -6,12 +6,48 @@
|
||||
$target = $preview['target'] ?? null;
|
||||
$overview = $preview['overview'] ?? [];
|
||||
$finance = $preview['finance'] ?? [];
|
||||
$promotion = $preview['promotion'] ?? ['summary' => [], 'rows' => []];
|
||||
$promotionSummary = $promotion['summary'] ?? [];
|
||||
$promotionTable = $promotionTable ?? [];
|
||||
$promotionRows = $promotionTable['rows'] ?? ($promotion['rows'] ?? []);
|
||||
$promotionSort = (string) ($promotionTable['sort'] ?? 'class');
|
||||
$promotionOrder = (string) ($promotionTable['order'] ?? 'asc');
|
||||
$promotionPage = (int) ($promotionTable['page'] ?? 1);
|
||||
$promotionPerPage = (int) ($promotionTable['perPage'] ?? 25);
|
||||
$promotionTotal = (int) ($promotionTable['total'] ?? count($promotionRows));
|
||||
$promotionPageCount = (int) ($promotionTable['pageCount'] ?? 1);
|
||||
$promotionFrom = (int) ($promotionTable['from'] ?? ($promotionTotal === 0 ? 0 : 1));
|
||||
$promotionTo = (int) ($promotionTable['to'] ?? count($promotionRows));
|
||||
$promotionPerPageOptions = $promotionTable['allowedPerPage'] ?? [10, 25, 50, 100];
|
||||
$blockers = $preview['blockers'] ?? [];
|
||||
$warnings = $preview['warnings'] ?? [];
|
||||
$carryForward = $preview['carry_forward'] ?? [];
|
||||
$status = (string) ($source['status'] ?? '');
|
||||
$batchStatus = (string) ($latestBatch['status'] ?? '');
|
||||
$money = static fn ($value): string => '$' . number_format((float) $value, 2);
|
||||
$score = static fn ($value): string => is_numeric($value) ? number_format((float) $value, 2) : '-';
|
||||
$promotionUrl = static function (array $overrides = []) use ($source, $target, $promotionSort, $promotionOrder, $promotionPage, $promotionPerPage): string {
|
||||
$query = array_merge([
|
||||
'target_school_year_id' => $target['id'] ?? null,
|
||||
'sort' => $promotionSort,
|
||||
'order' => $promotionOrder,
|
||||
'page' => $promotionPage,
|
||||
'per_page' => $promotionPerPage,
|
||||
], $overrides);
|
||||
$query = array_filter($query, static fn ($value): bool => $value !== null && $value !== '');
|
||||
|
||||
return site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview?' . http_build_query($query));
|
||||
};
|
||||
$sortLink = static function (string $key, string $label, string $class = '') use ($promotionSort, $promotionOrder, $promotionUrl): string {
|
||||
$nextOrder = $promotionSort === $key && $promotionOrder === 'asc' ? 'desc' : 'asc';
|
||||
$indicator = $promotionSort === $key ? ($promotionOrder === 'asc' ? ' (asc)' : ' (desc)') : '';
|
||||
|
||||
return '<a class="link-dark text-decoration-none ' . esc($class, 'attr') . '" href="' . esc($promotionUrl([
|
||||
'sort' => $key,
|
||||
'order' => $nextOrder,
|
||||
'page' => 1,
|
||||
]), 'attr') . '">' . esc($label . $indicator) . '</a>';
|
||||
};
|
||||
?>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
@@ -23,21 +59,30 @@
|
||||
<div class="container-fluid py-3">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="mb-1">Closing Preview: <?= esc($source['name'] ?? '') ?></h2>
|
||||
<h2 class="mb-1">End-Year Checklist: <?= esc($source['name'] ?? '') ?></h2>
|
||||
<div class="text-muted">
|
||||
Status: <span class="fw-semibold"><?= esc(ucfirst($status)) ?></span>
|
||||
<?php if ($target): ?>
|
||||
<span class="mx-2">Target: <span class="fw-semibold"><?= esc($target['name'] ?? '') ?></span></span>
|
||||
<span class="mx-2">Next year: <span class="fw-semibold"><?= esc($target['name'] ?? '') ?></span></span>
|
||||
<?php endif; ?>
|
||||
<span>Generated: <?= esc($preview['generated_at'] ?? '') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-secondary" href="<?= site_url('administrator/school-years') ?>">Back to Management</a>
|
||||
<a class="btn btn-outline-secondary" href="<?= site_url('administrator/school-years') ?>">Back to School Years</a>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<h5 class="mb-2">How to End This Year</h5>
|
||||
<ol class="mb-0">
|
||||
<li>Clear all blocking issues below.</li>
|
||||
<li>Start the end-year process to lock the checklist.</li>
|
||||
<li>Confirm carry-forward balances, then mark this school year closed.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<form class="row g-2 align-items-end mb-4" method="get" action="<?= site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview') ?>">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="target_school_year_id">Target school year</label>
|
||||
<label class="form-label" for="target_school_year_id">Next school year</label>
|
||||
<select class="form-select" id="target_school_year_id" name="target_school_year_id">
|
||||
<option value="">Auto-select next draft year</option>
|
||||
<?php foreach (($schoolYears ?? []) as $year): ?>
|
||||
@@ -50,7 +95,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-primary" type="submit">Refresh Preview</button>
|
||||
<button class="btn btn-primary" type="submit">Refresh Checklist</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -130,11 +175,11 @@
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<h5>Closing Progress</h5>
|
||||
<h5>End-Year Progress</h5>
|
||||
<ol class="mb-0">
|
||||
<li>Preview generated</li>
|
||||
<li>Checklist generated</li>
|
||||
<li class="<?= $blockers === [] ? '' : 'text-muted' ?>">Validation <?= $blockers === [] ? 'passed' : 'has blockers' ?></li>
|
||||
<li class="<?= $batchStatus !== '' ? '' : 'text-muted' ?>">Closing batch <?= $batchStatus !== '' ? esc($batchStatus) : 'not created' ?></li>
|
||||
<li class="<?= $batchStatus !== '' ? '' : 'text-muted' ?>">End-year process <?= $batchStatus !== '' ? esc($batchStatus) : 'not started' ?></li>
|
||||
<li class="<?= in_array($batchStatus, ['executed', 'completed'], true) ? '' : 'text-muted' ?>">Carry-forward completed</li>
|
||||
<li class="<?= $status === 'closed' ? '' : 'text-muted' ?>">Year closed</li>
|
||||
</ol>
|
||||
@@ -142,13 +187,186 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">Student Promotion Preview</h5>
|
||||
<div class="text-muted small">Promotion decisions must be generated and resolved before this school year can end.</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary btn-sm" href="<?= site_url('grading/decisions?' . http_build_query(['school_year' => $source['name'] ?? ''])) ?>">
|
||||
Manage Promotion Decisions
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Students</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['total_students'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Decided</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['with_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Pass</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['pass'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Other</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['other_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Queued</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['queued'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Assigned</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['assigned'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Applied</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['applied'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Pending</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['pending_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing Queue</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_queue'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||
<div class="text-muted small">
|
||||
Showing <?= esc((string) $promotionFrom) ?>-<?= esc((string) $promotionTo) ?> of <?= esc((string) $promotionTotal) ?> students
|
||||
</div>
|
||||
<form class="d-flex align-items-center gap-2" method="get" action="<?= site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview') ?>">
|
||||
<?php if ($target): ?>
|
||||
<input type="hidden" name="target_school_year_id" value="<?= (int) $target['id'] ?>">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="sort" value="<?= esc($promotionSort, 'attr') ?>">
|
||||
<input type="hidden" name="order" value="<?= esc($promotionOrder, 'attr') ?>">
|
||||
<input type="hidden" name="page" value="1">
|
||||
<label class="form-label small text-muted mb-0" for="promotion_per_page">Rows</label>
|
||||
<select class="form-select form-select-sm w-auto" id="promotion_per_page" name="per_page" onchange="this.form.submit()">
|
||||
<?php foreach ($promotionPerPageOptions as $option): ?>
|
||||
<option value="<?= (int) $option ?>" <?= (int) $option === $promotionPerPage ? 'selected' : '' ?>>
|
||||
<?= esc((string) $option) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $sortLink('student', 'Student') ?></th>
|
||||
<th><?= $sortLink('school_id', 'School ID') ?></th>
|
||||
<th><?= $sortLink('class', 'Class') ?></th>
|
||||
<th class="text-end"><?= $sortLink('year_score', 'Year Score') ?></th>
|
||||
<th><?= $sortLink('decision', 'Decision') ?></th>
|
||||
<th><?= $sortLink('source', 'Source') ?></th>
|
||||
<th><?= $sortLink('queue', 'Queue') ?></th>
|
||||
<th><?= $sortLink('target', 'Next Year Placement') ?></th>
|
||||
<th><?= $sortLink('status', 'Status') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($promotionTotal === 0): ?>
|
||||
<tr><td colspan="9" class="text-muted">No active class assignments found for this school year.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($promotionRows as $row): ?>
|
||||
<?php
|
||||
$rowStatus = (string) ($row['status'] ?? 'missing');
|
||||
$statusClass = $rowStatus === 'decided' ? 'success' : 'danger';
|
||||
$queueStatus = (string) ($row['queue_status'] ?? '');
|
||||
$sourceLabel = (string) ($row['source'] ?? '');
|
||||
if ($sourceLabel === 'automatic_kg_age') {
|
||||
$sourceLabel = 'Auto KG age';
|
||||
}
|
||||
$targetLabel = trim((string) ($row['target_section'] ?? ''));
|
||||
if ($targetLabel === '') {
|
||||
$targetLabel = trim((string) ($row['target_class'] ?? ''));
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?= esc($row['student_name'] ?? ('Student #' . (int) ($row['student_id'] ?? 0))) ?></td>
|
||||
<td><?= esc($row['school_id'] ?? '') ?></td>
|
||||
<td><?= esc($row['class_section_name'] ?? '') ?></td>
|
||||
<td class="text-end"><?= esc($score($row['year_score'] ?? null)) ?></td>
|
||||
<td><?= esc(($row['decision'] ?? '') !== '' ? $row['decision'] : '-') ?></td>
|
||||
<td><?= esc($sourceLabel) ?></td>
|
||||
<td><?= esc($queueStatus !== '' ? ucfirst($queueStatus) : '-') ?></td>
|
||||
<td><?= esc($targetLabel !== '' ? $targetLabel : '-') ?></td>
|
||||
<td><span class="badge bg-<?= esc($statusClass) ?>"><?= esc(ucfirst($rowStatus)) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php if ($promotionPageCount > 1): ?>
|
||||
<?php
|
||||
$pageStart = max(1, $promotionPage - 2);
|
||||
$pageEnd = min($promotionPageCount, $promotionPage + 2);
|
||||
if ($pageEnd - $pageStart < 4) {
|
||||
$pageStart = max(1, $pageEnd - 4);
|
||||
$pageEnd = min($promotionPageCount, $pageStart + 4);
|
||||
}
|
||||
?>
|
||||
<nav aria-label="Student promotion pages">
|
||||
<ul class="pagination pagination-sm justify-content-end mb-0">
|
||||
<li class="page-item <?= $promotionPage <= 1 ? 'disabled' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => max(1, $promotionPage - 1)]), 'attr') ?>">Previous</a>
|
||||
</li>
|
||||
<?php for ($pageNumber = $pageStart; $pageNumber <= $pageEnd; $pageNumber++): ?>
|
||||
<li class="page-item <?= $pageNumber === $promotionPage ? 'active' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => $pageNumber]), 'attr') ?>"><?= esc((string) $pageNumber) ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
<li class="page-item <?= $promotionPage >= $promotionPageCount ? 'disabled' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => min($promotionPageCount, $promotionPage + 1)]), 'attr') ?>">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<h5>Carry-Forward Families</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<table class="table table-sm align-middle no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Family</th>
|
||||
<th>Parent</th>
|
||||
<th class="text-end">Source Balance</th>
|
||||
<th class="text-end">Credits</th>
|
||||
<th class="text-end">Adjustments</th>
|
||||
@@ -157,11 +375,17 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($carryForward === []): ?>
|
||||
<tr><td colspan="5" class="text-muted">No carry-forward balances found.</td></tr>
|
||||
<tr><td colspan="6" class="text-muted">No carry-forward balances found.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($carryForward as $row): ?>
|
||||
<tr>
|
||||
<td><?= esc($row['family']) ?></td>
|
||||
<td>
|
||||
<div><?= esc($row['parent'] ?? ('Parent #' . (int) ($row['family_id'] ?? 0))) ?></div>
|
||||
<?php if (! empty($row['parent_email'])): ?>
|
||||
<div class="text-muted small"><?= esc($row['parent_email']) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end"><?= esc($money($row['source_balance'])) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['credit_amount'])) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['adjustment_amount'])) ?></td>
|
||||
@@ -178,28 +402,37 @@
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/start') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="target_school_year_id" value="<?= (int) $target['id'] ?>">
|
||||
<button class="btn btn-warning" type="submit">Begin Closing</button>
|
||||
<button class="btn btn-warning" type="submit">Close Year</button>
|
||||
</form>
|
||||
<?php elseif ($status === 'active'): ?>
|
||||
<button class="btn btn-warning" type="button" disabled>Close Year</button>
|
||||
<span class="align-self-center text-muted small">
|
||||
<?php if (! $target): ?>
|
||||
Create or select the next school year first.
|
||||
<?php elseif ($blockers !== []): ?>
|
||||
Resolve the blocking issues above first.
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && $batchStatus === 'started'): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/execute') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-primary" type="submit">Execute Carry-Forward</button>
|
||||
<button class="btn btn-primary" type="submit">Confirm Carry-Forward</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && $batchStatus === 'executed'): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/complete') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-success" type="submit">Complete Closing</button>
|
||||
<button class="btn btn-success" type="submit">Close Year</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && ! in_array($batchStatus, ['executed', 'completed'], true)): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/cancel') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-outline-secondary" type="submit">Cancel Closing</button>
|
||||
<button class="btn btn-outline-secondary" type="submit">Cancel End-Year Process</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user