add controllers, servoices
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
<?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'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user