getConfig('school_year') ?? ''); $semester = (string) ($configModel->getConfig('semester') ?? ''); $deadlineValue = trim((string) ($configModel->getConfig('exam_draft_deadline') ?? '')); if ($deadlineValue === '') { CLI::write('exam_draft_deadline is not configured.', 'yellow'); return; } $tz = new \DateTimeZone(config('App')->appTimezone ?? 'UTC'); try { $deadline = new \DateTimeImmutable($deadlineValue, $tz); } catch (\Throwable $e) { CLI::write('Invalid exam_draft_deadline value.', 'red'); return; } $deadline = $deadline->setTime(0, 0, 0); $today = new \DateTimeImmutable('now', $tz); $today = $today->setTime(0, 0, 0); if ($today > $deadline) { CLI::write('Deadline has passed. No reminders sent.', 'yellow'); return; } $daysToDeadline = (int) $today->diff($deadline)->format('%r%a'); if (!$this->shouldSendOnDay($daysToDeadline)) { CLI::write('No reminder scheduled for today.', 'yellow'); return; } $db = Database::connect(); $teacherClassRows = $db->table('teacher_class') ->select('teacher_id, class_section_id, school_year, semester') ->when($schoolYear !== '', static function ($builder) use ($schoolYear) { return $builder->where('school_year', $schoolYear); }) ->when($semester !== '', static function ($builder) use ($semester) { return $builder->where('semester', $semester); }) ->get() ->getResultArray(); if (empty($teacherClassRows)) { CLI::write('No teacher assignments found.', 'yellow'); return; } $draftTable = 'exam_drafts'; $fields = $db->getFieldNames($draftTable); $teacherColumn = in_array('teacher_id', $fields, true) ? 'teacher_id' : 'author_id'; $hasStatusColumn = in_array('status', $fields, true); $hasIsLegacyColumn = in_array('is_legacy', $fields, true); $draftsQuery = $db->table($draftTable) ->select("{$teacherColumn} AS teacher_id, class_section_id") ->when($schoolYear !== '', static function ($builder) use ($schoolYear) { return $builder->where('school_year', $schoolYear); }) ->when($semester !== '', static function ($builder) use ($semester) { return $builder->where('semester', $semester); }); if ($hasStatusColumn) { $draftsQuery = $draftsQuery->where('status !=', 'legacy'); } if ($hasIsLegacyColumn) { $draftsQuery = $draftsQuery->where('is_legacy', 0); } $draftRows = $draftsQuery->get()->getResultArray(); $submittedMap = []; foreach ($draftRows as $row) { $key = (int) ($row['teacher_id'] ?? 0) . '|' . (int) ($row['class_section_id'] ?? 0); $submittedMap[$key] = true; } $missingByTeacher = []; foreach ($teacherClassRows as $row) { $teacherId = (int) ($row['teacher_id'] ?? 0); $classSectionId = (int) ($row['class_section_id'] ?? 0); if ($teacherId <= 0 || $classSectionId <= 0) { continue; } $key = $teacherId . '|' . $classSectionId; if (!isset($submittedMap[$key])) { $missingByTeacher[$teacherId][] = $classSectionId; } } if (empty($missingByTeacher)) { CLI::write('All teachers have submitted drafts.', 'green'); return; } $userModel = new UserModel(); $classSectionModel = new ClassSectionModel(); $historyModel = new TeacherSubmissionNotificationHistoryModel(); $mailer = new EmailController(); $classSections = $classSectionModel ->select('class_section_id, class_section_name') ->findAll(); $classLookup = []; foreach ($classSections as $section) { $classLookup[(int) ($section['class_section_id'] ?? 0)] = $section['class_section_name'] ?? ''; } $todayStamp = $today->format('Y-m-d'); foreach ($missingByTeacher as $teacherId => $sectionIds) { $teacher = $userModel->find($teacherId); $email = $teacher['email'] ?? ''; if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { continue; } $alreadySent = $historyModel ->where('teacher_id', $teacherId) ->where('notification_category', 'exam_draft_deadline') ->where('school_year', $schoolYear) ->where('semester', $semester) ->like('sent_at', $todayStamp) ->countAllResults() > 0; if ($alreadySent) { continue; } $classNames = array_map(static function ($id) use ($classLookup) { return $classLookup[(int) $id] ?? "Class {$id}"; }, $sectionIds); $classList = implode(', ', $classNames); $teacherName = trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? '')); if ($teacherName === '') { $teacherName = 'Teacher'; } $subject = 'Reminder: Exam draft submission deadline'; $body = '

Dear ' . esc($teacherName) . ',

' . '

This is a reminder to submit your exam draft(s) for: ' . esc($classList) . '.

' . '

Deadline: ' . esc($deadline->format('Y-m-d')) . '

' . '

Please submit your draft at Teacher Exam Drafts.

' . '

Thank you.

'; $status = 'failed'; if ($mailer->sendEmail($email, $subject, $body, 'notifications')) { $status = 'sent'; CLI::write("Sent reminder to {$email}", 'green'); } $historyModel->insert([ 'teacher_id' => $teacherId, 'class_section_id' => 0, 'admin_id' => null, 'notification_category' => 'exam_draft_deadline', 'message' => strip_tags($body), 'status' => $status, 'school_year' => $schoolYear, 'semester' => $semester, 'sent_at' => utc_now(), ]); } } private function shouldSendOnDay(int $daysToDeadline): bool { if ($daysToDeadline < 0) { return false; } if ($daysToDeadline <= 4) { return true; } return in_array($daysToDeadline, [28, 21, 14, 7], true); } }