404 lines
15 KiB
PHP
404 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Grading;
|
|
|
|
use App\Models\CurrentFlag;
|
|
use App\Models\ParentMeetingSchedule;
|
|
use App\Models\ScoreComment;
|
|
use App\Models\Student;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Event;
|
|
use RuntimeException;
|
|
|
|
class GradingBelowSixtyService
|
|
{
|
|
public function listRows(string $schoolYear, string $semester): array
|
|
{
|
|
$semesterKey = strtolower(trim($semester));
|
|
$rows = DB::table('semester_scores as ss')
|
|
->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,
|
|
]);
|
|
}
|
|
|
|
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 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 buildBelowSixtySubject(string $studentName, string $semester, string $schoolYear): string
|
|
{
|
|
$subject = 'Student Performance Alert';
|
|
if ($studentName !== '') {
|
|
$subject .= ' — ' . $studentName;
|
|
}
|
|
if ($semester !== '' || $schoolYear !== '') {
|
|
$subject .= ' (' . trim($semester . ' ' . $schoolYear) . ')';
|
|
}
|
|
return $subject;
|
|
}
|
|
|
|
private function fetchBelowSixtyMeetingContext(int $studentId, string $schoolYear, string $semester): array
|
|
{
|
|
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
|
if (empty($row)) {
|
|
return [];
|
|
}
|
|
|
|
$parentName = 'Parent/Guardian';
|
|
$parentUserId = null;
|
|
try {
|
|
$pRows = DB::select(
|
|
"SELECT u.id AS user_id, 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($pRows[0])) {
|
|
$parentUserId = (int) ($pRows[0]->user_id ?? 0) ?: null;
|
|
$parentName = trim((string) ($pRows[0]->firstname ?? '') . ' ' . (string) ($pRows[0]->lastname ?? ''));
|
|
if ($parentName === '') {
|
|
$parentName = 'Parent/Guardian';
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
}
|
|
|
|
if ($parentUserId === null) {
|
|
try {
|
|
$srow = DB::selectOne(
|
|
"SELECT s.parent_id, u.firstname, u.lastname
|
|
FROM students s
|
|
LEFT JOIN users u ON u.id = s.parent_id
|
|
WHERE s.id = ?
|
|
LIMIT 1",
|
|
[$studentId]
|
|
);
|
|
if (!empty($srow)) {
|
|
$parentUserId = (int) ($srow->parent_id ?? 0) ?: null;
|
|
$fallbackName = trim((string) ($srow->firstname ?? '') . ' ' . (string) ($srow->lastname ?? ''));
|
|
if ($fallbackName !== '') {
|
|
$parentName = $fallbackName;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
}
|
|
}
|
|
|
|
$studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
|
|
|
return [
|
|
'student_name' => $studentName !== '' ? $studentName : 'Student',
|
|
'parent_name' => $parentName,
|
|
'parent_user_id' => $parentUserId,
|
|
'class_section_name' => (string) ($row['class_section_name'] ?? ''),
|
|
];
|
|
}
|
|
}
|