select([ 's.id as student_id', 's.school_id', 's.firstname', 's.lastname', 'cs.class_section_name', 'ss.homework_avg', 'ss.project_avg', 'ss.participation_score', DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'), 'ss.ptap_score', 'ss.attendance_score', 'ss.midterm_exam_score', 'ss.final_exam_score', 'ss.semester_score', ]) ->join('students as s', 's.id', '=', 'ss.student_id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('s.is_active', 1) ->where('ss.school_year', $schoolYear) ->whereRaw('LOWER(TRIM(ss.semester)) = ?', [$semesterKey]) ->whereNotNull('ss.semester_score') ->where('ss.semester_score', '<', 60) ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get() ->map(fn ($row) => (array) $row) ->all(); if (empty($rows)) { return []; } $studentIds = array_values(array_unique(array_map( static fn ($r) => (int) ($r['student_id'] ?? 0), $rows ))); $studentIds = array_values(array_filter($studentIds, static fn ($id) => $id > 0)); $commentMap = []; if (!empty($studentIds)) { $commentRows = ScoreComment::query() ->select('student_id', 'comment', 'created_at') ->where('score_type', 'general') ->where('school_year', $schoolYear) ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey]) ->whereIn('student_id', $studentIds) ->orderBy('created_at', 'DESC') ->get(); foreach ($commentRows as $row) { $sid = (int) $row->student_id; if ($sid > 0 && !isset($commentMap[$sid])) { $commentMap[$sid] = (string) ($row->comment ?? ''); } } } $statusMap = []; if (!empty($studentIds)) { $flagRows = CurrentFlag::query() ->select('student_id', 'flag_state') ->where('flag', 'grade') ->where('school_year', $schoolYear) ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey]) ->whereIn('student_id', $studentIds) ->get(); foreach ($flagRows as $row) { $sid = (int) $row->student_id; if ($sid > 0) { $statusMap[$sid] = (string) ($row->flag_state ?? ''); } } } foreach ($rows as &$row) { $sid = (int) ($row['student_id'] ?? 0); $row['comment'] = $commentMap[$sid] ?? ''; $flagState = strtolower(trim((string) ($statusMap[$sid] ?? ''))); $row['status'] = ($flagState === 'closed' || $flagState === 'canceled') ? 'Closed' : 'Open'; } unset($row); return $rows; } public function emailContext(int $studentId, string $schoolYear, string $semester): array { $row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester); if (empty($row)) { throw new RuntimeException('Student record not found for the selected term.'); } $studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? '')); $subject = $this->buildBelowSixtySubject($studentName, $semester, $schoolYear); $parentName = $this->fetchBelowSixtyParentName($studentId); $scores = [ 'homework_avg' => $row['homework_avg'] ?? null, 'project_avg' => $row['project_avg'] ?? null, 'participation_score' => $row['participation_score'] ?? null, 'test_avg' => $row['test_avg'] ?? null, 'ptap_score' => $row['ptap_score'] ?? null, 'attendance_score' => $row['attendance_score'] ?? null, 'midterm_exam_score' => $row['midterm_exam_score'] ?? null, 'semester_score' => $row['semester_score'] ?? null, ]; return [ 'student_id' => $studentId, 'student_name' => $studentName !== '' ? $studentName : 'Student', 'parent_name' => $parentName, 'class_section_name' => $row['class_section_name'] ?? '', 'semester' => $semester, 'school_year' => $schoolYear, 'scores' => $scores, 'comment' => $row['comment'] ?? '', 'subject' => $subject, ]; } public function sendEmail(array $payload): void { Event::dispatch('below60.email', [$payload]); } public function updateStatus(int $studentId, string $semester, string $schoolYear, string $status, string $note, ?int $userId): void { $status = ucfirst(strtolower($status)); if (!in_array($status, ['Open', 'Closed'], true)) { throw new RuntimeException('Invalid status.'); } $semesterKey = strtolower(trim($semester)); $existing = CurrentFlag::query() ->where('student_id', $studentId) ->where('flag', 'grade') ->where('school_year', $schoolYear) ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey]) ->first(); $now = now(); if ($existing) { $data = [ 'flag_state' => $status, 'flag_datetime' => $now, 'updated_at' => $now, ]; if ($status === 'Open') { $data['updated_by_open'] = $userId; if ($note !== '') { $prev = (string) ($existing->open_description ?? ''); $data['open_description'] = trim($prev . PHP_EOL . $note); } } else { $data['updated_by_closed'] = $userId; if ($note !== '') { $prev = (string) ($existing->close_description ?? ''); $data['close_description'] = trim($prev . PHP_EOL . $note); } } $existing->update($data); return; } $row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester); $studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? '')); $grade = (string) ($row['class_section_name'] ?? ''); $data = [ 'student_id' => $studentId, 'student_name' => $studentName !== '' ? $studentName : 'Student', 'grade' => $grade, 'flag' => 'grade', 'flag_datetime' => $now, 'flag_state' => $status, 'semester' => $semester, 'school_year' => $schoolYear, 'updated_at' => $now, ]; if ($status === 'Open') { $data['updated_by_open'] = $userId; if ($note !== '') { $data['open_description'] = $note; } } else { $data['updated_by_closed'] = $userId; if ($note !== '') { $data['close_description'] = $note; } } CurrentFlag::query()->create($data); } public function meetingContext(int $studentId, string $schoolYear, string $semester): array { $context = $this->fetchBelowSixtyMeetingContext($studentId, $schoolYear, $semester); if (empty($context)) { throw new RuntimeException('Student record not found for the selected term.'); } return $context + [ 'student_id' => $studentId, 'semester' => $semester, 'school_year' => $schoolYear, ]; } public function saveMeeting(array $payload, ?int $userId): void { $studentId = (int) ($payload['student_id'] ?? 0); $semester = (string) ($payload['semester'] ?? ''); $schoolYear = (string) ($payload['school_year'] ?? ''); $date = trim((string) ($payload['date'] ?? '')); $time = trim((string) ($payload['time'] ?? '')); $notes = trim((string) ($payload['notes'] ?? '')); if ($studentId <= 0 || $semester === '' || $schoolYear === '' || $date === '') { throw new RuntimeException('Missing required fields.'); } $context = $this->fetchBelowSixtyMeetingContext($studentId, $schoolYear, $semester); if (empty($context)) { throw new RuntimeException('Student record not found for the selected term.'); } ParentMeetingSchedule::query()->create([ 'student_id' => $studentId, 'parent_user_id' => $context['parent_user_id'] ?? null, 'parent_name' => $context['parent_name'], 'student_name' => $context['student_name'], 'class_section_name' => $context['class_section_name'], 'date' => $date, 'time' => $time !== '' ? $time : null, 'notes' => $notes !== '' ? $notes : null, 'semester' => $semester, 'school_year' => $schoolYear, 'status' => 'scheduled', 'created_by' => $userId, ]); } public function listDecisionRows(string $schoolYear): array { $scoreRows = DB::table('semester_scores as ss') ->select([ 's.id as student_id', 's.school_id', 's.firstname', 's.lastname', 's.age', 'ss.class_section_id', 'cs.class_section_name', DB::raw('LOWER(TRIM(ss.semester)) as sem_key'), 'ss.semester_score', ]) ->join('students as s', 's.id', '=', 'ss.student_id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('s.is_active', 1) ->where('ss.school_year', $schoolYear) ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring']) ->whereNotNull('ss.semester_score') ->orderBy('cs.class_section_name', 'ASC') ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->orderByDesc('ss.updated_at') ->orderByDesc('ss.id') ->get(); if ($scoreRows->isEmpty()) { return []; } $studentMap = []; foreach ($scoreRows as $row) { $studentId = (int) ($row->student_id ?? 0); if ($studentId <= 0) { continue; } if (!isset($studentMap[$studentId])) { $studentMap[$studentId] = [ 'student_id' => $studentId, 'school_id' => $row->school_id, 'firstname' => $row->firstname, 'lastname' => $row->lastname, 'age' => $row->age, 'class_section_id' => (int) ($row->class_section_id ?? 0), 'class_section_name' => $row->class_section_name, 'fall_score' => null, 'spring_score' => null, ]; } $semesterKey = strtolower(trim((string) ($row->sem_key ?? ''))); $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null; if ($score === null) { continue; } if ($semesterKey === 'fall' && $studentMap[$studentId]['fall_score'] === null) { $studentMap[$studentId]['fall_score'] = $score; } if ($semesterKey === 'spring' && $studentMap[$studentId]['spring_score'] === null) { $studentMap[$studentId]['spring_score'] = $score; } } $studentIds = array_keys($studentMap); if (empty($studentIds)) { return []; } $belowDecisionMap = BelowSixtyDecision::query() ->where('school_year', $schoolYear) ->where('semester', 'year') ->whereIn('student_id', $studentIds) ->get() ->keyBy(static fn (BelowSixtyDecision $row) => (int) $row->student_id); $studentDecisionMap = StudentDecision::query() ->where('school_year', $schoolYear) ->whereIn('student_id', $studentIds) ->get() ->keyBy(static fn (StudentDecision $row) => (int) $row->student_id); $certificateRows = DB::table('certificate_records') ->select('student_id', 'certificate_number', 'issued_at') ->where('school_year', $schoolYear) ->whereIn('student_id', $studentIds) ->orderByDesc('issued_at') ->get(); $certificateMap = []; foreach ($certificateRows as $row) { $studentId = (int) ($row->student_id ?? 0); if ($studentId > 0 && !isset($certificateMap[$studentId])) { $certificateMap[$studentId] = (string) ($row->certificate_number ?? ''); } } $rows = []; foreach ($studentMap as $studentId => $info) { $fall = $info['fall_score']; $spring = $info['spring_score']; if ($fall === null || $spring === null) { continue; } $yearScore = round(($fall + $spring) / 2, 2); if ($yearScore >= 60) { continue; } $belowDecision = $belowDecisionMap->get($studentId); $studentDecision = $studentDecisionMap->get($studentId); if ($studentDecision !== null && is_numeric($studentDecision->year_score)) { $yearScore = round((float) $studentDecision->year_score, 2); } $rows[] = [ 'student_id' => $studentId, 'school_id' => $info['school_id'], 'firstname' => $info['firstname'], 'lastname' => $info['lastname'], 'age' => $info['age'], 'class_section_id' => $info['class_section_id'], 'class_section_name' => $info['class_section_name'], 'fall_score' => $fall, 'spring_score' => $spring, 'year_score' => $yearScore, 'decision' => (string) ($belowDecision?->decision ?? ''), 'decision_notes' => (string) ($belowDecision?->notes ?? ''), 'consolidated_decision' => $studentDecision?->decision, 'certificate_number' => $certificateMap[$studentId] ?? '', ]; } return $rows; } public function saveDecision(int $studentId, string $schoolYear, string $decision, string $notes, ?int $userId): array { if ($studentId <= 0 || trim($schoolYear) === '') { throw new RuntimeException('Missing student or school year.'); } $decision = trim($decision); $notes = trim($notes); $belowDecision = BelowSixtyDecision::query()->firstOrNew([ 'student_id' => $studentId, 'semester' => 'year', 'school_year' => $schoolYear, ]); $belowDecision->decision = $decision !== '' ? $decision : null; $belowDecision->notes = $notes !== '' ? $notes : null; $belowDecision->decided_by = $userId; $belowDecision->save(); $scoreRows = DB::table('semester_scores as ss') ->select([ DB::raw('LOWER(TRIM(ss.semester)) as sem_key'), 'ss.semester_score', 'cs.class_section_name', ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('ss.student_id', $studentId) ->where('ss.school_year', $schoolYear) ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring']) ->whereNotNull('ss.semester_score') ->orderByDesc('ss.updated_at') ->orderByDesc('ss.id') ->get(); $fallScore = null; $springScore = null; $classSectionName = null; foreach ($scoreRows as $row) { $semesterKey = strtolower(trim((string) ($row->sem_key ?? ''))); $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null; if ($classSectionName === null && trim((string) ($row->class_section_name ?? '')) !== '') { $classSectionName = (string) $row->class_section_name; } if ($score === null) { continue; } if ($semesterKey === 'fall' && $fallScore === null) { $fallScore = $score; } if ($semesterKey === 'spring' && $springScore === null) { $springScore = $score; } } if ($classSectionName === null) { $enrollment = DB::table('student_class as sc') ->select('cs.class_section_name') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->where('sc.student_id', $studentId) ->where('sc.school_year', $schoolYear) ->orderByDesc('sc.id') ->first(); $classSectionName = $enrollment?->class_section_name; } $yearScore = null; if ($fallScore !== null && $springScore !== null) { $yearScore = round(($fallScore + $springScore) / 2, 2); } elseif ($fallScore !== null) { $yearScore = round($fallScore, 2); } elseif ($springScore !== null) { $yearScore = round($springScore, 2); } $studentDecision = StudentDecision::query()->firstOrNew([ 'student_id' => $studentId, 'school_year' => $schoolYear, ]); $studentDecision->class_section_name = $classSectionName; $studentDecision->year_score = $yearScore; $studentDecision->decision = $decision !== '' ? $decision : null; $studentDecision->source = $decision !== '' ? 'manual' : 'pending'; $studentDecision->notes = $notes !== '' ? $notes : null; $studentDecision->generated_by = $userId; $studentDecision->save(); return [ 'student_id' => $studentId, 'school_year' => $schoolYear, 'decision' => $decision, 'notes' => $notes, 'year_score' => $yearScore, 'class_section_name' => $classSectionName, ]; } public function studentDecisionDetails(int $studentId, string $schoolYear): array { if ($studentId <= 0 || trim($schoolYear) === '') { throw new RuntimeException('Missing student or school year.'); } return [ 'semesters' => $this->fetchAllSemestersForStudent($studentId, $schoolYear), ]; } public function decisionEmailContext(int $studentId, string $schoolYear): array { $context = $this->getBelowSixtyDecisionEmailContext($studentId, $schoolYear); if (empty($context['student'])) { throw new RuntimeException('Student not found.'); } $decision = trim((string) ($context['decision_row']['decision'] ?? '')); if ($decision === '') { throw new RuntimeException('No saved decision found for this student. Save a decision first.'); } $subject = 'Whole Year Academic Decision — ' . (string) ($context['student_name'] ?? 'Student') . ' (' . $schoolYear . ')'; return [ 'student_id' => $studentId, 'student_name' => (string) ($context['student_name'] ?? 'Student'), 'class_section_name' => (string) ($context['class_section_name'] ?? ''), 'school_year' => $schoolYear, 'semester' => 'year', 'decision' => $decision, 'subject' => $subject, 'html' => $this->buildDecisionEmailHtml($context), 'year_score' => $context['year_score'] ?? null, ]; } public function sendDecisionEmail(int $studentId, string $schoolYear, ?string $subject, ?string $html): void { $context = $this->getBelowSixtyDecisionEmailContext($studentId, $schoolYear); if (empty($context['student'])) { throw new RuntimeException('Student not found.'); } $decision = trim((string) ($context['decision_row']['decision'] ?? '')); if ($decision === '') { throw new RuntimeException('No saved decision found for this student.'); } $resolvedSubject = trim((string) $subject); if ($resolvedSubject === '') { $resolvedSubject = 'Whole Year Academic Decision — ' . (string) ($context['student_name'] ?? 'Student') . ' (' . $schoolYear . ')'; } $payload = [ 'student_id' => $studentId, 'student_name' => (string) ($context['student_name'] ?? 'Student'), 'class_section_name' => (string) ($context['class_section_name'] ?? ''), 'semester' => 'year', 'school_year' => $schoolYear, 'decision' => $decision, 'notes' => (string) ($context['decision_row']['notes'] ?? ''), 'subject' => $resolvedSubject, 'scores' => [ 'fall_score' => $context['fall_score'] ?? null, 'spring_score' => $context['spring_score'] ?? null, 'year_score' => $context['year_score'] ?? null, ], 'all_semesters' => $context['all_semesters'] ?? [], ]; $resolvedHtml = trim((string) $html); if ($resolvedHtml !== '') { $payload['html'] = $resolvedHtml; } else { $payload['html'] = $this->buildDecisionEmailHtml($context); } $this->sendEmail($payload); } private function fetchBelowSixtyEmailRow(int $studentId, string $schoolYear, string $semester): array { $semesterKey = strtolower(trim($semester)); $row = DB::table('semester_scores as ss') ->select([ 's.id as student_id', 's.firstname', 's.lastname', 'cs.class_section_name', 'ss.homework_avg', 'ss.project_avg', 'ss.participation_score', DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'), 'ss.ptap_score', 'ss.attendance_score', 'ss.midterm_exam_score', 'ss.semester_score', ]) ->join('students as s', 's.id', '=', 'ss.student_id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('ss.school_year', $schoolYear) ->where('ss.student_id', $studentId) ->where('s.is_active', 1) ->whereRaw('LOWER(TRIM(ss.semester)) = ?', [$semesterKey]) ->first(); if (!$row) { return []; } $commentRow = ScoreComment::query() ->select('comment') ->where('score_type', 'general') ->where('school_year', $schoolYear) ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey]) ->where('student_id', $studentId) ->orderBy('created_at', 'DESC') ->first(); $row = (array) $row; $row['comment'] = (string) ($commentRow?->comment ?? ''); return $row; } private function fetchAllSemestersForStudent(int $studentId, string $schoolYear): array { $rows = DB::table('semester_scores as ss') ->select([ 'ss.semester', 'cs.class_section_name', 'ss.homework_avg', 'ss.project_avg', 'ss.participation_score', DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'), 'ss.ptap_score', 'ss.attendance_score', 'ss.midterm_exam_score', 'ss.final_exam_score', 'ss.semester_score', ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('ss.student_id', $studentId) ->where('ss.school_year', $schoolYear) ->orderBy('ss.semester') ->get(); $semesters = []; foreach ($rows as $row) { $semester = ucfirst(strtolower(trim((string) ($row->semester ?? '')))); $semesters[$semester] = [ 'semester' => $semester, 'class_section_name' => $row->class_section_name, 'homework_avg' => $row->homework_avg, 'project_avg' => $row->project_avg, 'participation_score' => $row->participation_score, 'test_avg' => $row->test_avg, 'ptap_score' => $row->ptap_score, 'attendance_score' => $row->attendance_score, 'midterm_exam_score' => $row->midterm_exam_score, 'final_exam_score' => $row->final_exam_score, 'semester_score' => $row->semester_score, 'comments' => [], ]; } if (!empty($semesters)) { $commentRows = DB::table('score_comments') ->select('semester', 'score_type', 'comment', 'created_at') ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->whereNotNull('comment') ->where('comment', '!=', '') ->orderBy('semester') ->orderBy('score_type') ->orderByDesc('created_at') ->get(); foreach ($commentRows as $row) { $semester = ucfirst(strtolower(trim((string) ($row->semester ?? '')))); $type = strtolower(trim((string) ($row->score_type ?? 'general'))); if (isset($semesters[$semester]) && !isset($semesters[$semester]['comments'][$type])) { $semesters[$semester]['comments'][$type] = (string) ($row->comment ?? ''); } } } return array_values($semesters); } private function fetchBelowSixtyParentName(int $studentId): string { $parentName = 'Parent/Guardian'; try { $rows = DB::select( "SELECT u.firstname, u.lastname FROM family_students fs JOIN family_guardians fg ON fg.family_id = fs.family_id JOIN users u ON u.id = fg.user_id WHERE fs.student_id = ? ORDER BY fg.is_primary DESC, u.lastname, u.firstname LIMIT 1", [$studentId] ); if (!empty($rows[0])) { $candidate = trim((string) ($rows[0]->firstname ?? '') . ' ' . (string) ($rows[0]->lastname ?? '')); if ($candidate !== '') { $parentName = $candidate; } } } catch (\Throwable $e) { } return $parentName; } private function getBelowSixtyDecisionEmailContext(int $studentId, string $schoolYear): array { $student = DB::table('students as s') ->select(['s.id', 's.firstname', 's.lastname', 's.is_active']) ->where('s.id', $studentId) ->first(); if ($student === null) { return [ 'student' => null, 'decision_row' => null, 'student_name' => '', 'class_section_name' => '', 'fall_score' => null, 'spring_score' => null, 'year_score' => null, 'all_semesters' => [], ]; } $studentName = trim((string) ($student->firstname ?? '') . ' ' . (string) ($student->lastname ?? '')); if ($studentName === '') { $studentName = 'Student'; } $decisionRow = BelowSixtyDecision::query() ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->where('semester', 'year') ->first(); $scoreRows = DB::table('semester_scores as ss') ->select([ DB::raw('LOWER(TRIM(ss.semester)) as sem_key'), 'ss.semester', 'ss.semester_score', 'ss.class_section_id', 'cs.class_section_name', ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id') ->where('ss.student_id', $studentId) ->where('ss.school_year', $schoolYear) ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring']) ->whereNotNull('ss.semester_score') ->orderByDesc('ss.updated_at') ->orderByDesc('ss.id') ->get(); $fallScore = null; $springScore = null; $classSectionName = ''; foreach ($scoreRows as $row) { $semesterKey = strtolower(trim((string) ($row->sem_key ?? ''))); $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null; if ($classSectionName === '' && trim((string) ($row->class_section_name ?? '')) !== '') { $classSectionName = (string) $row->class_section_name; } if ($score === null) { continue; } if ($semesterKey === 'fall' && $fallScore === null) { $fallScore = $score; } if ($semesterKey === 'spring' && $springScore === null) { $springScore = $score; } } if ($classSectionName === '') { $enrollment = DB::table('student_class as sc') ->select('cs.class_section_name') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->where('sc.student_id', $studentId) ->where('sc.school_year', $schoolYear) ->orderByDesc('sc.id') ->first(); $classSectionName = (string) ($enrollment?->class_section_name ?? ''); } $yearScore = null; if ($fallScore !== null && $springScore !== null) { $yearScore = round(($fallScore + $springScore) / 2, 2); } elseif ($fallScore !== null) { $yearScore = round($fallScore, 2); } elseif ($springScore !== null) { $yearScore = round($springScore, 2); } return [ 'student' => (array) $student, 'decision_row' => $decisionRow?->toArray(), 'student_name' => $studentName, 'class_section_name' => $classSectionName, 'school_year' => $schoolYear, 'fall_score' => $fallScore, 'spring_score' => $springScore, 'year_score' => $yearScore, 'all_semesters' => $this->fetchAllSemestersForStudent($studentId, $schoolYear), ]; } private function buildDecisionEmailHtml(array $context): string { $studentName = (string) ($context['student_name'] ?? 'Student'); $schoolYear = (string) ($context['school_year'] ?? ''); $classSectionName = (string) ($context['class_section_name'] ?? ''); $decision = trim((string) ($context['decision_row']['decision'] ?? '')); $notes = trim((string) ($context['decision_row']['notes'] ?? '')); $fallScore = $context['fall_score'] ?? null; $springScore = $context['spring_score'] ?? null; $yearScore = $context['year_score'] ?? null; $fallText = $fallScore !== null ? number_format((float) $fallScore, 2) : 'N/A'; $springText = $springScore !== null ? number_format((float) $springScore, 2) : 'N/A'; $yearText = $yearScore !== null ? number_format((float) $yearScore, 2) : 'N/A'; $html = '
Dear Parent/Guardian,
This message is regarding ' . e($studentName) . ' for the ' . e($schoolYear) . ' school year.
Class: ' . e($classSectionName !== '' ? $classSectionName : 'N/A') . '
Fall Score: ' . e($fallText) . '
Spring Score: ' . e($springText) . '
Whole-Year Score: ' . e($yearText) . '
Decision: ' . e($decision) . '
Decision Notes:
' . nl2br(e($notes)) . '
Please contact the school administration if you have any questions.
Regards,
Al Rahma Sunday School
Detailed Scores and Comments
No detailed score data found.
'; } $scoreLabels = [ 'homework_avg' => 'Homework Avg', 'project_avg' => 'Project Avg', 'participation_score' => 'Participation', 'test_avg' => 'Test Avg', 'ptap_score' => 'PTAP Score', 'attendance_score' => 'Attendance', 'midterm_exam_score' => 'Midterm Score', 'final_exam_score' => 'Final Exam', 'semester_score' => 'Semester Score', ]; $commentTypeLabels = [ 'general' => 'General', 'attendance' => 'Attendance', 'attendance_comment' => 'Attendance', 'midterm' => 'Midterm', 'final' => 'Final Exam', 'ptap' => 'PTAP', ]; $html = 'Detailed Scores and Comments
'; foreach ($semesters as $semester) { $semesterName = trim((string) ($semester['semester'] ?? '')); if ($semesterName === '') { $semesterName = 'Semester'; } $classSectionName = trim((string) ($semester['class_section_name'] ?? '')); $html .= '' . e($semesterName) . ' Semester'; if ($classSectionName !== '') { $html .= ' — ' . e($classSectionName); } $html .= '
| Item | Score |
|---|---|
| ' . e($label) . ' | ' . e($scoreText) . ' |
| No scores recorded. | |
Comments
'; foreach ($deduped as $comment) { $html .= '
' . e($comment['label']) . ':
' . nl2br(e($comment['text'])) . '