fix invoice and enrollment fees
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 48s
Tests / PHPUnit (push) Failing after 1m19s

This commit is contained in:
root
2026-08-27 18:34:36 -04:00
parent 0ae9993d82
commit 38644b32ae
29 changed files with 1836 additions and 1555 deletions
+127 -9
View File
@@ -74,11 +74,70 @@
width: 100%;
}
.enrollment-policy-ack {
align-items: flex-start;
background: #fff8e1;
border: 2px solid #ffc107;
border-radius: 8px;
bottom: 0;
box-shadow: 0 -.35rem 1rem rgba(33, 37, 41, .08);
display: flex;
gap: .85rem;
margin-top: 1rem;
padding: 1rem;
position: sticky;
z-index: 2;
}
.enrollment-policy-ack.is-accepted {
background: #eef8f0;
border-color: #198754;
}
.enrollment-policy-ack .form-check-input {
border: 2px solid #212529;
flex: 0 0 auto;
height: 1.6rem;
margin: .1rem 0 0;
width: 1.6rem;
}
.enrollment-policy-ack .form-check-input:focus {
box-shadow: 0 0 0 .25rem rgba(255, 193, 7, .35);
}
.enrollment-policy-ack .form-check-input:checked {
background-color: #198754;
border-color: #198754;
}
.enrollment-policy-ack-label {
color: #212529;
cursor: pointer;
font-size: 1rem;
line-height: 1.35;
}
.enrollment-policy-ack-label strong {
display: block;
}
.enrollment-policy-ack-label span {
color: #6c757d;
display: block;
font-size: .875rem;
margin-top: .15rem;
}
@media (max-width: 767.98px) {
.enrollment-policy-frame {
height: 72vh;
min-height: 480px;
}
.enrollment-policy-ack {
padding: .85rem;
}
}
.enrollment-withdraw-control {
@@ -578,6 +637,7 @@ $studentCount = count($students ?? []);
data-required-action="<?= esc($student['required_action_label'] ?? 'Contact administration') ?>"
data-expected-placement="<?= esc($student['expected_placement_label'] ?? $gradeLabel) ?>"
data-is-new="<?= (string) ($student['is_new'] ?? '1') === '1' ? '1' : '0' ?>"
data-counts-tuition="<?= $hasSettledEnrollment ? '1' : '0' ?>"
data-selectable="<?= $canEnroll ? '1' : '0' ?>"
data-already-enrolled="<?= $hasSettledEnrollment ? '1' : '0' ?>"
data-block-title="<?= esc($blockTitle) ?>"
@@ -781,10 +841,11 @@ $studentCount = count($students ?? []);
<h6 class="fw-semibold">Acknowledge school policies</h6>
<div class="text-muted small mb-3">Review the current school policies before submitting enrollment.</div>
<iframe src="<?= base_url('policy/school_policy') ?>" class="enrollment-policy-frame" frameborder="0" title="School Policies"></iframe>
<div class="form-check mt-3">
<div class="enrollment-policy-ack <?= $hasAcceptedSchoolPolicy ? 'is-accepted' : '' ?>" id="schoolPolicyAcceptedPanel">
<input class="form-check-input" type="checkbox" value="1" id="schoolPolicyAcceptedCheckbox" <?= $hasAcceptedSchoolPolicy ? 'checked disabled' : '' ?>>
<label class="form-check-label" for="schoolPolicyAcceptedCheckbox">
I have read and accept all school policies.
<label class="enrollment-policy-ack-label" for="schoolPolicyAcceptedCheckbox">
<strong>I have read and accept all school policies.</strong>
<span>This acknowledgement is required before enrollment can continue.</span>
</label>
</div>
</div>
@@ -932,6 +993,7 @@ $studentCount = count($students ?? []);
let latestEligibility = {};
const policyAcceptedInput = document.getElementById('accept_school_policy_input');
const policyAcceptedCheckbox = document.getElementById('schoolPolicyAcceptedCheckbox');
const policyAcceptedPanel = document.getElementById('schoolPolicyAcceptedPanel');
const backButton = document.getElementById('enrollmentBackButton');
const nextButton = document.getElementById('enrollmentNextButton');
const submitButton = document.getElementById('enrollmentSubmitButton');
@@ -987,10 +1049,62 @@ $studentCount = count($students ?? []);
}
}
function parseGradeRank(value) {
let text = String(value || '').trim().toUpperCase().replace(/\s+/g, ' ');
text = text.replace(/[._-]/g, ' ');
if (['PK', 'P K', 'PREK', 'PRE K', 'PRE KINDER', 'PREKINDER'].includes(text)) return -1;
if (['K', 'KG', 'K G', 'KINDER', 'KINDERGARTEN'].includes(text)) return 1;
const youth = text.match(/^Y(?:OUTH)?\s*(\d+)?$/);
if (youth) return 9 + (youth[1] ? Math.max(1, Number(youth[1])) : 1);
const grade = text.match(/^(?:GR?ADE\s*)?(\d{1,2})\s*([A-Z]*)$/);
if (grade) return Number(grade[1]);
const match = text.match(/\d+/);
return match ? Number(match[0]) : 999;
}
function cardFeeGrade(card) {
return card?.dataset.expectedPlacement || card?.dataset.currentGrade || '';
}
function billableTuitionCards() {
const cardsByStudentId = new Map();
studentCards().forEach(card => {
if (card.dataset.countsTuition === '1') {
cardsByStudentId.set(String(card.dataset.studentId || ''), card);
}
});
selectedEnrollInputs().forEach(input => {
const card = input.closest('[data-student-card]');
if (card) {
cardsByStudentId.set(String(card.dataset.studentId || ''), card);
}
});
return Array.from(cardsByStudentId.values()).sort((left, right) => {
const rankDiff = parseGradeRank(cardFeeGrade(left)) - parseGradeRank(cardFeeGrade(right));
if (rankDiff !== 0) return rankDiff;
return String(left.dataset.studentName || '').localeCompare(String(right.dataset.studentName || ''));
});
}
function tuitionFeeByStudentId() {
const fees = new Map();
billableTuitionCards().forEach((card, index) => {
fees.set(
String(card.dataset.studentId || ''),
index === 0 ? Number(feeSchedule.firstStudentFee || 0) : Number(feeSchedule.secondStudentFee || 0)
);
});
return fees;
}
function calculateSelectedTuition() {
return selectedEnrollInputs().reduce((total, input, index) => {
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
return total + (familyPosition === 0 ? Number(feeSchedule.firstStudentFee || 0) : Number(feeSchedule.secondStudentFee || 0));
const fees = tuitionFeeByStudentId();
return selectedEnrollInputs().reduce((total, input) => {
const card = input.closest('[data-student-card]');
return total + Number(fees.get(String(card?.dataset.studentId || '')) || 0);
}, 0);
}
@@ -1030,6 +1144,7 @@ $studentCount = count($students ?? []);
const isNewStudent = card?.dataset.isNew === '1';
const name = (firstName + ' ' + lastName).trim() || card?.querySelector('.fw-semibold')?.textContent?.trim() || 'Student';
return {
studentId: card?.dataset.studentId || '',
name,
schoolId,
dob,
@@ -1051,9 +1166,9 @@ $studentCount = count($students ?? []);
return;
}
feeReviewList.innerHTML = cards.map((student, index) => {
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
const tuitionFee = familyPosition === 0 ? feeSchedule.firstStudentFee : feeSchedule.secondStudentFee;
const fees = tuitionFeeByStudentId();
feeReviewList.innerHTML = cards.map((student) => {
const tuitionFee = fees.get(String(student.studentId || '')) || 0;
const lines = [
['Student', student.name],
['School ID', student.schoolId || 'N/A'],
@@ -1286,6 +1401,9 @@ $studentCount = count($students ?? []);
if (policyAcceptedInput) {
policyAcceptedInput.value = hasAcceptedSchoolPolicy ? '1' : '0';
}
if (policyAcceptedPanel) {
policyAcceptedPanel.classList.toggle('is-accepted', hasAcceptedSchoolPolicy);
}
}
function setStudentFieldsEnabled(card, enabled) {