02ba2fe6b6
API CI/CD / Validate (composer + pint) (push) Successful in 3m8s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Test (PHPUnit) (push) Failing after 6m5s
API CI/CD / Security audit (push) Failing after 52s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
236 lines
8.5 KiB
PHP
236 lines
8.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Parents;
|
|
|
|
use App\Models\Enrollment;
|
|
use App\Models\Refund;
|
|
use App\Models\Student;
|
|
use App\Models\StudentClass;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ParentEnrollmentService
|
|
{
|
|
public function __construct(private ParentConfigService $configService) {}
|
|
|
|
public function overview(int $parentId, ?string $schoolYear = null): array
|
|
{
|
|
$context = $this->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,
|
|
];
|
|
}
|
|
}
|