Lower numbers win when selecting the primary parent-facing blocker. */ public const BLOCKER_PRIORITY = [ 'ALREADY_ENROLLED' => 0, 'ADULT_STUDENT_PARENT_BLOCKED' => 1, 'ADULT_STUDENT_ACTION_REQUIRED' => 1, 'EXPELLED' => 2, 'WITHDRAWN' => 3, 'OUTSTANDING_BALANCE_BLOCKED' => 4, 'SIBLING_LAST_NAME_MISMATCH' => 5, 'FINANCE_APPROVAL_REQUIRED' => 6, 'DEFERRED_DECISION' => 7, 'NO_FINAL_DECISION' => 8, 'UNRECOGNIZED_DECISION' => 9, ]; public static function messageForRuleCode(string $ruleCode): string { return match ($ruleCode) { 'ADULT_STUDENT_PARENT_BLOCKED', 'ADULT_STUDENT_ACTION_REQUIRED' => self::ADULT_STUDENT_PARENT_PORTAL_MESSAGE, 'EXPELLED' => self::EXPELLED_PORTAL_MESSAGE, 'WITHDRAWN' => self::WITHDRAWN_PORTAL_MESSAGE, 'OUTSTANDING_BALANCE_BLOCKED' => self::BALANCE_PORTAL_MESSAGE, 'SIBLING_LAST_NAME_MISMATCH' => self::SIBLING_PORTAL_MESSAGE, 'FINANCE_APPROVAL_REQUIRED' => self::FINANCE_APPROVAL_PORTAL_MESSAGE, 'DEFERRED_DECISION' => self::DEFERRED_MESSAGE, 'NO_FINAL_DECISION', 'UNRECOGNIZED_DECISION' => self::MISSING_DECISION_MESSAGE, 'ALREADY_ENROLLED' => self::ALREADY_ENROLLED_MESSAGE, default => 'Enrollment cannot continue at this time. Please contact school administration.', }; } public static function alreadyEnrolledMessage(?string $enrollmentStatus = null): string { return match (strtolower(trim((string) $enrollmentStatus))) { 'payment pending' => 'This student already has an enrollment for the selected school year. Payment is still pending.', 'admission under review', 'review & decision' => 'This student already has an enrollment submitted for the selected school year and it is under review.', 'waitlist' => 'This student is already on the waitlist for the selected school year.', 'withdraw under review' => 'This student is already enrolled and has a withdrawal request under review.', 'refund pending' => 'This student already has an enrollment for the selected school year. A refund is pending.', 'enrolled' => self::ALREADY_ENROLLED_MESSAGE, default => self::ALREADY_SUBMITTED_MESSAGE, }; } public static function alreadyEnrolledTitle(?string $enrollmentStatus = null): string { return match (strtolower(trim((string) $enrollmentStatus))) { 'payment pending', 'admission under review', 'review & decision' => 'Enrollment Already Submitted', 'waitlist' => 'Already on Waitlist', 'withdraw under review' => 'Withdrawal Under Review', 'refund pending' => 'Refund Pending', default => 'Already Enrolled', }; } /** * @param array $student */ public static function isMarkedWithdrawn(array $student): bool { if ((int) ($student['is_withdrawn'] ?? 0) === 1) { return true; } $status = strtolower(trim((string) ($student['enrollment_status'] ?? ''))); return $status === 'withdrawn' || $status === 'withdrawn student'; } /** * @param list $blockers */ public static function selectPrimaryBlocker(array $blockers): ?string { $blockers = array_values(array_unique(array_filter(array_map('strval', $blockers)))); if ($blockers === []) { return null; } usort($blockers, static function (string $a, string $b): int { $priorityA = self::BLOCKER_PRIORITY[$a] ?? 999; $priorityB = self::BLOCKER_PRIORITY[$b] ?? 999; return $priorityA <=> $priorityB ?: strcmp($a, $b); }); return $blockers[0]; } public static function ageOnSeptemberFirst(?string $dob, string $targetSchoolYear): ?int { $dob = trim((string) $dob); if ($dob === '' || ! preg_match('/^(\d{4})/', trim($targetSchoolYear), $matches)) { return null; } try { $birthDate = \DateTimeImmutable::createFromFormat('!Y-m-d', $dob); $errors = \DateTimeImmutable::getLastErrors(); if ( $birthDate === false || (is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0)) ) { return null; } $referenceDate = new \DateTimeImmutable($matches[1] . '-09-01'); } catch (\Throwable) { return null; } return $birthDate > $referenceDate ? null : $birthDate->diff($referenceDate)->y; } public static function parentDecisionMessage( array $student, ?array $decisionRow, string $targetSchoolYear, ?string $fallMakeupExamOn = null ): array { $name = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')); $name = $name !== '' ? $name : 'The student'; $decision = DeliberationDecision::normalize($decisionRow['decision'] ?? null); $source = strtolower(trim((string) ($decisionRow['source'] ?? ''))); if ($decision === DeliberationDecision::EXPELLED) { return self::message($name . ': ' . self::EXPELLED_MESSAGE, true, 'danger'); } $age = self::ageOnSeptemberFirst($student['dob'] ?? null, $targetSchoolYear); if ($age !== null && $age >= self::ADULT_STUDENT_MIN_AGE) { return self::message(str_replace('The student', $name, self::ADULT_STUDENT_MESSAGE), true, 'danger'); } if ($decision === DeliberationDecision::WITHDRAWN || self::isMarkedWithdrawn($student)) { return self::message($name . ': ' . self::WITHDRAWN_MESSAGE, true, 'warning'); } if ($decision === DeliberationDecision::DEFERRED_DECISION) { return self::message($name . ': ' . self::DEFERRED_MESSAGE, true, 'warning'); } if ($decision === null && $source === 'pending') { return self::message($name . ': ' . self::MISSING_DECISION_MESSAGE, true, 'warning'); } if ($decision === DeliberationDecision::MAKE_UP_EXAM) { $dateText = $fallMakeupExamOn !== null ? ' on ' . local_date($fallMakeupExamOn, 'm-d-Y') : ''; return self::message( $name . ' has a make-up exam decision. Enrollment is allowed, but the student will initially remain in the same grade as the closed school year until the fall make-up exam' . $dateText . ' is passed and administration confirms promotion.', false, 'warning' ); } if ($decision === DeliberationDecision::REPEAT_CLASS) { $previousClass = trim((string) ($decisionRow['class_section_name'] ?? '')); $previousClass = $previousClass !== '' ? $previousClass : 'the same class'; return self::message($name . ' has a repeat class decision and is accepted only in ' . $previousClass . '.', false, 'info'); } if ($decision === DeliberationDecision::PASSED || ($decision === null && $source !== 'pending' && trim((string) ($decisionRow['decision'] ?? '')) === '')) { return self::message('', false, 'info'); } return self::message( $name . ' does not have a final academic decision that permits online re-enrollment. Please contact the administration.', true, 'warning' ); } private static function message(string $message, bool $blocking, string $level): array { return [ 'message' => $message, 'blocking' => $blocking, 'level' => $level, ]; } }