renderEmail('emails/welcome_user', ['user' => $userData], $subject); $sent = false; if (!empty($userData['email'])) { $sent = $this->emailService->send( (string) $userData['email'], $subject, $message, 'registration' ); } $supportMessage = $this->renderEmail('emails/support_new_account', ['user' => $userData], $subject); $this->emailService->send( 'support@alrahmaisgl.org', 'New Account Created: ' . trim((string) ($userData['firstname'] ?? '') . ' ' . (string) ($userData['lastname'] ?? '')), $supportMessage, 'notifications' ); $this->ensureFamilyLinks($userData); return ['ok' => $sent]; } public function studentRegistered(array $data): array { if (empty($data['parents']) || !is_array($data['parents'])) { Log::warning('StudentRegistered: missing parent data'); return ['ok' => false, 'message' => 'Missing parent data.']; } $studentFullName = trim((string) ($data['firstname'] ?? '') . ' ' . (string) ($data['lastname'] ?? '')); if (!empty($data['parents']['user_id'])) { $title = 'Your child has been registered'; $msg = "{$studentFullName} has been successfully registered to Al Rahma Sunday School."; $this->notifier->notifyUser((int) $data['parents']['user_id'], $title, $msg, ['in_app', 'email'], 'registration', 'parent'); } $adminMessage = $this->renderEmail('emails/admin_student_registered', ['student' => $data], 'Student Registered'); $this->emailService->send( 'registration@alrahmaisgl.org', "New Student Registered: {$studentFullName}", $adminMessage, 'notifications' ); return ['ok' => true]; } public function deleteUnverifiedUser(array $user): array { $this->notifier->notifyUser( (int) ($user['id'] ?? 0), 'Account Deleted - Unverified', 'Your account was deleted because it was not verified within the time limit.', ['email'], 'notifications', 'parent' ); $adminEmails = array_map('trim', explode(',', (string) Configuration::getConfigValueByKey('delete_email_notify'))); $adminEmails = array_filter($adminEmails, static fn ($e) => $e !== ''); foreach ($adminEmails as $adminEmail) { $body = "Unverified account deleted: ID {$user['id']}, Email {$user['email']}"; $this->emailService->send($adminEmail, 'Unverified User Deleted', $body, 'notifications'); } return ['ok' => true]; } public function userRegistered(array $user): array { $this->notifier->notifyUser((int) ($user['id'] ?? 0), 'Welcome!', 'Thanks for registering with our school system.', ['in_app', 'email'], 'notifications', 'parent'); return ['ok' => true]; } public function userProfileUpdated(array $user): array { $this->notifier->notifyUser((int) ($user['id'] ?? 0), 'Profile Updated', 'Your profile information was successfully updated.', ['in_app'], 'notifications', 'parent'); return ['ok' => true]; } public function userDeactivated(array $user): array { $this->notifier->notifyUser((int) ($user['id'] ?? 0), 'Account Deactivated', 'Your account has been deactivated by the administrator.', ['in_app', 'email'], 'notifications', 'parent'); return ['ok' => true]; } public function scoresPosted(array $data): array { foreach ((array) ($data['students'] ?? []) as $student) { $this->notifier->notifyUser((int) ($student['id'] ?? 0), 'New Score Available', 'A new score has been posted.', ['in_app', 'email'], 'registration', 'student'); } return ['ok' => true]; } public function finalScoreReleased(array $data): array { foreach ((array) ($data['students'] ?? []) as $student) { $this->notifier->notifyUser((int) ($student['id'] ?? 0), 'Final Score Released', 'Your final score for the semester is now available.', ['in_app', 'email'], 'registration', 'student'); } $this->preparePromotionQueue(); return ['ok' => true]; } public function studentMarkedAbsent(array $record): array { $msg = (string) ($record['student_name'] ?? 'Student') . ' was absent on ' . (string) ($record['date'] ?? ''); $this->notifier->notifyUser((int) ($record['parent_id'] ?? 0), 'Absence Notification', $msg, ['in_app', 'email'], 'registration', 'parent'); return ['ok' => true]; } public function studentMarkedLate(array $record): array { $msg = (string) ($record['student_name'] ?? 'Student') . ' was late on ' . (string) ($record['date'] ?? ''); $this->notifier->notifyUser((int) ($record['parent_id'] ?? 0), 'Late Notification', $msg, ['in_app', 'email'], 'registration', 'parent'); return ['ok' => true]; } public function paymentMissed(array $user): array { $this->notifier->notifyUser((int) ($user['id'] ?? 0), 'Payment Missed', 'You have missed a scheduled payment. Please settle your balance.', ['in_app', 'email'], 'payment', 'parent'); return ['ok' => true]; } public function classScheduleUpdated(array $data): array { foreach ((array) ($data['students'] ?? []) as $student) { $this->notifier->notifyUser((int) ($student['id'] ?? 0), 'Schedule Update', 'Your class schedule has been updated.', ['in_app'], 'notifications', 'student'); } return ['ok' => true]; } public function newMessageReceived(array $data): array { $msg = 'You have a new message from ' . (string) ($data['sender_name'] ?? ''); $this->notifier->notifyUser((int) ($data['recipient_id'] ?? 0), 'New Message Received', $msg, ['in_app'], 'notifications', 'parent'); return ['ok' => true]; } public function systemAnnouncementPosted(array $data): array { $targetGroup = (string) ($data['target_group'] ?? 'everyone'); $users = (array) ($data['users'] ?? []); if (empty($users) && $targetGroup !== '') { $users = User::getUsersByRole($targetGroup); } foreach ($users as $user) { $this->notifier->notifyUser((int) ($user['id'] ?? 0), (string) ($data['title'] ?? ''), (string) ($data['message'] ?? ''), ['in_app'], 'notifications', $targetGroup); } return ['ok' => true]; } public function customNotification(array $data): array { $this->notifier->notifyUser( (int) ($data['user_id'] ?? 0), (string) ($data['title'] ?? ''), (string) ($data['message'] ?? ''), (array) ($data['channels'] ?? ['in_app']), 'notifications', (string) ($data['target_group'] ?? 'everyone') ); return ['ok' => true]; } private function ensureFamilyLinks(array $userData): void { $userId = (int) ($userData['id'] ?? 0); if ($userId <= 0) { return; } DB::transaction(function () use ($userData, $userId): void { $familyCode = 'FAM-' . $userId; $family = Family::query()->where('family_code', $familyCode)->first(); if (!$family) { $householdName = trim('Family of ' . ((string) ($userData['firstname'] ?? '') . ' ' . (string) ($userData['lastname'] ?? ''))); $family = Family::query()->create([ 'family_code' => $familyCode, 'household_name' => $householdName, 'address_line1' => $userData['address_street'] ?? null, 'city' => $userData['city'] ?? null, 'state' => $userData['state'] ?? null, 'postal_code' => $userData['zip'] ?? null, 'primary_phone' => $userData['cellphone'] ?? null, 'is_active' => 1, ]); } FamilyGuardian::query()->firstOrCreate([ 'family_id' => (int) $family->id, 'user_id' => $userId, ], [ 'relation' => 'primary', 'is_primary' => 1, 'receive_emails' => 1, 'receive_sms' => 0, ]); $studentIds = Student::query() ->where('parent_id', $userId) ->pluck('id') ->map(fn ($id) => (int) $id) ->all(); foreach ($studentIds as $studentId) { FamilyStudent::query()->firstOrCreate([ 'family_id' => (int) $family->id, 'student_id' => $studentId, ], [ 'is_primary_home' => 1, ]); } }); } private function preparePromotionQueue(): void { try { $currentYear = (string) Configuration::getConfigValueByKey('school_year'); if (!preg_match('/^(\d{4})-(\d{4})$/', $currentYear, $m)) { return; } $y1 = (int) $m[1]; $y2 = (int) $m[2]; $nextYear = ($y1 + 1) . '-' . ($y2 + 1); $rows = DB::table('students as s') ->select('s.id as student_id', 'sc.class_section_id', 'fall.semester_score as fall_score', 'spring.semester_score as spring_score') ->leftJoin('student_class as sc', function ($join) use ($currentYear) { $join->on('sc.student_id', '=', 's.id') ->where('sc.school_year', '=', $currentYear); }) ->leftJoin('semester_scores as fall', function ($join) use ($currentYear) { $join->on('fall.student_id', '=', 's.id') ->where('fall.semester', '=', 'fall') ->where('fall.school_year', '=', $currentYear); }) ->leftJoin('semester_scores as spring', function ($join) use ($currentYear) { $join->on('spring.student_id', '=', 's.id') ->where('spring.semester', '=', 'spring') ->where('spring.school_year', '=', $currentYear); }) ->get(); foreach ($rows as $row) { $fall = isset($row->fall_score) ? (float) $row->fall_score : null; $spring = isset($row->spring_score) ? (float) $row->spring_score : null; if ($fall === null || $spring === null) { continue; } $finalAvg = ($fall + $spring) / 2.0; if ($finalAvg < 60.0) { continue; } $fromSection = (int) ($row->class_section_id ?? 0); if ($fromSection <= 0) { continue; } $fromClassId = (int) (ClassSection::getClassId($fromSection) ?? 0); if ($fromClassId <= 0) { continue; } $toClassId = $fromClassId + 1; PromotionQueue::upsertQueue([ 'student_id' => (int) $row->student_id, 'from_class_section_id' => $fromSection, 'to_class_id' => $toClassId, 'to_class_section_id' => null, 'school_year_from' => $currentYear, 'school_year_to' => $nextYear, 'status' => 'queued', 'updated_by' => null, ]); } } catch (\Throwable $e) { Log::error('Promotion queue build failed: ' . $e->getMessage()); } } private function renderEmail(string $viewName, array $data, string $fallbackTitle): string { Log::debug('Skipping Blade email template', ['view' => $viewName]); return '

' . e($fallbackTitle) . '

'; } }