diff --git a/app/Controllers/Administrator/SchoolYearClosingController.php b/app/Controllers/Administrator/SchoolYearClosingController.php index 699c415..914a13e 100644 --- a/app/Controllers/Administrator/SchoolYearClosingController.php +++ b/app/Controllers/Administrator/SchoolYearClosingController.php @@ -62,7 +62,7 @@ class SchoolYearClosingController extends BaseController } service('schoolYearClosing')->start($id, $targetId, $this->userId()); - return redirect()->to('/administrator/school-years/' . $id . '/closing/preview')->with('success', 'End-year process started.'); + return redirect()->to('/administrator/school-years/' . $id . '/closing/preview?close_flow=1')->with('success', 'End-year process started.'); } catch (Throwable $e) { return redirect()->back()->with('error', $e->getMessage()); } @@ -72,7 +72,7 @@ class SchoolYearClosingController extends BaseController { try { service('schoolYearClosing')->execute($id, $this->userId()); - return redirect()->to('/administrator/school-years/' . $id . '/closing/preview')->with('success', 'Carry-forward confirmed.'); + return redirect()->to('/administrator/school-years/' . $id . '/closing/preview?close_flow=1')->with('success', 'Carry-forward confirmed.'); } catch (Throwable $e) { return redirect()->back()->with('error', $e->getMessage()); } @@ -82,7 +82,7 @@ class SchoolYearClosingController extends BaseController { try { service('schoolYearClosing')->complete($id, $this->userId()); - return redirect()->to('/administrator/school-years')->with('success', 'School year ended and closed.'); + return redirect()->to('/administrator/school-years/' . $id . '/closing/preview?close_flow=1')->with('success', 'School year ended and closed.'); } catch (Throwable $e) { return redirect()->back()->with('error', $e->getMessage()); } diff --git a/app/Controllers/ParentReportCardController.php b/app/Controllers/ParentReportCardController.php index c1388f9..8cae5c7 100644 --- a/app/Controllers/ParentReportCardController.php +++ b/app/Controllers/ParentReportCardController.php @@ -52,6 +52,7 @@ class ParentReportCardController extends BaseController $studentIds = array_values(array_filter(array_map(static fn ($s) => (int) ($s['id'] ?? 0), $students))); $ackMap = []; + $reportAvailableMap = $this->reportAvailabilityMap($studentIds, $schoolYear, $semester); if (! empty($studentIds)) { $rows = $this->ackModel ->where('parent_id', $parentId) @@ -67,6 +68,7 @@ class ParentReportCardController extends BaseController return view('parent/report_cards', [ 'students' => $students, 'ackMap' => $ackMap, + 'reportAvailableMap' => $reportAvailableMap, 'schoolYear' => $schoolYear, 'semester' => $semester, 'isEditable' => ! $schoolYearContext->isReadonly(), @@ -89,6 +91,11 @@ class ParentReportCardController extends BaseController $schoolYear = trim($schoolYearContext->yearName()); $semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? '')); + if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) { + return redirect()->to(site_url('parent/report-cards')) + ->with('error', 'No report card exists for the selected school year and semester.'); + } + if (! $schoolYearContext->isReadonly()) { $this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [ 'viewed_at' => date('Y-m-d H:i:s'), @@ -136,6 +143,12 @@ class ParentReportCardController extends BaseController $schoolYear = trim($schoolYearContext->yearName()); $semester = trim((string) ($this->configModel->getConfig('semester') ?? '')); + + if (! $this->reportExists((int) $studentId, $schoolYear, $semester)) { + return redirect()->to(site_url('parent/report-cards')) + ->with('error', 'No report card exists for the selected school year and semester.'); + } + $now = date('Y-m-d H:i:s'); $this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [ 'viewed_at' => $now, @@ -173,6 +186,80 @@ class ParentReportCardController extends BaseController return $parentId ?: null; } + protected function reportAvailabilityMap(array $studentIds, string $schoolYear, string $semester): array + { + $studentIds = array_values(array_filter(array_map('intval', $studentIds), static fn ($id) => $id > 0)); + if (empty($studentIds) || $schoolYear === '') { + return []; + } + + $builder = $this->db->table('semester_scores') + ->select('student_id') + ->whereIn('student_id', $studentIds) + ->where('school_year', $schoolYear); + + $semesterVariants = $this->semesterVariants($semester); + if (! empty($semesterVariants)) { + $builder->whereIn('semester', $semesterVariants); + } + + $rows = $builder + ->groupBy('student_id') + ->get() + ->getResultArray(); + + $map = []; + foreach ($rows as $row) { + $sid = (int) ($row['student_id'] ?? 0); + if ($sid > 0) { + $map[$sid] = true; + } + } + + return $map; + } + + protected function reportExists(int $studentId, string $schoolYear, string $semester): bool + { + if ($studentId <= 0 || $schoolYear === '') { + return false; + } + + $builder = $this->db->table('semester_scores') + ->select('id') + ->where('student_id', $studentId) + ->where('school_year', $schoolYear) + ->limit(1); + + $semesterVariants = $this->semesterVariants($semester); + if (! empty($semesterVariants)) { + $builder->whereIn('semester', $semesterVariants); + } + + return (bool) $builder->get()->getRowArray(); + } + + protected function semesterVariants(string $semester): array + { + $raw = trim($semester); + if ($raw === '') { + return []; + } + + $normalized = strtolower($raw); + if ($normalized === 'fall' || $normalized === 'first' || str_contains($normalized, 'fall') || str_contains($normalized, '1')) { + $values = ['Fall', 'fall', 'First', 'first', 'Semester 1', 'semester 1', 'Semester1', 'semester1', 'Sem 1', 'sem 1', '1', '01', 'S1', 's1']; + } elseif ($normalized === 'spring' || $normalized === 'second' || str_contains($normalized, 'spring') || str_contains($normalized, '2')) { + $values = ['Spring', 'spring', 'Second', 'second', 'Semester 2', 'semester 2', 'Semester2', 'semester2', 'Sem 2', 'sem 2', '2', '02', 'S2', 's2']; + } else { + $values = [$raw]; + } + + $values[] = $raw; + + return array_values(array_unique($values)); + } + protected function touchAcknowledgement( int $parentId, int $studentId, diff --git a/app/Controllers/View/ParentController.php b/app/Controllers/View/ParentController.php index 820b83f..dc35931 100644 --- a/app/Controllers/View/ParentController.php +++ b/app/Controllers/View/ParentController.php @@ -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); diff --git a/app/Controllers/View/ParentFinancialAidController.php b/app/Controllers/View/ParentFinancialAidController.php index 02ed22a..e3bb09b 100644 --- a/app/Controllers/View/ParentFinancialAidController.php +++ b/app/Controllers/View/ParentFinancialAidController.php @@ -35,6 +35,7 @@ class ParentFinancialAidController extends BaseController 'students' => $students, 'requests' => $requests, 'openRequest' => $model->openRequestForParent($parentId, $schoolYear), + 'existingRequest' => $model->requestForParentYear($parentId, $schoolYear), ]); } @@ -47,8 +48,8 @@ class ParentFinancialAidController extends BaseController $schoolYear = (string) ((new ConfigurationModel())->getConfig('school_year') ?? ''); $model = new FinancialAidRequestModel(); - if ($model->openRequestForParent($parentId, $schoolYear) !== null) { - return redirect()->back()->with('error', 'You already have an open financial aid request for this school year.'); + if ($model->requestForParentYear($parentId, $schoolYear) !== null) { + return redirect()->back()->with('error', 'You can submit only one financial aid application per school year.'); } $studentIds = array_values(array_unique(array_filter(array_map('intval', (array) $this->request->getPost('student_ids'))))); diff --git a/app/Controllers/View/ReportCardsController.php b/app/Controllers/View/ReportCardsController.php index 953788a..06bf1a7 100644 --- a/app/Controllers/View/ReportCardsController.php +++ b/app/Controllers/View/ReportCardsController.php @@ -1224,12 +1224,6 @@ $scoresEndY = $pdf->GetY(); public function generateSingleReport($studentId) { - // Prevent Debug Toolbar from corrupting PDF - if (ENVIRONMENT !== 'production') { - // Turn off toolbar manually by unsetting the collector - unset(service('toolbar')->collectors['CodeIgniter\Debug\Toolbar\Collectors\Timers']); - } - $download = $this->request->getGet('download') === '1'; $schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear; $semester = $this->request->getGet('semester') ?? $this->semester; @@ -1237,7 +1231,7 @@ $scoresEndY = $pdf->GetY(); $data = $this->prepareStudentReportData((int)$studentId, (string)$schoolYear, (string)$semester); if (!$data) { - return "Student not found or missing scores."; + return $this->missingReportResponse((int)$studentId, (string)$schoolYear, (string)$semester); } $data['report_date'] = $reportDate['iso']; $data['report_date_display'] = $reportDate['display']; @@ -1264,12 +1258,6 @@ $scoresEndY = $pdf->GetY(); public function generateClassReport($classSectionId) { - // Disable toolbar output to prevent PDF corruption - if (ENVIRONMENT !== 'production') { - unset(service('toolbar')->collectors['CodeIgniter\Debug\Toolbar\Collectors\Timers']); - } - - $download = $this->request->getGet('download') === '1'; $schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear; $semester = $this->request->getGet('semester') ?? $this->semester; @@ -1290,10 +1278,11 @@ $scoresEndY = $pdf->GetY(); ->findAll(); if (!$scores) { - return "No students found for this class section."; + return $this->missingReportResponse(null, (string)$schoolYear, (string)$semester, (int)$classSectionId); } $pdf = new \FPDF('P', 'mm', 'Letter'); + $generatedCount = 0; foreach ($scores as $score) { $studentId = $score['student_id']; @@ -1304,9 +1293,14 @@ $scoresEndY = $pdf->GetY(); $data['report_date'] = $reportDate['iso']; $data['report_date_display'] = $reportDate['display']; $this->formatReportPDF($pdf, $data); + $generatedCount++; } } + if ($generatedCount === 0) { + return $this->missingReportResponse(null, (string)$schoolYear, (string)$semester, (int)$classSectionId); + } + $filename = 'ClassReport_Section_' . $classSectionId . '.pdf'; // Clean any existing output buffer @@ -1324,6 +1318,20 @@ $scoresEndY = $pdf->GetY(); ->setBody($pdfContent); } + protected function missingReportResponse(?int $studentId, string $schoolYear, string $semester, ?int $classSectionId = null) + { + $target = $studentId !== null + ? 'student #' . $studentId + : 'class section #' . (int)$classSectionId; + $period = trim($schoolYear . ($semester !== '' ? ' ' . $semester : '')); + $message = 'No report card exists for ' . $target . ($period !== '' ? ' for ' . $period : '') . '.'; + + return $this->response + ->setStatusCode(404) + ->setHeader('Content-Type', 'text/plain; charset=UTF-8') + ->setBody($message . "\nPlease select a school year and semester that has saved scores before viewing or signing the report card."); + } + protected function normalizeSemester(?string $input): string { diff --git a/app/Database/Migrations/2026-08-15-000002_AddUniqueFinancialAidParentYear.php b/app/Database/Migrations/2026-08-15-000002_AddUniqueFinancialAidParentYear.php new file mode 100644 index 0000000..3e5989c --- /dev/null +++ b/app/Database/Migrations/2026-08-15-000002_AddUniqueFinancialAidParentYear.php @@ -0,0 +1,61 @@ +db->tableExists($this->table) || $this->indexExists()) { + return; + } + + $duplicate = $this->db->table($this->table) + ->select('parent_id, school_year, COUNT(*) AS total', false) + ->groupBy(['parent_id', 'school_year']) + ->having('total >', 1) + ->limit(1) + ->get() + ->getRowArray(); + + if ($duplicate !== null) { + log_message('warning', 'Skipped unique financial aid parent/year index because duplicate requests already exist.'); + return; + } + + $this->db->query(sprintf( + 'CREATE UNIQUE INDEX %s ON %s (parent_id, school_year)', + $this->db->escapeIdentifiers($this->index), + $this->db->escapeIdentifiers($this->table) + )); + } + + public function down() + { + if (! $this->db->tableExists($this->table) || ! $this->indexExists()) { + return; + } + + $this->db->query(sprintf( + 'DROP INDEX %s ON %s', + $this->db->escapeIdentifiers($this->index), + $this->db->escapeIdentifiers($this->table) + )); + } + + private function indexExists(): bool + { + foreach ($this->db->getIndexData($this->table) as $index) { + if (($index->name ?? '') === $this->index) { + return true; + } + } + + return false; + } +} diff --git a/app/Listeners/SchoolEventListener.php b/app/Listeners/SchoolEventListener.php index 3d10d3d..b911649 100644 --- a/app/Listeners/SchoolEventListener.php +++ b/app/Listeners/SchoolEventListener.php @@ -78,6 +78,20 @@ class SchoolEventListener public function handleAdmissionUnderReview(array $data, array $studentdata) { $parentName = trim(($data['firstname'] ?? '') . ' ' . ($data['lastname'] ?? '')); + $enrollmentContext = (string) ($data['enrollment_context'] ?? 'first_enrollment'); + $isReEnrollment = $enrollmentContext === 're_enrollment'; + $isMixedEnrollment = $enrollmentContext === 'mixed'; + $emailSubject = $isReEnrollment + ? 'Re-Enrollment Submission Received' + : ($isMixedEnrollment ? 'Enrollment Submission Received' : 'Admission Application Under Review'); + $notificationTitle = $isReEnrollment + ? 'Re-Enrollment Received' + : ($isMixedEnrollment ? 'Enrollment Submission Received' : 'Admission Under Review'); + $notificationMessage = $isReEnrollment + ? 'Your re-enrollment submission has been received. Please check your email for details.' + : ($isMixedEnrollment + ? 'Your enrollment submission has been received. Please check your email for details.' + : 'Your admission application is under review. We will notify you once it is approved.'); $studentNames = []; foreach ($studentdata as $student) { @@ -89,15 +103,16 @@ class SchoolEventListener $emailData = [ 'parentName' => $parentName, 'studentName' => $studentNameData, + 'enrollmentContext' => $enrollmentContext, 'portalLink' => base_url('/login'), - 'title' => 'Admission Application Under Review', // used by layout
Dear ' . esc($family['name']) . ',
' - . 'We are pleased to welcome your family to the registration process for the ' . esc($schoolYearName) . ' school year.
' - . 'Registration opens on ' . esc($opens) . ' and closes on ' . esc($deadline) . '.
' - . 'Please review the information below for each of your children, including the deliberation decision, registration eligibility, expected academic placement, and any required action.
' - . implode('', $students) + . 'Registration for the ' . esc($schoolYearName) . ' school year is now open. Please review the student summary below and complete the portal steps before ' . esc($deadline) . '.
' + . $this->studentsTable($studentRows) . $financial - . 'To complete registration for each eligible student:
' - . 'Re-enrollment opens on ' . esc($opens) . ' and closes on ' . esc($deadline) . '. Complete re-enrollment for each eligible student before the deadline to secure their enrollment for the upcoming school year.
' + . 'Registration portal: ' . esc(site_url('parent/enroll_classes')) . '
' . 'For assistance, please contact the school administration.
' . 'Sincerely,
Al Rahma Sunday School
School Administration
No eligible linked students were found for this email.
'; + } + + return 'Deliberation decision: ' . esc($decision ?: 'Pending') . '
'
- . 'Registration status: ' . esc($status) . '
'
- . 'Expected placement: ' . esc($placement) . '
' . esc($message) . '
' - . 'Required action: ' . esc($requiredAction) . '
'; + return 'Dear = $parentName ?>,
- Thank you for submitting the enrollment application for the following = count($studentName) ?> students: + + Thank you for submitting re-enrollment for the following = count($studentName) ?> students: + + Thank you for submitting enrollment updates for the following = count($studentName) ?> students: + + Thank you for submitting the enrollment application for the following = count($studentName) ?> students: +
- Thank you for submitting the enrollment application for = $studentName ?>. + + Thank you for submitting re-enrollment for = $studentName ?>. + + Thank you for submitting the enrollment update for = $studentName ?>. + + Thank you for submitting the enrollment application for = $studentName ?>. +
+ ++ Your re-enrollment = $applicationWord ?> = $studentCount === 1 ? 'has' : 'have' ?> been received. The = $studentCount === 1 ? 'student is' : 'students are' ?> approved to continue, and the next step is to complete any required payment or follow-up items shown in the parent portal. +
++ You will be notified if any additional information is needed. +
+ ++ Your enrollment = $applicationWord ?> = $studentCount === 1 ? 'is' : 'are' ?> being processed. New student applications may require admissions review, while returning student re-enrollment may move directly to the next required portal step. +
++ You will be notified immediately once an update is available. +
+ ++ Your application= is_array($studentName) ? 's are' : ' is' ?> currently under review by our admissions committee. This process typically takes 1–2 business days. +
++ You will be notified immediately once a decision has been made. +
+- Your application= is_array($studentName) ? 's are' : ' is' ?> currently under review by our admissions committee. This process typically takes 1–2 business days. -
-- You will be notified immediately once a decision has been made. -
-- You can check your application status at any time through the parent portal. + You can check your = $isReEnrollment ? 're-enrollment' : 'application' ?> status at any time through the parent portal.
@@ -42,4 +79,4 @@
Best regards,
Admissions Team