From 717baaea790eedd01217cab6bd0c4c06de381973 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 15 Aug 2026 21:20:51 -0400 Subject: [PATCH] Fix last-name exception carry-forward and parent action label --- app/Controllers/View/ParentController.php | 6 ++-- app/Services/EnrollmentTransitionService.php | 35 ++++++++++++++++++- .../EnrollmentTransitionServiceTest.php | 6 ++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/Controllers/View/ParentController.php b/app/Controllers/View/ParentController.php index dc35931..08d4458 100644 --- a/app/Controllers/View/ParentController.php +++ b/app/Controllers/View/ParentController.php @@ -1669,7 +1669,7 @@ class ParentController extends BaseController private function requiredActionLabel(?array $evaluation): string { if ($evaluation === null) { - return 'Contact administration'; + return 'Complete re-enrollment before the registration deadline.'; } $state = $this->parentEnrollmentStateFromEvaluation($evaluation, null); @@ -1678,7 +1678,7 @@ class ParentController extends BaseController 'Eligible with follow-up' => 'Complete re-enrollment and follow the listed next step.', 'Already submitted' => 'Already submitted', 'Action needed' => 'Action needed: pay the previous-year balance or contact administration.', - 'Under review' => 'Under review. Contact the school administration.', + 'Under review' => 'Contact the school administration.', default => 'Contact administration', }; } @@ -1716,7 +1716,7 @@ class ParentController extends BaseController return 'Already submitted'; } - if (! empty($evaluation['can_enroll']) || $decision === 'EXCEPTION_ELIGIBLE' || $decision === 'ELIGIBLE') { + if (! empty($evaluation['can_enroll']) || ! empty($evaluation['parent_enrollment_allowed']) || $decision === 'EXCEPTION_ELIGIBLE' || $decision === 'ELIGIBLE') { if ($decision === 'ELIGIBLE_WITH_WARNING' || ($evaluation['warning_rule_codes'] ?? []) !== []) { return 'Eligible with follow-up'; } diff --git a/app/Services/EnrollmentTransitionService.php b/app/Services/EnrollmentTransitionService.php index 62491a5..615563b 100644 --- a/app/Services/EnrollmentTransitionService.php +++ b/app/Services/EnrollmentTransitionService.php @@ -1445,6 +1445,20 @@ final class EnrollmentTransitionService return; } + $approvedStudentIds = $this->exceptionFamilyStudentIds($prior); + $currentStudentIds = $this->linkedStudentIds($parentId); + sort($currentStudentIds); + if ($approvedStudentIds !== [] && $approvedStudentIds !== $currentStudentIds) { + $evaluation['last_name_exception_carry_forward'] = [ + 'eligible' => false, + 'prior_exception_id' => (int) ($prior['id'] ?? 0), + 'prior_school_year' => (string) ($prior['school_year'] ?? ''), + 'approved_student_ids' => $approvedStudentIds, + 'reason' => 'NEW_STUDENT_ADDED', + ]; + return; + } + $this->removeRuleCode($evaluation, 'SIBLING_LAST_NAME_MISMATCH'); $this->addRuleCode($evaluation, 'LAST_NAME_EXCEPTION_CARRIED_FORWARD', 'warning'); $evaluation['warnings'] = array_values(array_unique(array_merge( @@ -1456,7 +1470,7 @@ final class EnrollmentTransitionService 'eligible' => true, 'prior_exception_id' => (int) ($prior['id'] ?? 0), 'prior_school_year' => (string) ($prior['school_year'] ?? ''), - 'approved_student_ids' => [(int) ($prior['student_id'] ?? $studentId)], + 'approved_student_ids' => $approvedStudentIds !== [] ? $approvedStudentIds : [(int) ($prior['student_id'] ?? $studentId)], ]; } @@ -1522,6 +1536,25 @@ final class EnrollmentTransitionService return in_array('SIBLING_LAST_NAME_MISMATCH', $codes, true); } + /** + * @return list + */ + private function exceptionFamilyStudentIds(array $exception): array + { + $ids = json_decode((string) ($exception['family_student_ids_json'] ?? ''), true); + if (! is_array($ids)) { + return []; + } + + $ids = array_values(array_unique(array_filter(array_map( + static fn ($id): int => (int) $id, + $ids + ), static fn (int $id): bool => $id > 0))); + sort($ids); + + return $ids; + } + private function linkedStudentsForParent(int $parentId): array { if ($parentId <= 0 || ! $this->db->tableExists('students')) { diff --git a/tests/app/Services/EnrollmentTransitionServiceTest.php b/tests/app/Services/EnrollmentTransitionServiceTest.php index e56ee3c..bfe4c8d 100644 --- a/tests/app/Services/EnrollmentTransitionServiceTest.php +++ b/tests/app/Services/EnrollmentTransitionServiceTest.php @@ -260,7 +260,7 @@ final class EnrollmentTransitionServiceTest extends TestCase 'warnings' => [], ]; - $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']); + $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, 1, '2025-2026', '2026-2027']); $this->assertNotContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']); $this->assertContains('LAST_NAME_EXCEPTION_CARRIED_FORWARD', $evaluation['warning_rule_codes']); @@ -327,7 +327,7 @@ final class EnrollmentTransitionServiceTest extends TestCase 'blockers' => ['Linked siblings have different last names. Please contact administration to review the family record.'], ]; - $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']); + $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, 1, '2025-2026', '2026-2027']); $this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']); $this->assertFalse($evaluation['last_name_exception_carry_forward']['eligible']); @@ -341,7 +341,7 @@ final class EnrollmentTransitionServiceTest extends TestCase 'blocking_rule_codes' => ['SIBLING_LAST_NAME_MISMATCH', 'OUTSTANDING_BALANCE_BLOCKED'], ]; - $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, '2025-2026', '2026-2027']); + $this->invoke($service, 'applyCarriedForwardLastNameException', [&$evaluation, 9, 1, '2025-2026', '2026-2027']); $this->assertContains('SIBLING_LAST_NAME_MISMATCH', $evaluation['blocking_rule_codes']); $this->assertArrayNotHasKey('last_name_exception_carry_forward', $evaluation);