fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s
Tests / PHPUnit (push) Failing after 1m6s
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<div class="container-fluid py-3">
|
||||
<h2 class="mb-3">Financial Aid Requests</h2>
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-3">
|
||||
<input class="form-control" name="school_year" placeholder="School year" value="<?= esc($schoolYear ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Parent</th>
|
||||
<th>Year</th>
|
||||
<th>Status</th>
|
||||
<th>Requested</th>
|
||||
<th>Submitted</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($requests ?? []) as $row): ?>
|
||||
<?php $parent = $parents[(int) ($row['parent_id'] ?? 0)] ?? []; ?>
|
||||
<tr>
|
||||
<td><?= (int) ($row['id'] ?? 0) ?></td>
|
||||
<td><?= esc(trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')) ?: ('#' . (int) ($row['parent_id'] ?? 0))) ?></td>
|
||||
<td><?= esc($row['school_year'] ?? '') ?></td>
|
||||
<td><?= esc($row['status'] ?? '') ?></td>
|
||||
<td><?= $row['requested_amount'] !== null && $row['requested_amount'] !== '' ? '$' . number_format((float) $row['requested_amount'], 2) : '—' ?></td>
|
||||
<td><?= esc($row['created_at'] ?? '') ?></td>
|
||||
<td><a class="btn btn-sm btn-outline-primary" href="<?= site_url('administrator/financial-aid/' . (int) $row['id']) ?>">Review</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($requests)): ?>
|
||||
<tr><td colspan="7" class="text-muted text-center">No financial aid requests found.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<div class="container py-3">
|
||||
<a href="<?= site_url('administrator/financial-aid') ?>" class="small">← Back to queue</a>
|
||||
<h2 class="mt-2">Review Financial Aid Request #<?= (int) ($requestRow['id'] ?? 0) ?></h2>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="border rounded p-3 mb-3">
|
||||
<div><strong>Parent:</strong> <?= esc(trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? ''))) ?> <?= esc($parent['email'] ?? '') ?></div>
|
||||
<div><strong>School year:</strong> <?= esc($requestRow['school_year'] ?? '') ?></div>
|
||||
<div><strong>Status:</strong> <?= esc($requestRow['status'] ?? '') ?></div>
|
||||
<div><strong>Household size:</strong> <?= esc((string) ($requestRow['household_size'] ?? 'Not provided')) ?></div>
|
||||
<div><strong>Requested amount:</strong> <?= $requestRow['requested_amount'] !== null && $requestRow['requested_amount'] !== '' ? '$' . number_format((float) $requestRow['requested_amount'], 2) : 'Not specified' ?></div>
|
||||
<div class="mt-2"><strong>Need statement</strong></div>
|
||||
<p><?= nl2br(esc($requestRow['need_statement'] ?? '')) ?></p>
|
||||
<div><strong>Students</strong></div>
|
||||
<ul>
|
||||
<?php foreach (($students ?? []) as $student): ?>
|
||||
<li><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (in_array((string) ($requestRow['status'] ?? ''), ['submitted', 'under_review'], true)): ?>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<form method="post" action="<?= site_url('administrator/financial-aid/' . (int) $requestRow['id'] . '/approve') ?>" class="border rounded p-3">
|
||||
<?= csrf_field() ?>
|
||||
<h5>Approve</h5>
|
||||
<label class="form-label" for="admin_amount">Amount to apply</label>
|
||||
<input class="form-control mb-2" type="number" min="0.01" step="0.01" name="admin_amount" id="admin_amount" required value="<?= esc(old('admin_amount', $requestRow['requested_amount'] ?? '')) ?>">
|
||||
<label class="form-label" for="admin_note">Note</label>
|
||||
<textarea class="form-control mb-2" name="admin_note" id="admin_note" rows="3"><?= esc(old('admin_note')) ?></textarea>
|
||||
<button class="btn btn-success" type="submit">Approve and apply to invoice</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<form method="post" action="<?= site_url('administrator/financial-aid/' . (int) $requestRow['id'] . '/deny') ?>" class="border rounded p-3">
|
||||
<?= csrf_field() ?>
|
||||
<h5>Deny</h5>
|
||||
<label class="form-label" for="deny_note">Reason</label>
|
||||
<textarea class="form-control mb-2" name="admin_note" id="deny_note" rows="3" required><?= esc(old('admin_note')) ?></textarea>
|
||||
<button class="btn btn-danger" type="submit">Deny request</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -152,17 +152,17 @@
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end mb-2">
|
||||
<div class="col-md-2">
|
||||
<label for="sectionCount" class="form-label">Number of Sections</label>
|
||||
<label for="sectionCount" class="form-label">Max Number of Sections</label>
|
||||
<input type="number" min="1" id="sectionCount" class="form-control" placeholder="e.g. 2" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="minStudents" class="form-label">Minimum Students</label>
|
||||
<label for="minStudents" class="form-label">Min Students Per Section</label>
|
||||
<input type="number" min="1" id="minStudents" class="form-control" placeholder="e.g. 20" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<!--div class="col-md-2">
|
||||
<label for="maxStudents" class="form-label">Maximum Students</label>
|
||||
<input type="number" min="1" id="maxStudents" class="form-control" placeholder="Optional" />
|
||||
</div>
|
||||
</div-->
|
||||
<div class="col-md-3 d-flex gap-2">
|
||||
<button type="button" id="refreshTotalsBtn" class="btn btn-outline-secondary">Refresh Totals</button>
|
||||
<button type="button" id="generateAllBtn" class="btn btn-primary">Generate All</button>
|
||||
@@ -217,6 +217,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tblBody"></tbody>
|
||||
<tfoot class="table-light">
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<th class="text-end" id="allStudentsTotal">0</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,6 +254,7 @@
|
||||
}, $classes ?? [])), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
|
||||
const tblBody = document.getElementById('tblBody');
|
||||
const allStudentsTotal = document.getElementById('allStudentsTotal');
|
||||
const sectionCountInput = document.getElementById('sectionCount');
|
||||
const minInput = document.getElementById('minStudents');
|
||||
const maxInput = document.getElementById('maxStudents');
|
||||
@@ -352,6 +360,20 @@
|
||||
});
|
||||
});
|
||||
|
||||
if (!resolvedClassId && selectedClassId > 0) {
|
||||
const firstSection = (sectionsByClassId[String(selectedClassId)] || [])[0] || null;
|
||||
if (firstSection) {
|
||||
select.value = String(firstSection.id);
|
||||
resolvedClassId = selectedClassId;
|
||||
} else {
|
||||
const baseClass = baseClasses.find(c => c.class_id === selectedClassId) || null;
|
||||
if (baseClass) {
|
||||
select.value = String(baseClass.class_section_id);
|
||||
resolvedClassId = selectedClassId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedOption = select.options[select.selectedIndex] || null;
|
||||
if (!resolvedClassId && selectedOption) {
|
||||
resolvedClassId = parseInt(selectedOption.dataset.classId || '0', 10);
|
||||
@@ -380,8 +402,10 @@
|
||||
function buildInitialTable(rows) {
|
||||
tblBody.innerHTML = '';
|
||||
rowIndexByClassId = {};
|
||||
let allTotal = 0;
|
||||
|
||||
rows.forEach(function(r){
|
||||
allTotal += parseInt(r.total || '0', 10) || 0;
|
||||
const tr = document.createElement('tr');
|
||||
tr.dataset.classId = r.class_id;
|
||||
tr.dataset.classSectionId = r.class_section_id;
|
||||
@@ -417,13 +441,16 @@
|
||||
renderSectionsForRow(r.class_section_id, r.sections, r.students || []);
|
||||
}
|
||||
});
|
||||
if (allStudentsTotal) {
|
||||
allStudentsTotal.textContent = String(allTotal);
|
||||
}
|
||||
applyStudentSearch();
|
||||
}
|
||||
|
||||
function runDistribution(baseSectionId, baseName) {
|
||||
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
|
||||
const minStudents = parseInt(minInput.value || '0', 10);
|
||||
const maxStudents = parseInt(maxInput.value || '0', 10);
|
||||
const maxStudents = parseInt((maxInput && maxInput.value) || '0', 10);
|
||||
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
|
||||
msgEl.textContent = 'Enter number of sections and minimum students first.';
|
||||
return;
|
||||
@@ -611,6 +638,7 @@
|
||||
student_name: assignment.student_name || 'Student',
|
||||
age_at_reference: assignment.age_at_reference ?? null,
|
||||
gender: assignment.gender || '',
|
||||
previous_final_score: assignment.previous_final_score ?? null,
|
||||
last_year_class_section: assignment.last_year_class_section || '',
|
||||
class_id: parseInt(assignment.class_id || section.class_id || '0', 10),
|
||||
class_section_id: parseInt(assignment.class_section_id || section.class_section_id || '0', 10),
|
||||
@@ -623,6 +651,27 @@
|
||||
return out;
|
||||
}
|
||||
|
||||
function assignmentStats(assignments) {
|
||||
const stats = { male: 0, female: 0, scoreTotal: 0, scoreCount: 0 };
|
||||
(Array.isArray(assignments) ? assignments : []).forEach(function(assignment){
|
||||
const gender = String(assignment.gender || '').trim().toLowerCase();
|
||||
if (gender === 'female' || gender === 'f') {
|
||||
stats.female++;
|
||||
} else if (gender === 'male' || gender === 'm') {
|
||||
stats.male++;
|
||||
}
|
||||
|
||||
const score = Number(assignment.previous_final_score);
|
||||
if (Number.isFinite(score)) {
|
||||
stats.scoreTotal += score;
|
||||
stats.scoreCount++;
|
||||
}
|
||||
});
|
||||
|
||||
stats.averageScore = stats.scoreCount ? stats.scoreTotal / stats.scoreCount : null;
|
||||
return stats;
|
||||
}
|
||||
|
||||
function renderClassRoster(tr, assignments) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'distribution-class-roster';
|
||||
@@ -641,6 +690,23 @@
|
||||
title.appendChild(count);
|
||||
wrap.appendChild(title);
|
||||
|
||||
const stats = assignmentStats(assignments);
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'distribution-meta';
|
||||
if (stats.averageScore !== null) {
|
||||
const avg = document.createElement('span');
|
||||
avg.className = 'badge text-bg-light';
|
||||
avg.textContent = 'Avg ' + stats.averageScore.toFixed(2);
|
||||
meta.appendChild(avg);
|
||||
}
|
||||
if (stats.male || stats.female) {
|
||||
const gender = document.createElement('span');
|
||||
gender.className = 'badge text-bg-light';
|
||||
gender.textContent = 'M ' + stats.male + ' / F ' + stats.female;
|
||||
meta.appendChild(gender);
|
||||
}
|
||||
if (meta.children.length) wrap.appendChild(meta);
|
||||
|
||||
if (!assignments.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'distribution-empty';
|
||||
|
||||
@@ -30,7 +30,6 @@ $warningText = static function (array $warnings): string {
|
||||
'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'] ?? '',
|
||||
'youth_unit_price' => $filters['youth_unit_price'] ?? '',
|
||||
]);
|
||||
?>
|
||||
<a href="<?= site_url('administrator/tuition-forecast/export?' . $exportQuery) ?>" class="btn btn-success">
|
||||
@@ -71,7 +70,7 @@ $warningText = static function (array $warnings): string {
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="unit_price" class="form-label">Grades Unit Price</label>
|
||||
<label for="unit_price" class="form-label">Base Tuition</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -80,19 +79,7 @@ $warningText = static function (array $warnings): string {
|
||||
name="unit_price"
|
||||
class="form-control"
|
||||
value="<?= esc((string) ($filters['unit_price'] ?? ($summary['unit_price'] ?? ''))) ?>"
|
||||
placeholder="New tuition unit price for grades">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="youth_unit_price" class="form-label">Youth Unit Price</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
id="youth_unit_price"
|
||||
name="youth_unit_price"
|
||||
class="form-control"
|
||||
value="<?= esc((string) ($filters['youth_unit_price'] ?? ($summary['youth_unit_price'] ?? ''))) ?>"
|
||||
placeholder="New tuition unit price for youth">
|
||||
placeholder="First student tuition">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check">
|
||||
@@ -149,19 +136,11 @@ $warningText = static function (array $warnings): string {
|
||||
<div class="col-xl col-lg-3 col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Grades Unit Price</div>
|
||||
<div class="text-muted small">Base Tuition</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['unit_price'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl col-lg-3 col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small">Youth Unit Price</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['youth_unit_price'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl col-lg-3 col-md-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
|
||||
Reference in New Issue
Block a user