db = \Config\Database::connect(); $this->teacherClassModel = new TeacherClassModel(); $this->homeworkModel = new HomeworkModel(); $this->userModel = new UserModel(); $this->studentClassModel = new StudentClassModel(); $this->studentModel = new StudentModel(); $this->configModel = new ConfigurationModel(); $this->schoolYear = $this->configModel->getConfig('school_year'); $this->semester = $this->configModel->getConfig('semester'); // Log the service initialization log_message('debug', 'Initializing SemesterScoreService'); //$this->semesterScoreService = Services::SemesterScoreService(); $this->semesterScoreService = service('semesterScoreService'); // $this->semesterScoreService = \Config\Services::semesterScoreService(); $this->gradingLockModel = new GradingLockModel(); $this->missingScoreOverrideModel = new MissingScoreOverrideModel(); // Check if the service is null if ($this->semesterScoreService === null) { log_message('error', 'SemesterScoreService is null'); } } public function updateHomeworkScores(array $scores = null, int $updatedBy = null, int $classSectionId = null) { $scores = $this->request->getPost('scores'); $semester = $this->request->getPost('semester') ?? $this->semester; $schoolYear = $this->request->getPost('school_year') ?? $this->schoolYear; if ($updatedBy === null) { $updatedBy = session()->get('user_id'); } $classSectionId = $this->request->getPost('class_section_id') ?? session()->get('class_section_id'); $classSectionId = (int) ($classSectionId ?? 0); if ($classSectionId > 0 && $this->isScoresLocked($classSectionId, $semester, $schoolYear)) { return redirect()->back()->with('error', 'Scores are locked for this class. Unlock to edit.'); } $missingOk = $this->request->getPost('missing_ok') ?? []; $studentIds = is_array($scores) ? array_keys($scores) : array_keys((array) $missingOk); $indexSet = []; if (is_array($scores)) { foreach ($scores as $studentId => $hwData) { if (!is_array($hwData)) { continue; } foreach ($hwData as $index => $score) { if (is_numeric($index)) { $indexSet[(int)$index] = true; } } } } $checkedItems = []; if (is_array($missingOk)) { foreach ($missingOk as $studentId => $indexes) { if (!is_array($indexes)) { continue; } foreach ($indexes as $index => $flag) { if (!is_numeric($index)) { continue; } $checkedItems[] = [ 'student_id' => (int) $studentId, 'item_index' => (int) $index, ]; } } } $this->missingScoreOverrideModel->replaceOverrides( $classSectionId, (string) $semester, (string) $schoolYear, 'homework', $studentIds, array_keys($indexSet), $checkedItems, $updatedBy ); if (is_array($scores)) { foreach ($scores as $studentId => $hwData) { if (!is_numeric($studentId)) continue; $student = $this->studentModel->find($studentId); if (!$student) continue; foreach ($hwData as $index => $score) { $rawScore = $score ?? null; $isBlank = $rawScore === null || (is_string($rawScore) && trim($rawScore) === ''); $normalizedScore = (!$isBlank && is_numeric($rawScore)) ? (float) $rawScore : null; // STEP 1: Always fetch the existing record $existing = $this->homeworkModel->where([ 'student_id' => $studentId, 'homework_index' => $index, 'class_section_id' => $classSectionId, //'teacher_id' => $updatedBy, 'semester' => $semester, 'school_year' => $schoolYear, ])->first(); // STEP 2: Save numeric score (blank -> null) $data = [ 'student_id' => $studentId, 'school_id' => $student['school_id'] ?? '', 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $index, 'score' => $normalizedScore, 'semester' => $semester, 'school_year' => $schoolYear, 'updated_at' => utc_now(), ]; if ($isBlank) { if ($existing) { $this->homeworkModel->update($existing['id'], $data); log_message('debug', "Updated homework ID {$existing['id']} with score null"); } else { $data['created_at'] = utc_now(); $this->homeworkModel->insert($data); log_message('debug', "Inserted blank homework for student $studentId index $index"); } continue; } if ($existing) { $this->homeworkModel->update($existing['id'], $data); log_message('debug', "Updated homework ID {$existing['id']} with score {$normalizedScore}"); } else { $data['created_at'] = utc_now(); $this->homeworkModel->insert($data); log_message('debug', "Inserted score for student $studentId index $index"); } } } } $studentUserInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { $this->semesterScoreService->updateScoresForStudents($studentUserInfo, $semester, $schoolYear); } catch (RuntimeException $e) { // Handle error } return redirect()->to(base_url('/teacher/addHomework'))->with('status', 'Homework scores updated successfully.'); } public function addNextHomeworkColumn() { $updatedBy = session()->get('user_id'); $classSectionId = session()->get('class_section_id'); $selectedSemester = $this->getSelectedSemester(); $normalized = $this->normalizeSemesterSelection($selectedSemester); $semesterLabel = $normalized !== '' ? ucfirst($normalized) : $selectedSemester; // Step 1: Get the highest existing homework_index $existingIndexes = $this->homeworkModel ->select('homework_index') ->where('class_section_id', $classSectionId) //->where('teacher_id', $updatedBy) ->whereIn('semester', $this->getSemesterVariants($semesterLabel)) ->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->studentClassModel ->where('class_section_id', $classSectionId) ->findAll(); // Step 3: Insert a new homework row for each student if not already exists $firstInsertedId = null; foreach ($students as $i => $student) { $studentId = $student['student_id']; // Check if record already exists $existing = $this->homeworkModel ->where('student_id', $studentId) ->where('homework_index', $nextIndex) ->where('class_section_id', $classSectionId) //->where('teacher_id', $updatedBy) ->whereIn('semester', $this->getSemesterVariants($semesterLabel)) ->where('school_year', $this->schoolYear) ->first(); if ($existing) continue; $insertData = [ 'student_id' => $studentId, 'school_id' => '', // Optional: populate if needed 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $nextIndex, 'score' => null, 'semester' => $semesterLabel, 'school_year' => $this->schoolYear, 'created_at' => utc_now(), 'updated_at' => utc_now() ]; $id = $this->homeworkModel->insert($insertData, true); if ($i === 0) { $firstInsertedId = $id; } } // Step 4: Return index and ID to frontend return $this->response->setJSON([ 'status' => 'success', 'homework_index' => $nextIndex, 'new_homework_id' => $firstInsertedId ]); } public function showHomework() { $updatedBy = session()->get('user_id'); $classSectionId = $this->getClassSectionIdForTeacher($updatedBy); if (!$classSectionId) { return redirect()->back()->with('status', 'No class section found for the current teacher.'); } session()->set('class_section_id', $classSectionId); $semester = $this->getSelectedSemester(); $homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $semester, $this->schoolYear); if (empty($homeworkHeaders)) { $homeworkHeaders = [1]; } $students = $this->getStudentsWithHomeworkScores( $classSectionId, $homeworkHeaders, $semester, $this->schoolYear ); $missingOkMap = $this->missingScoreOverrideModel->getOverridesMap($classSectionId, $semester, $this->schoolYear, 'homework'); return view('teacher/add_homework', [ 'students' => $students, 'homeworkHeaders' => $homeworkHeaders, 'semester' => $semester, 'schoolYear' => $this->schoolYear, 'class_section_id' => $classSectionId, 'missingOkMap' => $missingOkMap, ]); } // In your GradingController (or the controller that owns this action) public function showHomeworkMngt() { // Accept POST first, then GET (query string), then session fallback $incomingId = $this->request->getPost('class_section_id'); if (!$incomingId) { $incomingId = $this->request->getGet('class_section_id'); } $classSectionId = (int) ($incomingId ?: (session()->get('class_section_id') ?? 0)); if ($classSectionId <= 0) { return redirect()->back()->with('status', 'No class section found for the current teacher or selection.'); } $updatedBy = (int) (session()->get('user_id') ?? 0); $semesterParam = $this->request->getPost('semester') ?? $this->request->getGet('semester') ?? $this->request->getGet('filter'); $normalizedSemester = $this->normalizeSemesterSelection($semesterParam); if ($normalizedSemester !== '') { $label = ucfirst($normalizedSemester); $session = session(); $session->set('teacher_scores_selected_semester', $label); $session->set('semester', $label); } $schoolYearParam = $this->request->getPost('school_year') ?? $this->request->getGet('school_year'); $schoolYear = $this->normalizeSchoolYear($schoolYearParam) ?? $this->schoolYear; /* // OPTIONAL (recommended): ensure the classSectionId belongs to this teacher for the active term $updatedBy = (int) (session()->get('user_id') ?? 0); if ($updatedBy > 0) { // If you have a TeacherClassModel with new schema (position = 'main' or 'ta'): $isOwned = $this->teacherClassModel ->where('teacher_id', $updatedBy) ->where('class_section_id', $classSectionId) ->where('school_year', $this->schoolYear) ->where('semester', $this->semester) ->countAllResults() > 0; if (!$isOwned) { return redirect()->back()->with('status', 'You do not have access to that class section.'); } } */ // Persist chosen class section in the session session()->set('class_section_id', $classSectionId); // Load data for the view $semester = $this->getSelectedSemester(); $homeworkScores = $this->getHomeworkScoresByStudent($classSectionId, $semester, $schoolYear); $students = $this->getStudentsByClassSectionAndYear($classSectionId, $schoolYear); $homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $semester, $schoolYear); $scoresLocked = $this->isScoresLocked($classSectionId, $semester, $schoolYear); // Render return view('grading/homework', [ 'homeworkScores' => $homeworkScores, 'students' => $students, 'semester' => $semester, 'schoolYear' => $schoolYear, 'homeworkHeaders' => $homeworkHeaders, 'classSectionId' => $classSectionId, 'hasHomeworkCols' => !empty($homeworkHeaders), 'isManagement' => true, 'scoresLocked' => $scoresLocked, ]); } public function updateHomework() { $scores = $this->request->getPost('scores'); $studentIds = $this->request->getPost('student_ids'); $semester = $this->request->getPost('semester') ?? $this->getSelectedSemester(); $schoolYear = $this->request->getPost('school_year') ?? $this->schoolYear; $updatedBy = $this->request->getPost('teacher_id') ?? session()->get('user_id'); $classSectionId = $this->request->getPost('class_section_id') ?? session()->get('class_section_id'); // persisted in showHomeworkMngt $classSectionIdInt = (int) ($classSectionId ?? 0); if ($classSectionIdInt > 0 && $this->isScoresLocked($classSectionIdInt, $semester, $schoolYear)) { return redirect()->back()->with('error', 'Scores are locked for this class. Unlock to edit.'); } if ($scores && $studentIds && $semester && $schoolYear && $classSectionId) { $this->updateHomeworkScores($scores, $updatedBy, $classSectionId); session()->setFlashdata('status', 'Homework scores updated successfully.'); // ⬇️ Directly call the method and return its view return $this->showHomeworkMngt($updatedBy); } $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId); // Call the updateScoresForStudents method try { $this->semesterScoreService->updateScoresForStudents($studentTeacherInfo, $semester, $schoolYear); } catch (RuntimeException $e) { // Handle error } return redirect()->back()->with('status', 'Failed to update scores.'); } private function isScoresLocked(int $classSectionId, string $semester, string $schoolYear): bool { return $this->gradingLockModel->isLocked($classSectionId, $semester, $schoolYear); } // ──────────────── Helpers ──────────────── private function saveHomeworkScores(array $scores, array $studentIds, string $semester, string $schoolYear, int $updatedBy, int $classSectionId) { $now = utc_now(); foreach ($studentIds as $studentId) { if (!isset($scores[$studentId])) { continue; } foreach ($scores[$studentId] as $index => $score) { $isBlank = $score === null || (is_string($score) && trim($score) === ''); // Check if the record exists $existing = $this->homeworkModel ->where('student_id', $studentId) ->where('homework_index', $index) ->where('semester', $semester) ->where('school_year', $schoolYear) ->where('class_section_id', $classSectionId) //->where('teacher_id', $updatedBy) ->first(); if ($isBlank) { if ($existing) { $this->homeworkModel->update($existing['id'], [ 'score' => null, 'updated_by' => $updatedBy, 'updated_at' => $now, ]); } else { $this->homeworkModel->insert([ 'student_id' => $studentId, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $index, 'score' => null, 'semester' => $semester, 'school_year' => $schoolYear, 'created_at' => $now, 'updated_at' => $now, ]); } continue; } $data = [ 'student_id' => $studentId, 'class_section_id' => $classSectionId, 'updated_by' => $updatedBy, 'homework_index' => $index, 'score' => is_numeric($score) ? floatval($score) : null, 'semester' => $semester, 'school_year' => $schoolYear, 'updated_at' => $now, ]; if ($existing) { $this->homeworkModel->update($existing['id'], $data); } else { $data['created_at'] = $now; $data['school_id'] = ''; // Set if needed $this->homeworkModel->insert($data); } } } } private function getStudentsByClassSectionAndYear($classSectionId, $schoolYear) { // Step 1: Get student IDs from student_class table $studentClassRows = $this->studentClassModel ->select('student_id') ->where('class_section_id', $classSectionId) ->where('school_year', $schoolYear) ->findAll(); $studentIds = array_column($studentClassRows, 'student_id'); if (empty($studentIds)) { return []; // No students found } // Step 2: Get student data from students table $students = $this->studentModel ->whereIn('id', $studentIds) ->where('is_active', 1) ->orderBy('lastname', 'ASC') ->findAll(); return $students; } private function getClassSectionIdForTeacher($updatedBy) { $class = $this->teacherClassModel->where('teacher_id', $updatedBy)->first(); return $class['class_section_id'] ?? null; } private function getHomeworkScoresByStudent($classSectionId, $semester, $schoolYear) { $semVariants = $this->getSemesterVariants($semester); $rows = $this->homeworkModel ->select('student_id, homework_index, score') ->where('class_section_id', $classSectionId) ->whereIn('semester', $semVariants) ->where('school_year', $schoolYear) ->findAll(); $studentScores = []; foreach ($rows as $row) { $studentId = $row['student_id']; $index = $row['homework_index']; $score = $row['score']; $studentScores[$studentId]['scores'][$index] = $score; } return $studentScores; } private function getStudentsWithHomeworkScores($classSectionId, $homeworkHeaders, $semester, $schoolYear) { $semVariants = $this->getSemesterVariants($semester); $studentClasses = $this->studentClassModel ->active() ->where('student_class.class_section_id', $classSectionId) ->findAll(); $students = []; foreach ($studentClasses as $sc) { $student = $this->studentModel ->where('id', $sc['student_id']) ->where('is_active', 1) ->first(); if (!$student) continue; $scores = []; foreach ($homeworkHeaders as $ids) { $entry = $this->homeworkModel->where('homework_index', $ids) ->where('student_id', $sc['student_id']) ->where('class_section_id', $classSectionId) ->whereIn('semester', $semVariants) ->where('school_year', $schoolYear) ->first(); // Keep null/blank when no entry exists yet $scores[$ids] = $entry['score'] ?? null; } $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; } private function getHomeworkHeaders($classSectionId, $semester, $schoolYear) { $semVariants = $this->getSemesterVariants($semester); $rows = $this->homeworkModel ->select('homework_index') ->where('class_section_id', $classSectionId) //->where('teacher_id', $updatedBy) ->whereIn('semester', $semVariants) ->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); } private function getSemesterVariants(?string $semester): array { $raw = trim((string) $semester); $variants = [ $raw, strtolower($raw), ucfirst(strtolower($raw)), ]; $normalized = $this->normalizeSemesterSelection($raw); if ($normalized === 'fall') { $variants[] = 'First Semester'; $variants[] = 'Semester 1'; $variants[] = '1st Semester'; $variants[] = 'Fall Semester'; } elseif ($normalized === 'spring') { $variants[] = 'Second Semester'; $variants[] = 'Semester 2'; $variants[] = '2nd Semester'; $variants[] = 'Spring Semester'; } return array_values(array_unique(array_filter($variants, static fn($v) => $v !== ''))); } private function getSelectedSemester(): string { $session = session(); $selected = $session->get('teacher_scores_selected_semester'); if (!empty($selected)) { return (string) $selected; } return $session->get('semester') ?? $this->semester; } private function normalizeSemesterSelection(?string $input): string { $value = strtolower(trim((string) $input)); if ($value === 'fall' || str_contains($value, '1')) { return 'fall'; } if ($value === 'spring' || str_contains($value, '2')) { return 'spring'; } return ''; } private function normalizeSchoolYear(?string $input): ?string { $value = trim((string) $input); if ($value === '') { return null; } if (preg_match('/^\\d{4}\\s*[-\\/]\\s*\\d{4}$/', $value)) { return preg_replace('/\\s+/', '', str_replace('/', '-', $value)); } return null; } }