getSemestersForSchoolYear($schoolYear, $configuredSemester); $semester = $this->resolveSemesterSelection($semester, $semesterOptions, $configuredSemester); $this->ensureParentReleaseKeyExists('Fall'); $this->ensureParentReleaseKeyExists('Spring'); $scoresReleased = $this->getParentScoresReleasedForSemester($semester); $scoresReleasedFall = $this->getParentScoresReleasedForSemester('Fall'); $scoresReleasedSpring = $this->getParentScoresReleasedForSemester('Spring'); $rows = $this->buildGradingRows($semester, $schoolYear); $counts = $this->loadScoreCounts($rows, $semester, $schoolYear); $grades = []; $studentsBySection = []; foreach ($rows as $row) { $sectionId = (int) ($row['section_id'] ?? 0); $classIdValue = (int) ($row['class_id'] ?? 0); $sectionName = (string) ($row['class_section_name'] ?? ''); if ($sectionId <= 0 || $classIdValue <= 0) { continue; } if (! isset($grades[$classIdValue])) { $grades[$classIdValue] = []; } $exists = false; foreach ($grades[$classIdValue] as $section) { if ((int) $section['class_section_id'] === $sectionId) { $exists = true; break; } } if (! $exists) { $grades[$classIdValue][] = [ 'class_section_id' => $sectionId, 'class_section_name' => $sectionName, ]; } $studentId = (int) ($row['student_id'] ?? 0); if ($studentId <= 0) { continue; } $attendanceScore = $this->normalizeScore($row['ss_attendance_score'] ?? null); $studentRow = [ 'id' => $studentId, 'school_id' => $row['school_id'] ?? null, 'firstname' => $row['firstname'] ?? null, 'lastname' => $row['lastname'] ?? null, 'class_id' => $classIdValue, 'ptap' => $this->normalizeScore($row['ss_ptap_score'] ?? null), 'semester_score' => $this->normalizeScore($row['ss_semester_score'] ?? null), 'attendance' => $attendanceScore, 'homework_avg' => $this->normalizeCountedScore($row['ss_homework_avg'] ?? null, $counts['homework'] ?? [], $sectionId, $studentId), 'project_avg' => $this->normalizeCountedScore($row['ss_project_avg'] ?? null, $counts['project'] ?? [], $sectionId, $studentId), 'quiz_avg' => $this->normalizeCountedScore($row['ss_quiz_avg'] ?? null, $counts['quiz'] ?? [], $sectionId, $studentId), 'participation' => $this->normalizeCountedScore($row['ss_participation_score'] ?? null, $counts['participation'] ?? [], $sectionId, $studentId), 'midterm_exam' => $this->normalizeCountedScore($row['ss_midterm_exam_score'] ?? null, $counts['midterm'] ?? [], $sectionId, $studentId), 'final_exam' => $this->normalizeScore($row['ss_final_exam_score'] ?? null), 'matched_biz_csid' => $row['matched_biz_csid'] ?? null, 'matched_pk_csid' => $row['matched_pk_csid'] ?? null, 'placement_level' => $row['placement_level'] ?? null, ]; $studentsBySection[$sectionId][] = $studentRow; } $scoreLocks = $this->loadScoreLocks($grades, $semester, $schoolYear); return [ 'grades' => $grades, 'students_by_section' => $studentsBySection, 'semester' => $semester, 'school_year' => $schoolYear, 'requested_class_id' => $classId, 'semester_options' => $semesterOptions, 'scores_released' => $scoresReleased, 'scores_released_fall' => $scoresReleasedFall, 'scores_released_spring' => $scoresReleasedSpring, 'score_locks' => $scoreLocks, ]; } private function buildGradingRows(string $semester, string $schoolYear): array { $semLower = strtolower(trim($semester)); return DB::table('student_class as sc') ->select([ 'cs.id as section_pk', 'cs.class_section_id as section_id', 'cs.class_id as class_id', 'cs.class_section_name', 's.id as student_id', 's.school_id', 's.firstname', 's.lastname', 'pl.level as placement_level', DB::raw('COALESCE(ss_b.ptap_score, ss_p.ptap_score) as ss_ptap_score'), DB::raw('COALESCE(ss_b.semester_score, ss_p.semester_score) as ss_semester_score'), DB::raw('COALESCE(ss_b.attendance_score, ss_p.attendance_score) as ss_attendance_score'), DB::raw('COALESCE(ss_b.homework_avg, ss_p.homework_avg) as ss_homework_avg'), DB::raw('COALESCE(ss_b.project_avg, ss_p.project_avg) as ss_project_avg'), DB::raw('COALESCE(ss_b.quiz_avg, ss_p.quiz_avg) as ss_quiz_avg'), DB::raw('COALESCE(ss_b.participation_score, ss_p.participation_score) as ss_participation_score'), DB::raw('COALESCE(ss_b.midterm_exam_score, ss_p.midterm_exam_score) as ss_midterm_exam_score'), DB::raw('COALESCE(ss_b.final_exam_score, ss_p.final_exam_score) as ss_final_exam_score'), 'ss_b.class_section_id as matched_biz_csid', 'ss_p.class_section_id as matched_pk_csid', ]) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->join('students as s', 's.id', '=', 'sc.student_id') ->leftJoin('placement_levels as pl', function ($join) use ($schoolYear) { $join->on('pl.student_id', '=', 's.id') ->where('pl.school_year', '=', $schoolYear); }) ->leftJoin('semester_scores as ss_b', function ($join) use ($semLower, $schoolYear) { $join->on('ss_b.student_id', '=', 's.id') ->on('ss_b.class_section_id', '=', 'sc.class_section_id') ->whereRaw('LOWER(TRIM(ss_b.semester)) = ?', [$semLower]) ->where('ss_b.school_year', '=', $schoolYear); }) ->leftJoin('semester_scores as ss_p', function ($join) use ($semLower, $schoolYear) { $join->on('ss_p.student_id', '=', 's.id') ->on('ss_p.class_section_id', '=', 'cs.id') ->whereRaw('LOWER(TRIM(ss_p.semester)) = ?', [$semLower]) ->where('ss_p.school_year', '=', $schoolYear); }) ->where('sc.school_year', $schoolYear) ->where('s.is_active', 1) ->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_section_name', 'ASC') ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get() ->map(fn ($row) => (array) $row) ->all(); } private function loadScoreCounts(array $rows, string $semester, string $schoolYear): array { $sectionIds = []; $studentIds = []; foreach ($rows as $row) { $studentId = (int) ($row['student_id'] ?? 0); $sectionId = (int) ($row['section_id'] ?? 0); if ($studentId > 0) { $studentIds[$studentId] = true; } if ($sectionId > 0) { $sectionIds[$sectionId] = true; } } $sectionIds = array_keys($sectionIds); $studentIds = array_keys($studentIds); if (empty($sectionIds) || empty($studentIds)) { return [ 'quiz' => [], 'homework' => [], 'project' => [], 'participation' => [], 'midterm' => [], ]; } return [ 'quiz' => $this->countScores('quiz', $sectionIds, $studentIds, $semester, $schoolYear), 'homework' => $this->countScores('homework', $sectionIds, $studentIds, $semester, $schoolYear), 'project' => $this->countScores('project', $sectionIds, $studentIds, $semester, $schoolYear), 'participation' => $this->countScores('participation', $sectionIds, $studentIds, $semester, $schoolYear), 'midterm' => $this->countScores('midterm_exam', $sectionIds, $studentIds, $semester, $schoolYear), ]; } private function countScores(string $table, array $sectionIds, array $studentIds, string $semester, string $schoolYear): array { $rows = DB::table($table) ->select('student_id', 'class_section_id', DB::raw('COUNT(*) as cnt')) ->where('semester', $semester) ->where('school_year', $schoolYear) ->whereIn('class_section_id', $sectionIds) ->whereIn('student_id', $studentIds) ->whereNotNull('score') ->groupBy('student_id', 'class_section_id') ->get(); $counts = []; foreach ($rows as $row) { $counts[(int) $row->class_section_id][(int) $row->student_id] = (int) $row->cnt; } return $counts; } private function loadScoreLocks(array $grades, string $semester, string $schoolYear): array { $sectionIds = []; foreach ($grades as $sections) { foreach ($sections as $section) { $sid = (int) ($section['class_section_id'] ?? 0); if ($sid > 0) { $sectionIds[$sid] = true; } } } $sectionIds = array_keys($sectionIds); if (empty($sectionIds)) { return []; } $locks = GradingLock::query() ->whereIn('class_section_id', $sectionIds) ->where('semester', $semester) ->where('school_year', $schoolYear) ->get(); $map = []; foreach ($locks as $lock) { $map[(int) $lock->class_section_id] = (bool) $lock->is_locked; } return $map; } private function normalizeScore($value): ?float { if ($value === null || $value === '') { return null; } return round((float) $value, 2); } private function normalizeCountedScore($value, array $countMap, int $sectionId, int $studentId): ?float { $score = $this->normalizeScore($value); if ($score !== null && (float) $score === 0.0) { $count = (int) ($countMap[$sectionId][$studentId] ?? 0); if ($count === 0) { return null; } } return $score; } private function resolveSemesterSelection(?string $requestedSemester, array $semesterOptions, ?string $fallbackSemester): string { if ($requestedSemester !== null && $requestedSemester !== '') { foreach ($semesterOptions as $option) { if (strcasecmp($option, $requestedSemester) === 0) { return $option; } } return $requestedSemester; } if ($fallbackSemester !== null && $fallbackSemester !== '') { foreach ($semesterOptions as $option) { if (strcasecmp($option, $fallbackSemester) === 0) { return $option; } } return $fallbackSemester; } return $semesterOptions[0] ?? ''; } private function getSemestersForSchoolYear(string $schoolYear, ?string $fallbackSemester = null): array { $rows = DB::table('semester_scores') ->select(DB::raw('DISTINCT semester')) ->whereNotNull('semester') ->where('semester', '!=', '') ->where('school_year', $schoolYear) ->orderBy('semester', 'ASC') ->get(); $semesters = []; foreach ($rows as $row) { $value = trim((string) $row->semester); if ($value !== '') { $semesters[] = $value; } } if (empty($semesters)) { $semesters = ['Fall', 'Spring']; } if ($fallbackSemester !== null && $fallbackSemester !== '' && ! in_array($fallbackSemester, $semesters, true)) { array_unshift($semesters, $fallbackSemester); } return array_values(array_unique($semesters)); } private function getParentReleaseKey(string $semester): ?string { $norm = strtolower(trim($semester)); if ($norm === 'fall') { return 'parent_scores_released_fall'; } if ($norm === 'spring') { return 'parent_scores_released_spring'; } return null; } private function getParentScoresReleasedForSemester(string $semester): bool { $key = $this->getParentReleaseKey($semester); $raw = $key ? Configuration::getConfig($key) : null; $raw = (string) ($raw ?? ''); return in_array(strtolower(trim($raw)), ['1', 'true', 'yes', 'y', 'on'], true); } private function ensureParentReleaseKeyExists(string $semester): void { $key = $this->getParentReleaseKey($semester); if (! $key) { return; } if (Configuration::getConfigValueByKey($key) === null) { Configuration::setConfigValueByKey($key, '0'); } } }