homework = model(Homework::class); $this->config = model(Configuration::class); $this->studentClass = model(StudentClass::class); $this->student = model(Student::class); $this->teacherClass = model(TeacherClass::class); $this->user = model(User::class); $this->schoolYear = (string) ($this->config->getConfig('school_year') ?? ''); $this->semester = (string) ($this->config->getConfig('semester') ?? ''); $this->semesterScoreService = \Config\Services::semesterScoreService(); } /** * Get paginated homework list */ public function index() { $page = max(1, (int) ($this->request->getGet('page') ?? 1)); $perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20))); $classSectionId = $this->request->getGet('class_section_id'); $query = $this->homework->newQuery() ->when($this->schoolYear !== '', fn($q) => $q->where('school_year', $this->schoolYear)) ->when($this->semester !== '', fn($q) => $q->where('semester', $this->semester)) ->when($classSectionId, fn($q) => $q->where('class_section_id', $classSectionId)) ->orderBy('created_at', 'DESC'); $result = $this->paginate($query, $page, $perPage); return $this->success($result, 'Homework retrieved successfully'); } /** * Get homework by student ID */ public function getByStudent($id = null) { $query = $this->homework->newQuery() ->when($id, fn($q) => $q->where('student_id', $id)) ->when($this->schoolYear !== '', fn($q) => $q->where('school_year', $this->schoolYear)) ->when($this->semester !== '', fn($q) => $q->where('semester', $this->semester)); $homework = $query->get()->toArray(); return $this->success($homework, 'Homework retrieved successfully'); } /** * Create a single homework entry */ public function create() { $user = $this->getCurrentUser(); $data = $this->payloadData(); if (empty($data)) { return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST); } $errors = $this->validateRequest($data, [ 'student_id' => 'required|integer', 'class_section_id' => 'required|integer', 'homework_index' => 'required|integer', ]); if (!empty($errors)) { return $this->respondValidationError($errors); } $payload = [ 'student_id' => $data['student_id'], 'class_section_id' => $data['class_section_id'], 'homework_index' => $data['homework_index'], 'score' => $data['score'] ?? null, 'comment' => $data['comment'] ?? null, 'school_year' => $this->schoolYear, 'semester' => $this->semester, 'updated_by' => $user->id ?? null, ]; $homework = $this->homework->create($payload); return $this->success($homework, 'Homework created successfully', Response::HTTP_CREATED); } /** * Update a single homework entry */ public function update($id = null) { $homework = $this->homework->find($id); if (!$homework) { return $this->respondError('Homework not found', Response::HTTP_NOT_FOUND); } $user = $this->getCurrentUser(); $data = $this->payloadData(); if (empty($data)) { return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST); } $allowed = ['score', 'comment']; $update = []; foreach ($allowed as $field) { if (array_key_exists($field, $data)) { $update[$field] = $data[$field]; } } if (empty($update)) { return $this->respondError('No valid fields to update', Response::HTTP_BAD_REQUEST); } $update['updated_by'] = $user->id ?? null; $update['updated_at'] = utc_now(); $this->homework->update($id, $update); return $this->success($this->homework->find($id), 'Homework updated successfully'); } /** * Bulk update homework scores for multiple students */ public function updateScores() { $data = $this->payloadData(); $scores = $data['scores'] ?? null; $classSectionId = (int) ($data['class_section_id'] ?? 0); $updatedBy = $this->getCurrentUserId(); if (!$scores || !is_array($scores)) { return $this->respondError('Scores data is required', Response::HTTP_BAD_REQUEST); } if ($classSectionId <= 0) { return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST); } try { foreach ($scores as $studentId => $hwData) { if (!is_numeric($studentId)) continue; $student = $this->student->find($studentId); if (!$student) continue; foreach ($hwData as $index => $score) { // STEP 1: Always fetch the existing record $existing = $this->homework->where([ 'student_id' => $studentId, 'homework_index' => $index, 'class_section_id' => $classSectionId, 'semester' => $this->semester, 'school_year' => $this->schoolYear, ])->first(); // STEP 2: If blank, delete if exists if ($score === '' || $score === null) { if ($existing) { $this->homework->delete($existing['id']); log_message('debug', "Deleted blank score for student $studentId index $index"); } continue; } // STEP 3: Save numeric score if (is_numeric($score)) { $updateData = [ 'student_id' => $studentId, 'school_id' => $student['school_id'] ?? '', 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $index, 'score' => (float)$score, 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'updated_at' => utc_now(), ]; if ($existing) { $this->homework->update($existing['id'], $updateData); log_message('debug', "Updated homework ID {$existing['id']} with score $score"); } else { $updateData['created_at'] = utc_now(); $this->homework->insert($updateData); log_message('debug', "Inserted score for student $studentId index $index"); } } } } // Update semester scores for students in the class section $studentUserInfo = $this->student->getStudentInfoByClassSectionId( $classSectionId, $this->semester, $this->schoolYear ); try { foreach ($studentUserInfo 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'] ?? $updatedBy, ], $this->semester, $this->schoolYear); } } catch (RuntimeException $e) { log_message('error', 'Failed to update semester scores: ' . $e->getMessage()); } return $this->success(null, 'Homework scores updated successfully'); } catch (\Throwable $e) { log_message('error', 'Update homework scores error: ' . $e->getMessage()); return $this->respondError('Failed to update homework scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Add a new homework column/index for all students in a class section */ public function addNextColumn() { $data = $this->payloadData(); $classSectionId = (int) ($data['class_section_id'] ?? 0); $updatedBy = $this->getCurrentUserId(); if ($classSectionId <= 0) { return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST); } try { // Step 1: Get the highest existing homework_index $existingIndexes = $this->homework ->select('homework_index') ->where('class_section_id', $classSectionId) ->where('semester', $this->semester) ->where('school_year', $this->schoolYear) ->groupBy('homework_index') ->orderBy('homework_index', 'DESC') ->findAll(); $maxIndex = 0; foreach ($existingIndexes as $row) { if (isset($row['homework_index']) && is_numeric($row['homework_index'])) { $maxIndex = max($maxIndex, (int)$row['homework_index']); } } $nextIndex = $maxIndex + 1; // Step 2: Get all students in the class $students = $this->studentClass ->where('class_section_id', $classSectionId) ->where('school_year', $this->schoolYear) ->findAll(); // Step 3: Insert a new homework row for each student if not already exists $firstInsertedId = null; $insertedCount = 0; foreach ($students as $i => $student) { $studentId = $student['student_id']; // Check if record already exists $existing = $this->homework ->where('student_id', $studentId) ->where('homework_index', $nextIndex) ->where('class_section_id', $classSectionId) ->where('semester', $this->semester) ->where('school_year', $this->schoolYear) ->first(); if ($existing) continue; $studentRecord = $this->student->find($studentId); $insertData = [ 'student_id' => $studentId, 'school_id' => $studentRecord['school_id'] ?? '', 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $nextIndex, 'score' => null, 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'created_at' => utc_now(), 'updated_at' => utc_now() ]; $id = $this->homework->insert($insertData); if ($i === 0) { $firstInsertedId = $id; } $insertedCount++; } return $this->success([ 'homework_index' => $nextIndex, 'new_homework_id' => $firstInsertedId, 'students_processed' => $insertedCount ], 'Homework column added successfully'); } catch (\Throwable $e) { log_message('error', 'Add homework column error: ' . $e->getMessage()); return $this->respondError('Failed to add homework column: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get homework data for a class section (for management view) */ public function getByClassSection() { $classSectionId = (int) ($this->request->getGet('class_section_id') ?? 0); if ($classSectionId <= 0) { return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST); } try { $homeworkScores = $this->getHomeworkScoresByStudent($classSectionId, $this->semester, $this->schoolYear); $students = $this->getStudentsByClassSectionAndYear($classSectionId, $this->schoolYear); $homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $this->semester, $this->schoolYear); return $this->success([ 'homework_scores' => $homeworkScores, 'students' => $students, 'homework_headers' => $homeworkHeaders, 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'class_section_id' => $classSectionId, 'has_homework_cols' => !empty($homeworkHeaders), ], 'Homework data retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Get homework by class section error: ' . $e->getMessage()); return $this->respondError('Failed to retrieve homework data: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get students with their homework scores */ public function getStudentsWithScores() { $classSectionId = (int) ($this->request->getGet('class_section_id') ?? 0); if ($classSectionId <= 0) { return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST); } try { $homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $this->semester, $this->schoolYear); $students = $this->getStudentsWithHomeworkScores($classSectionId, $homeworkHeaders); return $this->success([ 'students' => $students, 'homework_headers' => $homeworkHeaders, 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'class_section_id' => $classSectionId, ], 'Students with homework scores retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Get students with scores error: ' . $e->getMessage()); return $this->respondError('Failed to retrieve students with scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } // ──────────────── Helper Methods ──────────────── /** * Get students by class section and year */ private function getStudentsByClassSectionAndYear($classSectionId, $schoolYear) { // Step 1: Get student IDs from student_class table $studentClassRows = $this->studentClass ->select('student_id') ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->findAll(); $studentIds = array_column($studentClassRows, 'student_id'); if (empty($studentIds)) { return []; } // Step 2: Get student data from students table $students = $this->student ->whereIn('id', $studentIds) ->orderBy('lastname', 'ASC') ->orderBy('firstname', 'ASC') ->findAll(); return $students; } /** * Get homework scores grouped by student */ private function getHomeworkScoresByStudent($classSectionId, $semester, $schoolYear) { $rows = $this->homework ->select('student_id, homework_index, score') ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->findAll(); $studentScores = []; foreach ($rows as $row) { $studentId = $row['student_id']; $index = $row['homework_index']; $score = $row['score']; if (!isset($studentScores[$studentId])) { $studentScores[$studentId] = ['scores' => []]; } $studentScores[$studentId]['scores'][$index] = $score; } return $studentScores; } /** * Get students with their homework scores */ private function getStudentsWithHomeworkScores($classSectionId, $homeworkHeaders) { $studentClasses = $this->studentClass ->where('class_section_id', $classSectionId) ->where('school_year', $this->schoolYear) ->findAll(); $students = []; foreach ($studentClasses as $sc) { $student = $this->student->find($sc['student_id']); if (!$student) continue; $scores = []; foreach ($homeworkHeaders as $index) { $entry = $this->homework ->where('homework_index', $index) ->where('student_id', $sc['student_id']) ->where('class_section_id', $classSectionId) ->where('semester', $this->semester) ->where('school_year', $this->schoolYear) ->first(); $scores[$index] = $entry['score'] ?? ''; } $students[] = [ 'student_id' => $sc['student_id'], 'firstname' => $student['firstname'], 'lastname' => $student['lastname'], 'scores' => $scores ]; } usort($students, fn($a, $b) => strcmp($a['lastname'], $b['lastname']) ?: strcmp($a['firstname'], $b['firstname'])); return $students; } /** * Get homework headers (unique homework_index values) */ private function getHomeworkHeaders($classSectionId, $semester, $schoolYear) { $rows = $this->homework ->select('homework_index') ->where('class_section_id', $classSectionId) ->where('semester', $semester) ->where('school_year', $schoolYear) ->orderBy('homework_index', 'ASC') ->findAll(); $headers = []; foreach ($rows as $row) { $index = $row['homework_index']; if (!is_array($index)) { $headers[$index] = true; } } ksort($headers); return array_keys($headers); } }