Files
alrahma_sunday_school_api/app/Services/Parents/ParentRegistrationService.php
T
root 940afe9319
API CI/CD / Validate (composer + pint) (push) Successful in 2m7s
API CI/CD / Test (PHPUnit) (push) Failing after 2m23s
API CI/CD / Build frontend assets (push) Successful in 2m18s
API CI/CD / Security audit (push) Successful in 31s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
fix unit tests as well as missing code
2026-06-25 14:26:32 -04:00

256 lines
9.5 KiB
PHP

<?php
namespace App\Services\Parents;
use App\Models\EmergencyContact;
use App\Models\Student;
use App\Models\StudentAllergy;
use App\Models\StudentMedicalCondition;
use App\Models\User;
use App\Services\SchoolIdService;
use Illuminate\Support\Facades\DB;
class ParentRegistrationService
{
public function __construct(
private ParentConfigService $configService,
private SchoolIdService $schoolIdService
) {}
public function overview(int $parentId): array
{
$context = $this->configService->context();
$parent = User::query()->find($parentId);
$kids = Student::query()->where('parent_id', $parentId)->get();
$kidIds = $kids->pluck('id')->all();
$allergies = StudentAllergy::query()
->whereIn('student_id', $kidIds)
->get()
->groupBy('student_id');
$conditions = StudentMedicalCondition::query()
->whereIn('student_id', $kidIds)
->get()
->groupBy('student_id');
$kids = $kids->map(function ($kid) use ($allergies, $conditions) {
$kid->allergies = ($allergies[$kid->id] ?? collect())->pluck('allergy')->all();
$kid->medical_conditions = ($conditions[$kid->id] ?? collect())->pluck('condition_name')->all();
return $kid->toArray();
})->all();
$emergencies = EmergencyContact::query()->where('parent_id', $parentId)->get()->toArray();
return [
'parent' => $parent ? $parent->toArray() : null,
'existingKids' => $kids,
'emergencies' => $emergencies,
'maxChilds' => $context['max_kids'],
'maxEmergency' => $context['max_emergency'],
'lastDayOfRegistration' => $context['enrollment_deadline'],
'registrationAgeDeadline' => $context['date_age_reference'],
];
}
public function register(int $parentId, array $students, array $emergencyContacts): array
{
$context = $this->configService->context();
$schoolYear = $context['school_year'];
$semester = $context['semester'];
$ageReference = $context['date_age_reference'] ?: now()->toDateString();
$existingKidsCount = Student::query()->where('parent_id', $parentId)->count();
$existingECCount = EmergencyContact::query()->where('parent_id', $parentId)->count();
if ($existingKidsCount + count($students) > $context['max_kids']) {
throw new \RuntimeException('Student limit exceeded.');
}
if ($existingECCount + count($emergencyContacts) > $context['max_emergency']) {
throw new \RuntimeException('Emergency contact limit exceeded.');
}
$createdStudentIds = [];
$skippedStudents = [];
DB::transaction(function () use (
$parentId,
$students,
$emergencyContacts,
$schoolYear,
$semester,
$ageReference,
&$createdStudentIds,
&$skippedStudents
) {
foreach ($students as $student) {
// Duplicate check is scoped to THIS parent: one family's
// legitimately registered child must not block another family
// from registering a child with the same name and DOB.
$exists = Student::query()
->where('parent_id', $parentId)
->where('school_year', $schoolYear)
->where('dob', $student['dob'])
->whereRaw('LOWER(TRIM(firstname)) = ?', [strtolower(trim((string) $student['firstname']))])
->whereRaw('LOWER(TRIM(lastname)) = ?', [strtolower(trim((string) $student['lastname']))])
->first();
if ($exists) {
$skippedStudents[] = [
'firstname' => $student['firstname'],
'lastname' => $student['lastname'],
'dob' => $student['dob'],
'reason' => 'already_registered',
];
continue;
}
$row = Student::query()->create([
'firstname' => $student['firstname'],
'lastname' => $student['lastname'],
'dob' => $student['dob'],
'age' => $this->calculateAge($student['dob'], $ageReference),
'gender' => $student['gender'],
'registration_grade' => $student['registration_grade'] ?? null,
'photo_consent' => ! empty($student['photo_consent']) ? 1 : 0,
'parent_id' => $parentId,
'year_of_registration' => (string) date('Y'),
'school_year' => $schoolYear,
'semester' => $semester,
'is_new' => isset($student['is_new']) ? (int) (bool) $student['is_new'] : null,
'registration_date' => now(),
'tuition_paid' => 0,
'school_id' => $this->schoolIdService->generateStudentSchoolId(),
]);
$createdStudentIds[] = (int) $row->id;
foreach (($student['medical_conditions'] ?? []) as $condition) {
$condition = trim((string) $condition);
if ($condition === '') {
continue;
}
StudentMedicalCondition::query()->create([
'student_id' => $row->id,
'condition_name' => $condition,
]);
}
foreach (($student['allergies'] ?? []) as $allergy) {
$allergy = trim((string) $allergy);
if ($allergy === '') {
continue;
}
StudentAllergy::query()->create([
'student_id' => $row->id,
'allergy' => $allergy,
]);
}
}
foreach ($emergencyContacts as $contact) {
EmergencyContact::query()->create([
'parent_id' => $parentId,
'emergency_contact_name' => $contact['name'],
'cellphone' => $contact['cellphone'],
'email' => $contact['email'] ?? null,
'relation' => $contact['relation'] ?? null,
'semester' => $semester,
'school_year' => $schoolYear,
]);
}
});
return [
'created_student_ids' => $createdStudentIds,
'skipped' => $skippedStudents,
];
}
public function updateStudent(int $parentId, int $studentId, array $payload): void
{
$student = Student::query()
->where('id', $studentId)
->where('parent_id', $parentId)
->firstOrFail();
$ageReference = $this->configService->context()['date_age_reference'] ?: now()->toDateString();
$student->update([
'firstname' => $payload['firstname'],
'lastname' => $payload['lastname'],
'dob' => $payload['dob'],
'age' => $this->calculateAge($payload['dob'], $ageReference),
'gender' => $payload['gender'],
'registration_grade' => $payload['registration_grade'] ?? null,
'photo_consent' => ! empty($payload['photo_consent']) ? 1 : 0,
]);
StudentMedicalCondition::query()->where('student_id', $studentId)->delete();
foreach (($payload['medical_conditions'] ?? []) as $condition) {
$condition = trim((string) $condition);
if ($condition === '') {
continue;
}
StudentMedicalCondition::query()->create([
'student_id' => $studentId,
'condition_name' => $condition,
]);
}
StudentAllergy::query()->where('student_id', $studentId)->delete();
foreach (($payload['allergies'] ?? []) as $allergy) {
$allergy = trim((string) $allergy);
if ($allergy === '') {
continue;
}
StudentAllergy::query()->create([
'student_id' => $studentId,
'allergy' => $allergy,
]);
}
}
public function deleteStudent(int $parentId, int $studentId): void
{
$student = Student::query()
->where('id', $studentId)
->where('parent_id', $parentId)
->firstOrFail();
$hasEnrollment = \Illuminate\Support\Facades\DB::table('enrollments')
->where('student_id', $studentId)
->exists();
$hasClassAssignment = \Illuminate\Support\Facades\DB::table('student_class')
->where('student_id', $studentId)
->exists();
if ($hasEnrollment || $hasClassAssignment) {
throw new \RuntimeException('Student deletion is not allowed after enrollment or class assignment.');
}
$student->delete();
$remaining = Student::query()->where('parent_id', $parentId)->count();
if ($remaining === 0) {
EmergencyContact::query()->where('parent_id', $parentId)->delete();
}
}
private function calculateAge(string $dob, string $referenceDate): int
{
try {
$dobObj = new \DateTimeImmutable($dob);
$refObj = new \DateTimeImmutable($referenceDate);
return (int) $dobObj->diff($refObj)->y;
} catch (\Throwable $e) {
return 0;
}
}
}