config = model(Configuration::class); $this->student = model(Student::class); $this->teacherClass = model(TeacherClass::class); $this->midtermExam = model(MidtermExam::class); $this->semesterScoreService = app(SemesterScoreService::class); $this->schoolYear = (string) ($this->config->getConfig('school_year') ?? ''); $this->semester = (string) ($this->config->getConfig('semester') ?? ''); } /** * GET /api/v1/midterm * Get midterm exam data for a class section */ public function index() { // Get class_section_id from POST -> GET -> session $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 $this->respondError('Missing class section.', Response::HTTP_BAD_REQUEST); } // Persist for downstream use session()->put('class_section_id', $classSectionId); try { $result = $this->getSavedScores($classSectionId, $this->semester, $this->schoolYear); return $this->success([ 'students' => $result['students'], 'semester' => $result['semester'], 'school_year' => $result['schoolYear'], 'class_section_id' => $classSectionId, ], 'Midterm exam data retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Midterm index error: ' . $e->getMessage()); return $this->respondError($e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * POST /api/v1/midterm * Update midterm exam scores (teacher endpoint) */ public function update() { $updatedBy = $this->getCurrentUserId(); if (!$updatedBy) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $payload = $this->payloadData(); $scores = $payload['final_score'] ?? null; $classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0); if (!is_array($scores)) { return $this->respondError('No scores submitted.', Response::HTTP_BAD_REQUEST); } if ($classSectionId <= 0) { return $this->respondError('Missing class section.', Response::HTTP_BAD_REQUEST); } try { $this->saveExamScores('midterm_exam', $scores, $updatedBy, $classSectionId, $this->semester, $this->schoolYear); $studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId, $this->semester, $this->schoolYear); // Add semester and school_year to each student info for the service foreach ($studentTeacherInfo as &$student) { $student['semester'] = $this->semester; $student['school_year'] = $this->schoolYear; } unset($student); // Call the updateScoresForStudents method try { $this->semesterScoreService->updateScoresForStudents($studentTeacherInfo); } catch (RuntimeException $e) { log_message('error', 'SemesterScoreService error: ' . $e->getMessage()); } return $this->success([ 'class_section_id' => $classSectionId, 'updated_count' => count($scores), ], 'Midterm exam scores updated successfully'); } catch (\Throwable $e) { log_message('error', 'Midterm update error: ' . $e->getMessage()); return $this->respondError('Failed to update midterm exam scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * POST /api/v1/midterm/management * Update midterm exam scores (management endpoint) */ public function updateManagement() { $payload = $this->payloadData(); $scores = $payload['final_score'] ?? null; $updatedBy = (int) ($payload['teacher_id'] ?? $this->getCurrentUserId() ?? 0); $classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0); if (!is_array($scores)) { return $this->respondError('No scores submitted.', Response::HTTP_BAD_REQUEST); } if ($classSectionId <= 0) { return $this->respondError('Missing class section.', Response::HTTP_BAD_REQUEST); } if ($updatedBy <= 0) { return $this->respondError('Missing teacher ID.', Response::HTTP_BAD_REQUEST); } try { $this->saveExamScores('midterm_exam', $scores, $updatedBy, $classSectionId, $this->semester, $this->schoolYear); $studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId, $this->semester, $this->schoolYear); // Add semester and school_year to each student info for the service foreach ($studentTeacherInfo as &$student) { $student['semester'] = $this->semester; $student['school_year'] = $this->schoolYear; } unset($student); // Call the updateScoresForStudents method try { $this->semesterScoreService->updateScoresForStudents($studentTeacherInfo); } catch (RuntimeException $e) { log_message('error', 'SemesterScoreService error: ' . $e->getMessage()); } // Return the same data structure as index for consistency $result = $this->getSavedScores($classSectionId, $this->semester, $this->schoolYear); return $this->success([ 'students' => $result['students'], 'semester' => $result['semester'], 'school_year' => $result['schoolYear'], 'class_section_id' => $classSectionId, 'updated_count' => count($scores), ], 'Midterm exam scores updated successfully'); } catch (\Throwable $e) { log_message('error', 'Midterm updateManagement error: ' . $e->getMessage()); return $this->respondError('Failed to update midterm exam scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * GET /api/v1/midterm/management * Get midterm exam data for management view */ public function showManagement() { // Get class_section_id from POST -> GET -> session $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 $this->respondError('Missing class section.', Response::HTTP_BAD_REQUEST); } try { // Load roster + saved scores for this class/term $result = $this->getSavedScores($classSectionId, $this->semester, $this->schoolYear); return $this->success([ 'students' => $result['students'], 'semester' => $result['semester'], 'school_year' => $result['schoolYear'], 'class_section_id' => $classSectionId, ], 'Midterm exam management data retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Midterm showManagement error: ' . $e->getMessage()); return $this->respondError($e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Save exam scores to the database */ private function saveExamScores(string $table, array $scores, int $updatedBy, int $classSectionId, string $semester, string $schoolYear): void { $now = utc_now(); foreach ($scores as $studentId => $data) { if (!is_numeric($studentId) || !isset($data['score'])) { continue; } $score = $data['score']; $existing = DB::table($table) ->where('student_id', $studentId) ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->first(); $dataToSave = [ 'student_id' => (int) $studentId, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'score' => is_numeric($score) ? (float) $score : null, 'semester' => $semester, 'school_year' => $schoolYear, 'updated_at' => $now, ]; if ($existing) { DB::table($table) ->where('id', $existing->id) ->update($dataToSave); } else { $dataToSave['created_at'] = $now; DB::table($table)->insert($dataToSave); } } } /** * Get roster + saved midterm scores for a specific classSection/term. */ private function getSavedScores(int $classSectionId, string $semester, string $schoolYear): array { if ($classSectionId <= 0) { throw new \InvalidArgumentException('Invalid class section id.'); } session()->put('class_section_id', $classSectionId); // Subquery: pick ONE midterm row per student (latest by id). $latestMidtermSub = DB::table('midterm_exam') ->select('student_id', DB::raw('MAX(id) AS max_id')) ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->groupBy('student_id'); // Main query: roster for this class/term + LEFT JOIN the single midterm row $rows = DB::table('students as s') ->select([ 's.id AS student_id', 's.school_id', 's.firstname', 's.lastname', 'me.score', ]) ->join('student_class as sc', function ($join) use ($classSectionId, $semester, $schoolYear) { $join->on('sc.student_id', '=', 's.id') ->where('sc.class_section_id', '=', $classSectionId) ->where('sc.semester', '=', $semester) ->where('sc.school_year', '=', $schoolYear); }) ->leftJoinSub($latestMidtermSub, 'ml', function ($join) { $join->on('ml.student_id', '=', 's.id'); }) ->leftJoin('midterm_exam as me', 'me.id', '=', 'ml.max_id') ->distinct() ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get() ->toArray(); // Final safety net: ensure one row per student_id $unique = []; foreach ($rows as $r) { $rArray = (array) $r; $unique[(int) $rArray['student_id']] = $rArray; } $students = array_values($unique); return [ 'students' => $students, 'semester' => $semester, 'schoolYear' => $schoolYear, ]; } }