226 lines
8.4 KiB
PHP
226 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Administrator;
|
|
|
|
use App\Mail\TeacherSubmissionReminderMail;
|
|
use App\Models\ClassSection;
|
|
use App\Models\TeacherSubmissionNotificationHistory;
|
|
use App\Models\User;
|
|
use App\Services\ApplicationUrlService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class TeacherSubmissionNotificationService
|
|
{
|
|
public function __construct(
|
|
protected AdministratorSharedService $shared,
|
|
protected TeacherSubmissionSupportService $support,
|
|
protected ApplicationUrlService $urls,
|
|
) {}
|
|
|
|
public function send(Request $request, int $adminId): array
|
|
{
|
|
$notify = $request->input('notify');
|
|
if (! is_array($notify)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Select at least one teacher to notify.',
|
|
'status' => 422,
|
|
];
|
|
}
|
|
|
|
$missingItemsPayload = $request->input('missing_items', []);
|
|
$schoolYear = $this->shared->getSchoolYear($request->input('school_year'));
|
|
$semester = $this->shared->getSemester($request->input('semester'));
|
|
$targets = $this->extractTargets($notify, $schoolYear, $semester);
|
|
|
|
if (empty($targets)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Select at least one teacher to notify.',
|
|
'status' => 422,
|
|
];
|
|
}
|
|
|
|
$teacherIds = array_values(array_unique(array_column($targets, 'teacher_id')));
|
|
$classSectionIds = array_values(array_unique(array_column($targets, 'class_section_id')));
|
|
|
|
$classSections = ClassSection::query()
|
|
->select('class_section_id', 'class_section_name')
|
|
->whereIn('class_section_id', $classSectionIds)
|
|
->get()
|
|
->keyBy('class_section_id');
|
|
|
|
$teachers = User::query()
|
|
->select('id', 'firstname', 'lastname', 'email')
|
|
->whereIn('id', $teacherIds)
|
|
->get()
|
|
->keyBy('id');
|
|
|
|
$adminUser = User::find($adminId);
|
|
$adminName = trim(($adminUser->firstname ?? '').' '.($adminUser->lastname ?? '')) ?: 'Administrator';
|
|
|
|
$scoreUrl = $this->urls->docsHomeUrl();
|
|
$sentCount = 0;
|
|
$failCount = 0;
|
|
|
|
foreach ($targets as $target) {
|
|
$classSectionId = (int) $target['class_section_id'];
|
|
$teacherId = (int) $target['teacher_id'];
|
|
|
|
$teacher = $teachers->get($teacherId);
|
|
$sectionName = $classSections->get($classSectionId)?->class_section_name ?? "Section {$classSectionId}";
|
|
$teacherName = trim(($teacher->firstname ?? '').' '.($teacher->lastname ?? '')) ?: 'Teacher';
|
|
|
|
$missingItems = $this->resolveMissingItems($missingItemsPayload, $classSectionId, $teacherId);
|
|
|
|
$missingNote = ! empty($missingItems)
|
|
? '<p>Outstanding items: '.e($this->support->formatMissingItemsText($missingItems)).'.</p>'
|
|
: '<p>Our records show no outstanding submissions for this section, but please verify if anything still needs attention.</p>';
|
|
|
|
$subject = "Reminder: Complete submissions for {$sectionName}";
|
|
$body = "<p>Dear {$teacherName},</p>"
|
|
."<p>Administration is gently reminding you to wrap up any remaining score submissions and/or comments for {$sectionName}.</p>"
|
|
.$missingNote
|
|
."<p>Visit <a href=\"{$scoreUrl}\">Teacher Score Submission</a> to address any remaining items.</p>"
|
|
."<p>Thank you,<br>{$adminName}</p>";
|
|
|
|
$email = $teacher->email ?? '';
|
|
$status = 'failed';
|
|
|
|
if (! empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
try {
|
|
Mail::to($email)->send(new TeacherSubmissionReminderMail($subject, $body));
|
|
$status = 'sent';
|
|
} catch (\Throwable $e) {
|
|
Log::error('Teacher submission notification failed: '.$e->getMessage());
|
|
}
|
|
}
|
|
|
|
if ($status === 'sent') {
|
|
$sentCount++;
|
|
} else {
|
|
$failCount++;
|
|
}
|
|
|
|
TeacherSubmissionNotificationHistory::create([
|
|
'teacher_id' => $teacherId,
|
|
'class_section_id' => $classSectionId,
|
|
'admin_id' => $adminId,
|
|
'notification_category' => 'teacher_submissions',
|
|
'message' => $this->support->truncateNotificationMessage($body),
|
|
'status' => $status,
|
|
'school_year' => $schoolYear,
|
|
'semester' => $semester,
|
|
'sent_at' => now(),
|
|
]);
|
|
}
|
|
|
|
$parts = [];
|
|
if ($sentCount > 0) {
|
|
$parts[] = $sentCount.' reminder'.($sentCount === 1 ? '' : 's').' sent';
|
|
}
|
|
if ($failCount > 0) {
|
|
$parts[] = $failCount.' reminder'.($failCount === 1 ? '' : 's').' failed';
|
|
}
|
|
|
|
return [
|
|
'success' => $failCount === 0,
|
|
'message' => ! empty($parts) ? implode(' and ', $parts) : 'No notifications were sent.',
|
|
'sent' => $sentCount,
|
|
'failed' => $failCount,
|
|
'status' => $failCount === 0 ? 200 : 207,
|
|
];
|
|
}
|
|
|
|
protected function extractTargets(array $notify, string $schoolYear, string $semester): array
|
|
{
|
|
if ($this->isFlatTeacherIdList($notify)) {
|
|
$teacherIds = array_values(array_unique(array_map('intval', array_filter($notify, static fn ($value) => (int) $value > 0))));
|
|
if (empty($teacherIds)) {
|
|
return [];
|
|
}
|
|
|
|
$query = ClassSection::query()
|
|
->select('teacher_class.class_section_id', 'teacher_class.teacher_id')
|
|
->join('teacher_class', 'teacher_class.class_section_id', '=', 'classSection.class_section_id')
|
|
->whereIn('teacher_class.teacher_id', $teacherIds)
|
|
->where('teacher_class.school_year', $schoolYear)
|
|
->orderBy('teacher_class.class_section_id');
|
|
|
|
if ($this->shared->teacherClassSupportsSemester() && $semester !== '') {
|
|
$query->where('teacher_class.semester', $semester);
|
|
}
|
|
|
|
return $query
|
|
->get()
|
|
->map(static fn ($row): array => [
|
|
'class_section_id' => (int) $row->class_section_id,
|
|
'teacher_id' => (int) $row->teacher_id,
|
|
])
|
|
->filter(static fn (array $row): bool => $row['class_section_id'] > 0 && $row['teacher_id'] > 0)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
$targets = [];
|
|
|
|
foreach ($notify as $sectionIdRaw => $teachers) {
|
|
$sectionId = (int) $sectionIdRaw;
|
|
if ($sectionId <= 0 || ! is_array($teachers)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($teachers as $teacherIdRaw => $value) {
|
|
$teacherId = (int) $teacherIdRaw;
|
|
if ($teacherId <= 0 || $value === null || $value === '') {
|
|
continue;
|
|
}
|
|
|
|
$targets["{$sectionId}_{$teacherId}"] = [
|
|
'class_section_id' => $sectionId,
|
|
'teacher_id' => $teacherId,
|
|
];
|
|
}
|
|
}
|
|
|
|
return array_values($targets);
|
|
}
|
|
|
|
protected function isFlatTeacherIdList(array $notify): bool
|
|
{
|
|
foreach ($notify as $value) {
|
|
if (is_array($value)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected function resolveMissingItems($payload, int $classSectionId, int $teacherId): array
|
|
{
|
|
if (! is_array($payload)) {
|
|
return [];
|
|
}
|
|
|
|
if (isset($payload[$classSectionId]) && is_array($payload[$classSectionId])) {
|
|
$sectionPayload = $payload[$classSectionId];
|
|
if (array_key_exists($teacherId, $sectionPayload) || array_key_exists((string) $teacherId, $sectionPayload)) {
|
|
$value = $sectionPayload[$teacherId] ?? $sectionPayload[(string) $teacherId] ?? null;
|
|
|
|
return $this->support->parseMissingItemsPayload($value);
|
|
}
|
|
}
|
|
|
|
if (array_key_exists($teacherId, $payload) || array_key_exists((string) $teacherId, $payload)) {
|
|
$value = $payload[$teacherId] ?? $payload[(string) $teacherId] ?? null;
|
|
|
|
return $this->support->parseMissingItemsPayload($value);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|