configService->context(); $selectedYear = $schoolYear ?: $context['school_year']; $students = Student::query() ->where('parent_id', $parentId) ->orderBy('lastname') ->orderBy('firstname') ->get() ->map(function ($student) use ($selectedYear) { $classSections = StudentClass::getClassSectionsByStudentId($student->id, $selectedYear, true); $student->class_section = ! empty($classSections) ? implode(', ', $classSections) : 'Class not Assigned'; $isArabicClass = ! empty($classSections) && array_reduce( $classSections, static fn ($carry, $name) => $carry || (is_string($name) && stripos($name, 'arabic') === 0), false ); $enrollment = Enrollment::query() ->select('enrollment_status', 'admission_status') ->where('student_id', $student->id) ->where('school_year', $selectedYear) ->first(); $statusMap = [ 'admission under review' => 'admission under review', 'payment pending' => 'payment pending', 'enrolled' => 'enrolled', 'withdraw under review' => 'withdraw under review', 'refund pending' => 'refund pending', 'withdrawn' => 'withdrawn', 'denied' => 'denied', 'waitlist' => 'waitlist', ]; if ($enrollment && isset($enrollment->admission_status)) { $student->admission_status = $enrollment->admission_status; if ($enrollment->admission_status === 'denied') { $student->enrollment_status = 'denied'; } else { $student->enrollment_status = $statusMap[$enrollment->enrollment_status] ?? 'not enrolled'; } } else { $student->admission_status = null; $student->enrollment_status = 'not enrolled'; } if ($student->enrollment_status === 'not enrolled' && $isArabicClass) { $student->enrollment_status = 'enrolled'; } $student->disable_enroll = in_array( $student->enrollment_status, ['admission under review', 'payment pending', 'enrolled', 'withdraw under review', 'denied'], true ); return $student->toArray(); }) ->all(); $schoolYears = DB::table('enrollments') ->select('school_year') ->distinct() ->orderBy('school_year', 'desc') ->get() ->map(fn ($row) => (array) $row) ->all(); if (empty($schoolYears)) { $schoolYears[] = ['school_year' => $selectedYear]; } return [ 'students' => $students, 'schoolYears' => $schoolYears, 'selectedYear' => $selectedYear, 'withdrawalDeadline' => $context['refund_deadline'], 'lastDayOfRegistration' => $context['enrollment_deadline'], 'schoolStartDate' => $context['fall_semester_start'], ]; } public function updateEnrollment(int $parentId, array $enrollIds, array $withdrawIds): array { $context = $this->configService->context(); $schoolYear = $context['school_year']; $semester = $context['semester']; // Authorize every submitted student id against this parent up-front to // prevent IDOR — a parent must not be able to enroll or withdraw // another family's students by passing arbitrary ids. $requestedIds = array_values(array_unique(array_map( 'intval', array_merge($enrollIds, $withdrawIds) ))); $requestedIds = array_values(array_filter($requestedIds, static fn ($id) => $id > 0)); $ownedIds = $requestedIds === [] ? [] : Student::query() ->where('parent_id', $parentId) ->whereIn('id', $requestedIds) ->pluck('id') ->map(fn ($id) => (int) $id) ->all(); $ownedSet = array_flip($ownedIds); $enrolled = []; $withdrawn = []; foreach ($enrollIds as $studentId) { $studentId = (int) $studentId; if (! isset($ownedSet[$studentId])) { continue; } $existing = Enrollment::query() ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->where('semester', $semester) ->first(); if ($existing) { if ((int) $existing->is_withdrawn === 1) { $existing->update([ 'is_withdrawn' => 0, 'withdrawal_date' => null, 'enrollment_status' => 'payment pending', 'updated_at' => now(), ]); } } else { Enrollment::query()->create([ 'student_id' => $studentId, 'parent_id' => $parentId, 'school_year' => $schoolYear, 'semester' => $semester, 'enrollment_date' => now()->toDateString(), 'is_withdrawn' => 0, 'enrollment_status' => 'admission under review', 'admission_status' => 'pending', 'created_at' => now(), ]); } $enrolled[] = $studentId; } foreach ($withdrawIds as $studentId) { $studentId = (int) $studentId; if (! isset($ownedSet[$studentId])) { continue; } $enrollment = Enrollment::query() ->where('student_id', $studentId) ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->where('semester', $semester) ->where('is_withdrawn', 0) ->first(); if (! $enrollment) { continue; } $enrollment->update([ 'withdrawal_date' => now()->toDateString(), 'enrollment_status' => 'withdraw under review', 'updated_at' => now(), ]); $invoice = DB::table('invoices') ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->orderByDesc('created_at') ->first(); if ($invoice) { $refund = Refund::query() ->where('parent_id', $parentId) ->where('invoice_id', $invoice->id) ->where('school_year', $schoolYear) ->first(); if ($refund) { $refund->update([ 'reason' => 'Withdrawal under review for student ID '.$studentId, 'note' => null, 'updated_by' => $parentId, ]); } else { Refund::query()->create([ 'parent_id' => $parentId, 'invoice_id' => $invoice->id, 'requested_at' => now(), 'school_year' => $schoolYear, 'status' => Refund::STATUS_PENDING, 'reason' => 'Withdrawal under review for student ID '.$studentId, 'request' => 'new', 'semester' => $semester, 'refund_amount' => 0.0, 'refund_paid_amount' => 0.0, ]); } } $withdrawn[] = $studentId; } return [ 'enrolled' => $enrolled, 'withdrawn' => $withdrawn, ]; } }