final = model(FinalExam::class); $this->config = model(Configuration::class); $this->student = model(Student::class); } public function getByStudent($id = null) { $schoolYear = $this->config->getConfig('school_year'); $semester = $this->config->getConfig('semester'); $final = $this->final->newQuery() ->when($id, fn($q) => $q->where('student_id', $id)) ->when($schoolYear, fn($q) => $q->where('school_year', $schoolYear)) ->when($semester, fn($q) => $q->where('semester', $semester)) ->first(); return $this->success($final ?: null, 'Final exam retrieved successfully'); } public function create() { $user = $this->getCurrentUser(); $data = $this->payloadData(); if (empty($data)) { return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST); } $rules = [ 'student_id' => 'required|integer', 'class_section_id' => 'required|integer', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $schoolYear = $this->config->getConfig('school_year'); $semester = $this->config->getConfig('semester'); $query = $this->final->newQuery() ->where('student_id', $data['student_id']) ->where('class_section_id', $data['class_section_id']); if ($schoolYear) { $query->where('school_year', $schoolYear); } if ($semester) { $query->where('semester', $semester); } $existing = $query->first(); $finalData = [ 'student_id' => $data['student_id'], 'class_section_id' => $data['class_section_id'], 'score' => $data['score'] ?? null, 'comment' => $data['comment'] ?? null, 'school_year' => $schoolYear, 'semester' => $semester, 'updated_by' => $user->id ?? null, ]; try { if ($existing) { $existing->fill($finalData); $existing->save(); $final = $existing->fresh(); } else { $final = $this->final->create($finalData); } return $this->success($final, 'Final exam saved successfully', Response::HTTP_CREATED); } catch (\Throwable $e) { log_message('error', 'Final exam save error: ' . $e->getMessage()); return $this->respondError('Failed to save final exam', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function update($id = null) { $final = $this->final->find($id); if (!$final) { return $this->respondError('Final exam not found', Response::HTTP_NOT_FOUND); } $data = $this->payloadData(); if (empty($data)) { return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST); } $allowed = ['score', 'comment']; $updateData = []; foreach ($allowed as $field) { if (array_key_exists($field, $data)) { $updateData[$field] = $data[$field]; } } if (empty($updateData)) { return $this->respondError('No valid fields to update', Response::HTTP_BAD_REQUEST); } $updateData['updated_by'] = $this->getCurrentUser()->id ?? null; try { $final->fill($updateData); $final->save(); return $this->success($final->fresh(), 'Final exam updated successfully'); } catch (\Throwable $e) { log_message('error', 'Final exam update error: ' . $e->getMessage()); return $this->respondError('Failed to update final exam', Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get students with their final exam scores for a class section * * @param int|null $classSectionId * @return \Illuminate\Http\JsonResponse */ public function getByClassSection($classSectionId = null) { // Get class_section_id from route parameter, query param, or request body $incoming = $classSectionId ?? $this->request->getGet('class_section_id') ?? $this->request->getPost('class_section_id') ?? null; $classSectionId = (int) ($incoming ?: 0); if ($classSectionId <= 0) { return $this->respondError('Missing or invalid class section ID', Response::HTTP_BAD_REQUEST); } try { $result = $this->getSavedScores($classSectionId); return $this->success($result, 'Students with final exam scores retrieved successfully'); } catch (\InvalidArgumentException $e) { return $this->respondError($e->getMessage(), Response::HTTP_BAD_REQUEST); } catch (\Throwable $e) { log_message('error', 'Final exam getByClassSection error: ' . $e->getMessage()); return $this->respondError('Failed to retrieve students with final exam scores', Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get students with their final exam scores for a class section * * @param int $classSectionId * @return array * @throws \InvalidArgumentException */ protected function getSavedScores(int $classSectionId): array { if ($classSectionId <= 0) { throw new \InvalidArgumentException('Invalid class section id.'); } $schoolYear = $this->config->getConfig('school_year'); $semester = $this->config->getConfig('semester'); $db = \Config\Database::connect(); // Subquery: pick ONE final exam row per student (latest by id) $latestFinalSub = $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 $builder = $this->student->builder(); $builder ->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 = ' . $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'); $builder->distinct(); $rows = $builder->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, 'class_section_id' => $classSectionId, ]; } }