333 lines
13 KiB
PHP
333 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Services\School;
|
|
|
|
use App\Models\ClassSection;
|
|
use App\Models\Configuration;
|
|
use App\Models\Family;
|
|
use App\Models\FamilyGuardian;
|
|
use App\Models\FamilyStudent;
|
|
use App\Models\PromotionQueue;
|
|
use App\Models\Student;
|
|
use App\Models\User;
|
|
use App\Services\EmailService;
|
|
use App\Services\Notifications\UserNotificationDispatchService;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class AccountEventService
|
|
{
|
|
public function __construct(
|
|
private EmailService $emailService,
|
|
private UserNotificationDispatchService $notifier
|
|
) {}
|
|
|
|
public function newAccountAdded(array $userData): array
|
|
{
|
|
$subject = 'Welcome to Al Rahma Sunday School!';
|
|
$message = $this->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 '<p>'.e($fallbackTitle).'</p>';
|
|
}
|
|
}
|