add notifications logic and add support of both JWT and Sanctum
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
<?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
|
||||
{
|
||||
try {
|
||||
return view($viewName, $data, ['saveData' => true]);
|
||||
} catch (\Throwable $e) {
|
||||
return '<p>' . e($fallbackTitle) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\School;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class EnrollmentEventService
|
||||
{
|
||||
public function __construct(
|
||||
private EmailService $emailService,
|
||||
private UserNotificationDispatchService $notifier
|
||||
) {
|
||||
}
|
||||
|
||||
public function admissionUnderReview(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Admission Application Under Review',
|
||||
];
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Admission Under Review',
|
||||
'Your admission application is under review. We will notify you once it is approved.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Admission Application Under Review',
|
||||
$this->renderEmail('emails/status_admission_review', $emailData, 'Admission Application Under Review'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function paymentPending(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameSentence($students, $parentData['student_name'] ?? 'your student');
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'amountDue' => $parentData['amount'] ?? '',
|
||||
'dueDate' => $parentData['due_date'] ?? '',
|
||||
'portalLink' => $parentData['paymentLink'] ?? ($parentData['portalLink'] ?? url('/login')),
|
||||
'title' => 'Admission Decision',
|
||||
];
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Payment Pending',
|
||||
'Your payment is pending. Please check your email for details.',
|
||||
'payment'
|
||||
);
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Admission Decision',
|
||||
$this->renderEmail('emails/status_payment_pending', $emailData, 'Admission Decision'),
|
||||
'payment'
|
||||
);
|
||||
}
|
||||
|
||||
public function studentEnrolled(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentsDetails = $this->studentDetails($students);
|
||||
$studentNameData = $this->studentNameData($students, $parentData);
|
||||
$first = $studentsDetails[0] ?? [];
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $parentData['student_name'] ?? $studentNameData,
|
||||
'schoolYear' => $parentData['school_year'] ?? date('Y'),
|
||||
'studentId' => $parentData['student_id'] ?? ($first['studentId'] ?? ''),
|
||||
'gradeLevel' => $parentData['grade_level'] ?? ($first['gradeLevel'] ?? ''),
|
||||
'teacherName' => $parentData['teacher_name'] ?? ($first['teacherName'] ?? ''),
|
||||
'startDate' => $parentData['start_date'] ?? ($first['startDate'] ?? ''),
|
||||
'students' => count($studentsDetails) > 1 ? $studentsDetails : null,
|
||||
'portalLink' => url('/login'),
|
||||
'title' => 'Enrollment Confirmation',
|
||||
];
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Enrollment Confirmed',
|
||||
'Enrollment confirmed. Please check your email for details.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Enrollment Confirmation',
|
||||
$this->renderEmail('emails/status_enrolled', $emailData, 'Enrollment Confirmation'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function withdrawUnderReview(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Withdrawal Request Received',
|
||||
'Your withdrawal request is under review. Please check your email for details.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $parentData['student_name'] ?? $studentName,
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Withdrawal Under Review',
|
||||
];
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Withdrawal Request Under Review',
|
||||
$this->renderEmail('emails/status_withdraw_review', $emailData, 'Withdrawal Request Under Review'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function refundPending(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Refund Pending',
|
||||
'A refund is being processed. Please check your email for details.',
|
||||
'payment'
|
||||
);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'refundAmount' => $parentData['amount'] ?? '',
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Refund Pending',
|
||||
];
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Refund Pending',
|
||||
$this->renderEmail('emails/status_refund_pending', $emailData, 'Refund Pending'),
|
||||
'payment'
|
||||
);
|
||||
}
|
||||
|
||||
public function withdrawn(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
$withdrawals = $this->withdrawalsList($students);
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Withdrawal Confirmed',
|
||||
'Your withdrawal has been completed. Please check your email for details.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'withdrawalDate' => $parentData['withdrawal_date'] ?? ($parentData['date'] ?? ''),
|
||||
'withdrawals' => !empty($withdrawals) ? $withdrawals : null,
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Withdrawal Confirmed',
|
||||
];
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Withdrawal Confirmed',
|
||||
$this->renderEmail('emails/status_withdrawn', $emailData, 'Withdrawal Confirmed'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function denied(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
$studentsWithReasons = $this->studentsWithReasons($students);
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Admission Decision',
|
||||
'We\'ve emailed you an update regarding your admission decision.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'students' => $studentsWithReasons ?: null,
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Admission Decision',
|
||||
];
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Admission Decision',
|
||||
$this->renderEmail('emails/status_denied', $emailData, 'Admission Decision'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function waitlist(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
$studentName = $this->studentNameData($students, $parentData);
|
||||
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Waitlist Update',
|
||||
'Your application is currently on the waitlist. Please check your email for details.',
|
||||
'registration'
|
||||
);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'studentName' => $studentName,
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Waitlist Update',
|
||||
];
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Admission Decision',
|
||||
$this->renderEmail('emails/status_waitlist', $emailData, 'Admission Decision'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
public function enrollmentStatusChanged(array $parentData, array $students): array
|
||||
{
|
||||
$parentName = $this->parentName($parentData);
|
||||
|
||||
$emailData = [
|
||||
'parentName' => $parentName,
|
||||
'schoolYear' => $parentData['school_year'] ?? '',
|
||||
'changes' => $parentData['changes'] ?? [],
|
||||
'portalLink' => $parentData['portalLink'] ?? url('/login'),
|
||||
'title' => 'Enrollment Status Update',
|
||||
];
|
||||
|
||||
$summaryCount = count($emailData['changes']);
|
||||
$this->notifyParent(
|
||||
(int) ($parentData['user_id'] ?? 0),
|
||||
'Enrollment Status Updated',
|
||||
"Enrollment status updated for {$summaryCount} student(s). Check your email for details.",
|
||||
'registration'
|
||||
);
|
||||
|
||||
return $this->sendEmail(
|
||||
(string) ($parentData['email'] ?? ''),
|
||||
'Enrollment Status Update',
|
||||
$this->renderEmail('emails/status_enrollment_batch', $emailData, 'Enrollment Status Update'),
|
||||
'registration'
|
||||
);
|
||||
}
|
||||
|
||||
private function parentName(array $parentData): string
|
||||
{
|
||||
return trim((string) ($parentData['firstname'] ?? '') . ' ' . (string) ($parentData['lastname'] ?? ''));
|
||||
}
|
||||
|
||||
private function studentNameData(array $studentdata, array $parentData)
|
||||
{
|
||||
$names = [];
|
||||
foreach ($studentdata as $s) {
|
||||
$nm = !empty($s['name'])
|
||||
? trim((string) $s['name'])
|
||||
: trim((string) ($s['firstname'] ?? '') . ' ' . (string) ($s['lastname'] ?? ''));
|
||||
if ($nm !== '') {
|
||||
$names[] = $nm;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($names)) {
|
||||
return $parentData['student_name'] ?? 'your student';
|
||||
}
|
||||
if (count($names) === 1) {
|
||||
return $names[0];
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
private function studentNameSentence(array $studentdata, string $fallback): string
|
||||
{
|
||||
$names = [];
|
||||
foreach ($studentdata as $s) {
|
||||
$nm = !empty($s['name'])
|
||||
? trim((string) $s['name'])
|
||||
: trim((string) ($s['firstname'] ?? '') . ' ' . (string) ($s['lastname'] ?? ''));
|
||||
if ($nm !== '') {
|
||||
$names[] = $nm;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($names) === 0) {
|
||||
return $fallback;
|
||||
}
|
||||
if (count($names) === 1) {
|
||||
return $names[0];
|
||||
}
|
||||
if (count($names) === 2) {
|
||||
return $names[0] . ' and ' . $names[1];
|
||||
}
|
||||
$last = array_pop($names);
|
||||
return implode(', ', $names) . ', and ' . $last;
|
||||
}
|
||||
|
||||
private function studentDetails(array $studentdata): array
|
||||
{
|
||||
$details = [];
|
||||
foreach ($studentdata as $s) {
|
||||
$nm = !empty($s['name'])
|
||||
? trim((string) $s['name'])
|
||||
: trim((string) ($s['firstname'] ?? '') . ' ' . (string) ($s['lastname'] ?? ''));
|
||||
if ($nm === '') {
|
||||
continue;
|
||||
}
|
||||
$details[] = [
|
||||
'name' => $nm,
|
||||
'studentId' => $s['student_id'] ?? $s['id'] ?? '',
|
||||
'gradeLevel' => $s['grade_level'] ?? $s['grade'] ?? '',
|
||||
'teacherName' => $s['teacher_name'] ?? $s['teacher'] ?? '',
|
||||
'startDate' => $s['start_date'] ?? '',
|
||||
];
|
||||
}
|
||||
return $details;
|
||||
}
|
||||
|
||||
private function withdrawalsList(array $studentdata): array
|
||||
{
|
||||
$withdrawals = [];
|
||||
foreach ($studentdata as $s) {
|
||||
$nm = !empty($s['name'])
|
||||
? trim((string) $s['name'])
|
||||
: trim((string) ($s['firstname'] ?? '') . ' ' . (string) ($s['lastname'] ?? ''));
|
||||
if ($nm === '') {
|
||||
continue;
|
||||
}
|
||||
$wd = $s['withdrawal_date'] ?? $s['date'] ?? null;
|
||||
if ($wd) {
|
||||
$withdrawals[] = [
|
||||
'name' => $nm,
|
||||
'withdrawalDate' => $wd,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $withdrawals;
|
||||
}
|
||||
|
||||
private function studentsWithReasons(array $studentdata): array
|
||||
{
|
||||
$rows = [];
|
||||
foreach ($studentdata as $s) {
|
||||
$nm = !empty($s['name'])
|
||||
? trim((string) $s['name'])
|
||||
: trim((string) ($s['firstname'] ?? '') . ' ' . (string) ($s['lastname'] ?? ''));
|
||||
if ($nm === '') {
|
||||
continue;
|
||||
}
|
||||
$rows[] = [
|
||||
'name' => $nm,
|
||||
'reason' => $s['reason'] ?? null,
|
||||
];
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function notifyParent(int $userId, string $title, string $message, string $topic): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return;
|
||||
}
|
||||
$this->notifier->notifyUser($userId, $title, $message, ['in_app'], $topic, 'parent');
|
||||
}
|
||||
|
||||
private function sendEmail(string $to, string $subject, string $html, string $fromKey): array
|
||||
{
|
||||
if ($to === '') {
|
||||
Log::warning('Enrollment event: missing recipient email', ['subject' => $subject]);
|
||||
return ['ok' => false, 'message' => 'Missing recipient email.'];
|
||||
}
|
||||
|
||||
$ok = $this->emailService->send($to, $subject, $html, $fromKey);
|
||||
if (!$ok) {
|
||||
Log::error('Enrollment event email failed', ['to' => $to, 'subject' => $subject]);
|
||||
}
|
||||
return ['ok' => $ok];
|
||||
}
|
||||
|
||||
private function renderEmail(string $viewName, array $data, string $fallbackTitle): string
|
||||
{
|
||||
try {
|
||||
return view($viewName, $data, ['saveData' => true]);
|
||||
} catch (\Throwable $e) {
|
||||
return '<p>' . e($fallbackTitle) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\School;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PaymentEventService
|
||||
{
|
||||
public function __construct(
|
||||
private EmailService $emailService,
|
||||
private UserNotificationDispatchService $notifier
|
||||
) {
|
||||
}
|
||||
|
||||
public function paymentReceived(array $data, array $studentdata = []): array
|
||||
{
|
||||
$parentName = trim((string) ($data['firstname'] ?? '') . ' ' . (string) ($data['lastname'] ?? ''));
|
||||
|
||||
$fmtMoney = static fn ($v) => '$' . number_format((float) $v, 2);
|
||||
$fmtDateTz = static function (?string $s): string {
|
||||
if (!$s) {
|
||||
return '';
|
||||
}
|
||||
$dt = new \DateTime($s, new \DateTimeZone('UTC'));
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
|
||||
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
|
||||
return $dt->format('m-d-Y h:i A');
|
||||
};
|
||||
|
||||
$txid = $data['transaction_id']
|
||||
?? $data['transactionId']
|
||||
?? $data['payment_id']
|
||||
?? $data['id']
|
||||
?? '';
|
||||
|
||||
$methodRaw = $data['payment_method'] ?? ($data['method'] ?? '');
|
||||
$method = $methodRaw ? ucfirst((string) $methodRaw) : '';
|
||||
|
||||
$amountPaid = $fmtMoney($data['amount'] ?? 0);
|
||||
$preBalance = $fmtMoney($data['pre_balance'] ?? 0);
|
||||
$postBalance = $fmtMoney($data['post_balance'] ?? 0);
|
||||
$invoiceTotal = $fmtMoney($data['invoice_total'] ?? 0);
|
||||
|
||||
$invoiceDiscountRaw = isset($data['invoice_discount']) ? (float) $data['invoice_discount'] : 0.0;
|
||||
$parentYearDiscountTotalRaw = isset($data['parent_year_discount_total']) ? (float) $data['parent_year_discount_total'] : 0.0;
|
||||
|
||||
$emailData = [
|
||||
'title' => 'Payment Receipt',
|
||||
'parentName' => $parentName,
|
||||
'schoolYear' => $data['school_year'] ?? '',
|
||||
'semester' => $data['semester'] ?? '',
|
||||
'invoiceId' => $data['invoice_id'] ?? null,
|
||||
'invoiceNumber' => $data['invoice_number'] ?? null,
|
||||
'transactionId' => $txid,
|
||||
'paymentDate' => $fmtDateTz($data['payment_date'] ?? null),
|
||||
'method' => $method,
|
||||
'paymentMethod' => $method,
|
||||
'amount' => $amountPaid,
|
||||
'invoiceTotal' => $invoiceTotal,
|
||||
'preBalance' => $preBalance,
|
||||
'postBalance' => $postBalance,
|
||||
'checkNumber' => $data['check_number'] ?? null,
|
||||
'installmentSeq' => (int) ($data['installment_seq'] ?? 1),
|
||||
'invoiceDiscount' => $invoiceDiscountRaw > 0 ? $fmtMoney($invoiceDiscountRaw) : null,
|
||||
'parentYearDiscountTotal' => $parentYearDiscountTotalRaw > 0 ? $fmtMoney($parentYearDiscountTotalRaw) : null,
|
||||
'portalLink' => url('/login'),
|
||||
'students' => $studentdata,
|
||||
];
|
||||
|
||||
$inAppTitle = 'Payment Received';
|
||||
$inAppMsg = sprintf(
|
||||
'We received %s for Invoice #%s (Installment #%d). New balance: %s. Check your email for details.',
|
||||
$emailData['amount'],
|
||||
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
|
||||
(int) $emailData['installmentSeq'],
|
||||
$emailData['postBalance']
|
||||
);
|
||||
$this->notifyUser((int) ($data['user_id'] ?? 0), $inAppTitle, $inAppMsg, 'payments');
|
||||
|
||||
$emailBody = $this->renderEmail('emails/payment_receipt', $emailData, 'Payment Receipt');
|
||||
$subject = sprintf(
|
||||
'Payment Receipt - Invoice #%s (%s) %s',
|
||||
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
|
||||
$emailData['amount'],
|
||||
$txid ? "[TX: {$txid}]" : ''
|
||||
);
|
||||
|
||||
return $this->sendEmail((string) ($data['email'] ?? ''), $subject, $emailBody, 'payments');
|
||||
}
|
||||
|
||||
public function extraCharge(array $data, array $studentdata = []): array
|
||||
{
|
||||
$fmtMoney = static fn ($v) => '$' . number_format((float) $v, 2);
|
||||
$fmtFromUtc = static function (?string $s, string $fmt = 'm-d-Y h:i A'): string {
|
||||
if (!$s) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
$dt = new \DateTime($s, new \DateTimeZone('UTC'));
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
|
||||
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
|
||||
return $dt->format($fmt);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('extraCharge date error: ' . $e->getMessage());
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
$fmtDueLocal = static function (?string $s): string {
|
||||
if (!$s) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
if (strlen($s) <= 10) {
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $s, new \DateTimeZone($tzName ?: 'UTC'));
|
||||
return $dt ? $dt->format('m-d-Y') : '';
|
||||
}
|
||||
|
||||
if (preg_match('/(Z|[+\-]\d{2}:\d{2})$/', $s)) {
|
||||
$dt = new \DateTime($s);
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
|
||||
$dt->setTimezone(new \DateTimeZone($tzName ?: 'UTC'));
|
||||
return $dt->format('m-d-Y h:i A');
|
||||
}
|
||||
|
||||
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
|
||||
$dt = new \DateTime($s, new \DateTimeZone($tzName ?: 'UTC'));
|
||||
return $dt->format('m-d-Y h:i A');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('extraCharge due date error: ' . $e->getMessage());
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
$parentName = trim((string) ($data['firstname'] ?? '') . ' ' . (string) ($data['lastname'] ?? ''));
|
||||
|
||||
$amountSigned = (float) ($data['amount_signed'] ?? 0);
|
||||
$amountAbs = (float) ($data['amount_abs'] ?? abs($amountSigned));
|
||||
$preBalance = $fmtMoney($data['pre_balance'] ?? 0);
|
||||
$postBalance = $fmtMoney($data['post_balance'] ?? 0);
|
||||
$invoiceTotal = $fmtMoney($data['invoice_total'] ?? 0);
|
||||
$createdAt = $fmtFromUtc($data['created_at'] ?? null);
|
||||
$dueDate = $fmtDueLocal($data['due_date'] ?? null);
|
||||
$typeLabel = (($data['charge_type'] ?? 'add') === 'add') ? 'Added charge' : 'Deducted amount';
|
||||
$amountFmt = $fmtMoney($amountSigned);
|
||||
|
||||
$emailData = [
|
||||
'title' => 'Account Charge Update',
|
||||
'parentName' => $parentName,
|
||||
'schoolYear' => $data['school_year'] ?? '',
|
||||
'semester' => $data['semester'] ?? '',
|
||||
'invoiceId' => $data['invoice_id'] ?? null,
|
||||
'invoiceNumber' => $data['invoice_number'] ?? null,
|
||||
'typeLabel' => $typeLabel,
|
||||
'chargeTitle' => $data['charge_title'] ?? '',
|
||||
'chargeDesc' => $data['charge_desc'] ?? '',
|
||||
'chargeType' => $data['charge_type'] ?? 'add',
|
||||
'amountSigned' => $amountFmt,
|
||||
'amountAbs' => $fmtMoney($amountAbs),
|
||||
'invoiceTotal' => $invoiceTotal,
|
||||
'preBalance' => $preBalance,
|
||||
'postBalance' => $postBalance,
|
||||
'createdAt' => $createdAt,
|
||||
'dueDate' => $dueDate !== '' ? $dueDate : null,
|
||||
'portalLink' => $data['portal_link'] ?? url('/login'),
|
||||
'invoiceLink' => $data['invoice_link'] ?? null,
|
||||
'students' => $studentdata,
|
||||
];
|
||||
|
||||
$inAppTitle = 'Account Updated';
|
||||
$inAppMsg = sprintf(
|
||||
'%s: %s (%s). New balance: %s.',
|
||||
$typeLabel,
|
||||
$emailData['chargeTitle'],
|
||||
$emailData['amountSigned'],
|
||||
$emailData['postBalance']
|
||||
);
|
||||
$this->notifyUser((int) ($data['user_id'] ?? 0), $inAppTitle, $inAppMsg, 'billing');
|
||||
|
||||
$subject = sprintf(
|
||||
'Charge Update - Invoice #%s (%s)',
|
||||
(string) ($emailData['invoiceId'] ?? $emailData['invoiceNumber'] ?? ''),
|
||||
$emailData['amountSigned']
|
||||
);
|
||||
|
||||
$body = $this->renderEmail('emails/extra_charge_notice', $emailData, 'Account Charge Update');
|
||||
return $this->sendEmail((string) ($data['email'] ?? ''), $subject, $body, 'billing');
|
||||
}
|
||||
|
||||
private function notifyUser(int $userId, string $title, string $message, string $topic): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return;
|
||||
}
|
||||
$this->notifier->notifyUser($userId, $title, $message, ['in_app'], $topic, 'parent');
|
||||
}
|
||||
|
||||
private function sendEmail(string $to, string $subject, string $html, string $fromKey): array
|
||||
{
|
||||
if ($to === '') {
|
||||
Log::warning('Payment event missing recipient email', ['subject' => $subject]);
|
||||
return ['ok' => false, 'message' => 'Missing recipient email.'];
|
||||
}
|
||||
|
||||
$ok = $this->emailService->send($to, $subject, $html, $fromKey);
|
||||
if (!$ok) {
|
||||
Log::error('Payment event email failed', ['to' => $to, 'subject' => $subject]);
|
||||
}
|
||||
return ['ok' => $ok];
|
||||
}
|
||||
|
||||
private function renderEmail(string $viewName, array $data, string $fallbackTitle): string
|
||||
{
|
||||
try {
|
||||
return view($viewName, $data, ['saveData' => true]);
|
||||
} catch (\Throwable $e) {
|
||||
return '<p>' . e($fallbackTitle) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user