config = model(Configuration::class); $this->homework = model(Homework::class); $this->user = model(User::class); $this->studentClass = model(StudentClass::class); $this->student = model(Student::class); $this->teacherClass = model(TeacherClass::class); $this->classSection = model(ClassSection::class); $this->schoolYear = (string) ($this->config->getConfig('school_year') ?? ''); $this->semester = (string) ($this->config->getConfig('semester') ?? ''); $this->semesterScoreService = \Config\Services::semesterScoreService(); } /** * Show scores for a specific type, class section, and student */ public function show($type, $classSectionId = null, $studentId = null) { $model = $this->getModelByType($type); if (!$model) { return $this->respondError('Invalid type', Response::HTTP_BAD_REQUEST); } $student = model(Student::class); $student = null; if ($studentId) { $student = $student->find($studentId); if (!$student) { return $this->respondError('Student not found', Response::HTTP_NOT_FOUND); } } try { $scores = $model->where([ 'student_id' => $studentId, 'semester' => $this->semester, 'school_year' => $this->schoolYear ])->findAll(); return $this->success([ 'student' => $student, 'scores' => $scores, 'type' => $type, 'class_section_id' => $classSectionId, 'semester' => $this->semester, 'school_year' => $this->schoolYear, ], 'Scores retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Grading show error: ' . $e->getMessage()); return $this->respondError('Failed to retrieve scores', Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update scores for various types */ public function update() { $data = $this->payloadData(); $type = $data['type'] ?? null; $studentId = (int) ($data['student_id'] ?? 0); $classSectionId = (int) ($data['class_section_id'] ?? 0); if (!$type) { return $this->respondError('Type is required', Response::HTTP_BAD_REQUEST); } $model = $this->getModelByType($type); if (!$model) { return $this->respondError('Invalid type', Response::HTTP_BAD_REQUEST); } try { if (in_array($type, ['homework', 'quiz', 'project'])) { $scoreIds = $data['score_ids'] ?? []; $scores = $data['scores'] ?? []; $comments = $data['comments'] ?? []; foreach ($scoreIds as $i => $id) { $updateData = [ 'score' => $scores[$i] ?? null, 'updated_at' => utc_now() ]; if (isset($comments[$i])) { $updateData['comment'] = $comments[$i]; } $model->update($id, $updateData); } } elseif (in_array($type, ['midterm', 'final', 'test'])) { $score = $data['score'] ?? null; $updateData = [ 'score' => $score, 'updated_at' => utc_now() ]; $existing = $model->where([ 'student_id' => $studentId, 'class_section_id' => $classSectionId, 'semester' => $this->semester, 'school_year' => $this->schoolYear ])->first(); if ($existing) { $model->update($existing['id'], $updateData); } else { $insertData = array_merge($updateData, [ 'student_id' => $studentId, 'class_section_id' => $classSectionId, 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'created_at' => utc_now() ]); $model->insert($insertData); } } elseif ($type === 'comments') { $comment = $data['comment'] ?? null; // Remove existing comments for this student/semester/year $model->where([ 'student_id' => $studentId, 'semester' => $this->semester, 'school_year' => $this->schoolYear ])->delete(); // Insert new comment $model->insert([ 'student_id' => $studentId, 'score_type' => 'general', 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'comment' => $comment, 'commented_by' => $this->getCurrentUserId(), 'created_at' => utc_now() ]); } // Update semester scores for students in the class section if ($classSectionId > 0) { $studentTeacherInfo = $this->student->getStudentInfoByClassSectionId( $classSectionId, $this->semester, $this->schoolYear ); try { foreach ($studentTeacherInfo as $studentInfo) { $this->semesterScoreService->updateStudentScores([ 'student_id' => $studentInfo['student_id'], 'school_id' => $studentInfo['school_id'] ?? null, 'class_section_id' => $studentInfo['class_section_id'], 'updated_by' => $studentInfo['updated_by'] ?? $this->getCurrentUserId(), ], $this->semester, $this->schoolYear); } } catch (RuntimeException $e) { log_message('error', 'Failed to update semester scores: ' . $e->getMessage()); } } return $this->success(null, 'Scores updated successfully'); } catch (\Throwable $e) { log_message('error', 'Grading update error: ' . $e->getMessage()); return $this->respondError('Failed to update scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get grading main view data - all students grouped by class sections */ public function grading() { $schoolYear = (string) $this->schoolYear; $semester = (string) $this->semester; // Roster + dual-join to semester_scores: // ss_b: match when semester_scores.class_section_id = BUSINESS ID (student_class.class_section_id) // ss_p: match when semester_scores.class_section_id = PK (classSection.id) $rows = DB::table('student_class as sc') ->select([ 'cs.id as section_pk', 'cs.class_section_id as section_id', 'cs.class_id', 'cs.class_section_name', 's.id as student_id', 's.school_id', 's.firstname', 's.lastname', DB::raw('COALESCE(ss_b.ptap_score, ss_p.ptap_score) as ss_ptap_score'), DB::raw('COALESCE(ss_b.semester_score, ss_p.semester_score) as ss_semester_score'), 'ss_b.class_section_id as matched_biz_csid', 'ss_p.class_section_id as matched_pk_csid' ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->join('students as s', 's.id', '=', 'sc.student_id') ->leftJoin('semester_scores as ss_b', function($join) use ($semester, $schoolYear) { $join->on('ss_b.student_id', '=', 's.id') ->on('ss_b.class_section_id', '=', 'sc.class_section_id') ->whereRaw('LOWER(ss_b.semester) = LOWER(?)', [$semester]) ->where('ss_b.school_year', '=', $schoolYear); }) ->leftJoin('semester_scores as ss_p', function($join) use ($semester, $schoolYear) { $join->on('ss_p.student_id', '=', 's.id') ->on('ss_p.class_section_id', '=', 'cs.id') ->whereRaw('LOWER(ss_p.semester) = LOWER(?)', [$semester]) ->where('ss_p.school_year', '=', $schoolYear); }) ->where('sc.school_year', $schoolYear) ->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_section_name', 'ASC') ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get() ->toArray(); // Build structures keyed by BUSINESS section id $grades = []; // class_id => [ ['class_section_id','class_section_name'], ... ] $studentsBySection = []; // section_id => [ students... ] foreach ($rows as $r) { $sectionId = (int) ($r->section_id ?? 0); $classId = (int) ($r->class_id ?? 0); $sectionName = (string) ($r->class_section_name ?? ''); if ($sectionId <= 0 || $classId <= 0) continue; if (!isset($grades[$classId])) { $grades[$classId] = []; } $exists = false; foreach ($grades[$classId] as $s) { if ((int)$s['class_section_id'] === $sectionId) { $exists = true; break; } } if (!$exists) { $grades[$classId][] = [ 'class_section_id' => $sectionId, 'class_section_name' => $sectionName, ]; } $sid = (int) ($r->student_id ?? 0); if ($sid <= 0) continue; $ptapScore = $r->ss_ptap_score ?? null; $semesterScore = $r->ss_semester_score ?? null; if (!isset($studentsBySection[$sectionId])) { $studentsBySection[$sectionId] = []; } $studentsBySection[$sectionId][] = [ 'id' => $sid, 'school_id' => $r->school_id ?? null, 'firstname' => $r->firstname ?? null, 'lastname' => $r->lastname ?? null, 'ptap' => is_null($ptapScore) ? null : (float) $ptapScore, 'semester_score' => is_null($semesterScore) ? null : (float) $semesterScore, ]; } return $this->success([ 'grades' => $grades, 'students_by_section' => $studentsBySection, 'semester' => $semester, 'school_year' => $schoolYear, ], 'Grading data retrieved successfully'); } /** * Get all scores and comments for students */ public function getScoreComment() { // Get all students for the current semester and school year $studentClassEntries = $this->studentClass ->where('semester', $this->semester) ->where('school_year', $this->schoolYear) ->findAll(); // Group student IDs $studentIds = array_map(fn($entry) => $entry['student_id'], $studentClassEntries); if (empty($studentIds)) { return $this->success([], 'No students found'); } // Fetch all scores and comments for the students $scoresAndComments = $this->getAllScoresAndComments($studentIds, $this->semester, $this->schoolYear); return $this->success($scoresAndComments, 'Scores and comments retrieved successfully'); } /** * Fetch all scores and comments for a list of students based on semester and school year. * * @param array $studentIds List of student IDs. * @param string $semester Current semester (e.g., 'fall', 'spring'). * @param string $schoolYear Current school year (e.g., '2025-2026'). * @return array */ private function getAllScoresAndComments($studentIds, $semester, $schoolYear) { // Validate input parameters if (empty($studentIds) || !is_array($studentIds)) { return []; } if (empty($semester) || empty($schoolYear)) { throw new \InvalidArgumentException('Semester and school year must be provided'); } // Initialize models $models = [ 'final_exam' => model(FinalExam::class), 'homework' => model(Homework::class), 'midterm' => model(MidtermExam::class), 'project' => model(Project::class), 'quiz' => model(Quiz::class), 'comments' => model(ScoreComment::class), 'semester_scores' => model(SemesterScore::class), 'student' => model(Student::class), 'student_class' => model(StudentClass::class), 'teacher_class' => model(TeacherClass::class), 'config' => model(Configuration::class), 'attendance' => model(AttendanceRecord::class) ]; // Get semester days configuration $semesterKey = strtolower($semester) === 'fall' ? 'total_semester1_days' : 'total_semester2_days'; $totalSemesterDays = $models['config']->getConfig($semesterKey) ?? 0; // Common query conditions $conditions = [ 'semester' => $semester, 'school_year' => $schoolYear ]; // Fetch all student data first $students = $models['student']->whereIn('id', $studentIds) ->where('school_year', $schoolYear) ->findAll(); if (empty($students)) { return []; } // Initialize result array with student data $allScores = []; foreach ($students as $student) { $className = $models['student_class']->getClassSectionsByStudentId($student['id'], $schoolYear); $updatedBy = $models['teacher_class']->getTeacherIdByClassSection($className, $semester, $schoolYear); $allScores[$student['id']] = [ 'school_id' => $student['school_id'], 'firstname' => $student['firstname'], 'lastname' => $student['lastname'], 'class_name' => $className, 'comments' => [] ]; } // Fetch and process attendance data foreach ($studentIds as $studentId) { if (!isset($allScores[$studentId])) continue; $absences = $models['attendance']->getTotalAbsences($studentId, $semester, $schoolYear); $attendance = min((($totalSemesterDays - $absences + 1) / $totalSemesterDays) * 100, 100); $allScores[$studentId]['attendance'] = [ 'score' => round($attendance, 2), 'absences' => $absences, 'total_days' => $totalSemesterDays ]; } // Fetch and process all score types $scoreTypes = [ 'final_exam' => $models['final_exam'], 'homework' => $models['homework'], 'midterm' => $models['midterm'], 'project' => $models['project'], 'quiz' => $models['quiz'], 'semester_score' => $models['semester_scores'] ]; foreach ($scoreTypes as $type => $model) { $scores = $model->whereIn('student_id', $studentIds) ->where($conditions) ->findAll(); foreach ($scores as $score) { if ($type === 'semester_score') { $allScores[$score['student_id']][$type] = [ 'homework_avg' => $score['homework_avg'], 'quiz_avg' => $score['quiz_avg'], 'project_avg' => $score['project_avg'], 'midterm_exam_score' => $score['midterm_exam_score'], 'final_exam_score' => $score['final_exam_score'], 'attendance_score' => $score['attendance_score'], 'participation_score' => $score['participation_score'], 'ptap_score' => $score['ptap_score'], 'test_avg' => $score['test_avg'], 'semester_score' => $score['semester_score'], 'semester' => $score['semester'], 'school_year' => $score['school_year'] ]; } else { $allScores[$score['student_id']][$type] = $score; } } } // Fetch and process comments $comments = $models['comments']->whereIn('student_id', $studentIds) ->where($conditions) ->findAll(); foreach ($comments as $comment) { $allScores[$comment['student_id']]['comments'][] = $comment; } return $allScores; } /** * Get model instance by type */ private function getModelByType(?string $type) { return match ($type) { 'homework' => model(Homework::class), 'quiz' => model(Quiz::class), 'project' => model(Project::class), 'midterm' => model(MidtermExam::class), 'final' => model(FinalExam::class), 'test' => model(SemesterScore::class), 'comments' => model(ScoreComment::class), default => null, }; } }