fix school year and enrollment steps
Tests / PHPUnit (push) Failing after 58s

This commit is contained in:
root
2026-08-15 21:15:05 -04:00
parent 4603d9ced2
commit 3cbfc40934
18 changed files with 1142 additions and 163 deletions
+172 -6
View File
@@ -391,7 +391,8 @@ class ParentController extends BaseController
'lastDayOfRegistration' => $this->lastDayOfRegistration,
'schoolStartDate' => $this->schoolStartDate,
'hasAcceptedSchoolPolicy' => $this->hasAcceptedPolicyForYear((int) $parentId, $selectedYear),
'familyFinancialSummary' => $this->familyFinancialSummary((int) $parentId, $previousSchoolYear, $selectedYear),
'familyFinancialSummary' => $this->familyFinancialSummary((int) $parentId, $previousSchoolYear, $selectedYear, $students),
'enrollmentFeeSchedule' => $this->enrollmentFeeSchedule($selectedYear),
]);
} catch (Exception $e) {
log_message('error', 'An error occurred in enrollClasses: ' . $e->getMessage());
@@ -462,6 +463,12 @@ class ParentController extends BaseController
$submittedStudentIds = array_values(array_unique(array_filter(array_map('intval', (array) $enroll), static fn (int $id): bool => $id > 0)));
$evaluations = [];
$errors = [];
$submittedEnrollmentContexts = [];
$studentInfoErrors = $this->updateEnrollmentStudentInfo($submittedStudentIds, (int) $parentId, $selectedYear);
if ($studentInfoErrors !== []) {
return redirect()->back()->withInput()->with('error', implode(' ', $studentInfoErrors));
}
foreach ($submittedStudentIds as $studentId) {
try {
@@ -524,8 +531,10 @@ class ParentController extends BaseController
->getRowArray();
$studentName = trim((string) ($studentData[$studentId]['firstname'] ?? '') . ' ' . (string) ($studentData[$studentId]['lastname'] ?? '')) ?: 'Student ID ' . $studentId;
$isReturningReEnrollment = $this->isReturningReEnrollmentStudent((int)$studentId, $selectedYear);
$submittedEnrollmentContexts[$studentId] = $isReturningReEnrollment ? 're_enrollment' : 'first_enrollment';
if ($existingEnrollment) {
$isReturningReEnrollment = $this->isReturningReEnrollmentStudent((int)$studentId, $selectedYear);
$targetEnrollmentStatus = $isReturningReEnrollment ? 'payment pending' : 'admission under review';
$targetAdmissionStatus = $isReturningReEnrollment ? 'accepted' : 'pending';
@@ -568,7 +577,6 @@ class ParentController extends BaseController
implode(' ', array_map('strval', $evaluation['rule_codes'] ?? []))
);
} else {
$isReturningReEnrollment = $this->isReturningReEnrollmentStudent((int)$studentId, $selectedYear);
$targetEnrollmentStatus = $isReturningReEnrollment ? 'payment pending' : 'admission under review';
$targetAdmissionStatus = $isReturningReEnrollment ? 'accepted' : 'pending';
@@ -616,6 +624,7 @@ class ParentController extends BaseController
if (! $this->db->transStatus()) {
return redirect()->back()->withInput()->with('error', 'A database error occurred while submitting enrollment.');
}
}
// $studentData now holds info for all students processed
@@ -710,6 +719,8 @@ class ParentController extends BaseController
return redirect()->to('/parent/enroll_classes');
} else {
// Redirect to enrollment success page if there are enrollments
$contextValues = array_values(array_unique($submittedEnrollmentContexts ?? []));
$parentData['enrollment_context'] = count($contextValues) === 1 ? $contextValues[0] : 'mixed';
Events::trigger('admissionUnderReview', $parentData, $studentData); //send notification for enrolled student
return redirect()->to('/parent/enroll_classes');
}
@@ -739,6 +750,115 @@ class ParentController extends BaseController
return $this->filterEnrollmentPayloadByColumns($payload);
}
private function updateEnrollmentStudentInfo(array $studentIds, int $parentId, string $schoolYear): array
{
$studentInfo = $this->request->getPost('student_info');
if (! is_array($studentInfo)) {
return [];
}
$submittedIds = array_fill_keys(array_map('intval', $studentIds), true);
$errors = [];
$updates = [];
foreach ($studentInfo as $studentId => $fields) {
$studentId = (int) $studentId;
if ($studentId <= 0 || ! isset($submittedIds[$studentId]) || ! is_array($fields)) {
continue;
}
$existing = $this->studentModel
->where('id', $studentId)
->where('parent_id', $parentId)
->first();
if (! is_array($existing)) {
$errors[] = 'Student ID ' . $studentId . ': student record was not found.';
continue;
}
$firstName = $this->normalizeEnrollmentStudentName((string) ($fields['firstname'] ?? ''));
$lastName = $this->normalizeEnrollmentStudentName((string) ($fields['lastname'] ?? ''));
$dob = trim((string) ($fields['dob'] ?? ''));
$gender = trim((string) ($fields['gender'] ?? ''));
$registrationGrade = trim((string) ($fields['registration_grade'] ?? ''));
$photoConsent = (string) ($fields['photo_consent'] ?? '');
$studentLabel = trim($firstName . ' ' . $lastName) ?: 'Student ID ' . $studentId;
try {
$this->validateNames($firstName);
$this->validateNames($lastName);
} catch (InvalidArgumentException $e) {
$errors[] = $studentLabel . ': ' . $e->getMessage();
continue;
}
if (! in_array($gender, ['Male', 'Female'], true)) {
$errors[] = $studentLabel . ': gender is required.';
continue;
}
if ($photoConsent !== '0' && $photoConsent !== '1') {
$errors[] = $studentLabel . ': photo consent is required.';
continue;
}
if ($registrationGrade === '' || mb_strlen($registrationGrade) > 50) {
$errors[] = $studentLabel . ': registration grade is required.';
continue;
}
$dobObj = \DateTimeImmutable::createFromFormat('!Y-m-d', $dob, new \DateTimeZone('UTC'));
$dobErrors = \DateTimeImmutable::getLastErrors();
$dobWarningCount = is_array($dobErrors) ? (int) ($dobErrors['warning_count'] ?? 0) : 0;
$dobErrorCount = is_array($dobErrors) ? (int) ($dobErrors['error_count'] ?? 0) : 0;
if ($dobObj === false || $dobWarningCount > 0 || $dobErrorCount > 0) {
$errors[] = $studentLabel . ': date of birth must use YYYY-MM-DD format.';
continue;
}
$validation = $this->validateDobAge(
$dob,
$this->registrationMinimumAgeDeadline($schoolYear),
5,
18,
$this->schoolYearAgeDeadline($schoolYear)
);
if (! $validation['isValid']) {
$errors[] = $studentLabel . ': ' . $validation['message'] . '.';
continue;
}
$updates[$studentId] = [
'firstname' => $firstName,
'lastname' => $lastName,
'dob' => $dobObj->format('Y-m-d'),
'age' => $this->calculateAgeAsOfSchoolYearStartYear($dobObj->format('Y-m-d'), $schoolYear),
'gender' => $gender,
'registration_grade' => $registrationGrade,
'photo_consent' => (int) $photoConsent,
];
}
if ($errors !== []) {
return $errors;
}
foreach ($updates as $studentId => $payload) {
if (! $this->studentModel->update($studentId, $payload)) {
$errors[] = 'Student ID ' . $studentId . ': student information could not be updated.';
}
}
return $errors;
}
private function normalizeEnrollmentStudentName(string $name): string
{
$name = trim(preg_replace('/\s+/', ' ', $name) ?? '');
return mb_convert_case($name, MB_CASE_TITLE, 'UTF-8');
}
private function exceptionReasonFromEvaluation(array $evaluation): ?string
{
if (! empty($evaluation['admin_exception'])) {
@@ -1625,16 +1745,16 @@ class ParentController extends BaseController
return 'Contact administration';
}
private function familyFinancialSummary(int $parentId, ?string $previousSchoolYear, string $selectedYear): array
private function familyFinancialSummary(int $parentId, ?string $previousSchoolYear, string $selectedYear, array $students = []): array
{
$schoolYearConfig = $this->schoolYearConfig($selectedYear);
$carryOver = $previousSchoolYear !== null ? $this->invoiceBalanceForParent($parentId, $previousSchoolYear) : 0.0;
$currentBalance = $this->invoiceBalanceForParent($parentId, $selectedYear);
$registrationFee = round((float) ($schoolYearConfig['registration_fee'] ?? 0), 2);
$tuitionDue = round((float) ($schoolYearConfig['tuition_due_at_registration'] ?? 0), 2);
$tuitionDue = $this->enrollmentTuitionDue($students);
$mandatoryFees = round((float) ($schoolYearConfig['mandatory_fees'] ?? 0), 2);
$behavior = (string) ($schoolYearConfig['carry_over_balance_behavior'] ?? 'submission_blocked_until_payment');
$amountDue = max(0.0, $carryOver) + $registrationFee + $tuitionDue + $mandatoryFees;
$amountDue = max(0.0, $carryOver) + max(0.0, $currentBalance) + $registrationFee + $tuitionDue + $mandatoryFees;
return [
'currency' => '$',
@@ -1650,6 +1770,52 @@ class ParentController extends BaseController
];
}
private function enrollmentTuitionDue(array $students): float
{
$tuitionStudents = [];
foreach ($students as $student) {
$status = strtolower(trim((string) ($student['enrollment_status'] ?? '')));
$eligibilityMessage = is_array($student['enrollment_eligibility_message'] ?? null)
? $student['enrollment_eligibility_message']
: ['blocking' => false];
if ($status !== 'not enrolled' || ! empty($eligibilityMessage['blocking'])) {
continue;
}
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
$tuitionStudents[] = [
'student_id' => (int) ($student['id'] ?? $student['student_id'] ?? 0),
'class_section_id' => (int) (
$evaluation['assigned_class_section_id']
?? $student['class_section_id']
?? 0
),
];
}
if ($tuitionStudents === []) {
return 0.0;
}
return (new FeeCalculationService())->calculateEnrollmentTuition($tuitionStudents);
}
private function enrollmentFeeSchedule(string $selectedYear): array
{
$schoolYearConfig = $this->schoolYearConfig($selectedYear);
return [
'currency' => '$',
'first_student_fee' => round((float) ($this->configModel->getConfig('first_student_fee') ?? 380), 2),
'second_student_fee' => round((float) ($this->configModel->getConfig('second_student_fee') ?? 280), 2),
'registration_fee' => round((float) ($schoolYearConfig['registration_fee'] ?? 0), 2),
'tuition_due_at_registration' => round((float) ($schoolYearConfig['tuition_due_at_registration'] ?? 0), 2),
'mandatory_fees' => round((float) ($schoolYearConfig['mandatory_fees'] ?? 0), 2),
];
}
private function financialPolicyMessage(string $behavior, string $configured): string
{
$configured = trim($configured);