db = \Config\Database::connect(); $this->configModel = new ConfigurationModel(); $this->studentModel = new StudentModel(); $this->teacherClassModel = new TeacherClassModel(); $this->schoolYear = $this->configModel->getConfig('school_year'); $this->semester = $this->configModel->getConfig('semester'); $this->gradingLockModel = new GradingLockModel(); $this->missingScoreOverrideModel = new MissingScoreOverrideModel(); } public function add() { // Accept class_section_id from POST/GET with session fallback $incoming = $this->request->getPost('class_section_id') ?? $this->request->getGet('class_section_id') ?? session()->get('class_section_id'); $classSectionId = (int) ($incoming ?: 0); if ($classSectionId <= 0) { return redirect()->back()->with('status', 'Missing class section.'); } // Persist for downstream calls session()->set('class_section_id', $classSectionId); $semester = $this->getTeacherSelectedSemester(); $schoolYear = $this->schoolYear; try { $result = $this->getSavedScores($classSectionId, $semester, $schoolYear); $missingOkMap = $this->missingScoreOverrideModel->getOverridesMap($classSectionId, $semester, $schoolYear, 'final_exam'); return view('teacher/add_final_exam', [ 'students' => $result['students'], 'semester' => $result['semester'], 'schoolYear' => $result['schoolYear'], 'classSectionId' => $classSectionId, 'missingOkMap' => $missingOkMap, ]); } catch (\Throwable $e) { return redirect()->back()->with('status', $e->getMessage()); } } public function showFinalMngt() { $incoming = $this->request->getPost('class_section_id') ?? $this->request->getGet('class_section_id') ?? session()->get('class_section_id'); $classSectionId = (int) ($incoming ?: 0); if ($classSectionId <= 0) { return redirect()->back()->with('status', 'Missing class section.'); } $semester = $this->getTeacherSelectedSemester(); $schoolYear = $this->schoolYear; try { $result = $this->getSavedScores($classSectionId, $semester, $schoolYear); $scoresLocked = $this->gradingLockModel->isLocked($classSectionId, $semester, $schoolYear); return view('grading/final', [ 'students' => $result['students'], 'semester' => $result['semester'], 'schoolYear' => $result['schoolYear'], 'classSectionId' => $classSectionId, 'scoresLocked' => $scoresLocked, ]); } catch (\Throwable $e) { return redirect()->back()->with('status', $e->getMessage()); } } private function getSavedScores(int $classSectionId, string $semester, string $schoolYear): array { if ($classSectionId <= 0) { throw new \InvalidArgumentException('Invalid class section id.'); } session()->set('class_section_id', $classSectionId); // Subquery: pick ONE final exam row per student (latest by id) $latestFinalSub = $this->db->table('final_exam') ->select('student_id, MAX(id) AS max_id') ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->groupBy('student_id') ->getCompiledSelect(); // Main query: roster + LEFT JOIN the single final exam row $qb = $this->studentModel ->select(' s.id AS student_id, s.school_id, s.firstname, s.lastname, fe.score ') ->from('students s') ->join( 'student_class sc', 'sc.student_id = s.id AND sc.class_section_id = ' . (int) $classSectionId . ' AND sc.school_year = ' . $this->db->escape($schoolYear), 'inner', false ) ->join('(' . $latestFinalSub . ') fl', 'fl.student_id = s.id', 'left', false) ->join('final_exam fe', 'fe.id = fl.max_id', 'left') ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC'); $qb->distinct(); $rows = $qb->get()->getResultArray(); // Ensure one row per student $unique = []; foreach ($rows as $r) { $unique[(int)$r['student_id']] = $r; } $students = array_values($unique); return [ 'students' => $students, 'semester' => $semester, 'schoolYear' => $schoolYear, ]; } private function getTeacherSelectedSemester(): string { helper('semester_selection_helper'); return selected_teacher_semester(); } }