> */ public function fetchStudentList(?string $schoolYear, array $selectedStudentIds = []): array { $selectedStudentIds = $this->formatter->normalizeIds($selectedStudentIds); $classSectionAggregate = DB::connection()->getDriverName() === 'sqlite' ? 'GROUP_CONCAT(DISTINCT cs.class_section_name) as class_section_name' : "GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ', ') as class_section_name"; $query = DB::table('students') ->leftJoin('student_class as sc', function ($join) use ($schoolYear) { $join->on('sc.student_id', '=', 'students.id'); if (! empty($schoolYear)) { $join->where('sc.school_year', '=', $schoolYear); } }) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id') ->select([ 'students.id as student_id', 'students.firstname', 'students.lastname', 'students.registration_grade', 'students.school_id', DB::raw('COALESCE(MAX(sc.school_year), ?) as school_year'), DB::raw($classSectionAggregate), ]) ->addBinding((string) ($schoolYear ?? ''), 'select') ->where('students.is_active', 1) ->groupBy( 'students.id', 'students.firstname', 'students.lastname', 'students.registration_grade', 'students.school_id' ) ->orderBy('students.lastname') ->orderBy('students.firstname'); if (! empty($selectedStudentIds)) { $query->whereIn('students.id', $selectedStudentIds); } elseif (! empty($schoolYear)) { $query->whereNotNull('sc.student_id'); } $rows = array_map(static fn ($row) => (array) $row, $query->get()->all()); $out = []; foreach ($rows as $row) { $studentId = (int) ($row['student_id'] ?? 0); if ($studentId <= 0) { continue; } $schoolId = trim((string) ($row['school_id'] ?? '')); if ($schoolId === '' || ! preg_match('/^[A-Za-z0-9_-]+$/', $schoolId)) { continue; } $out[] = [ 'student_id' => $studentId, 'firstname' => (string) ($row['firstname'] ?? ''), 'lastname' => (string) ($row['lastname'] ?? ''), 'role_name' => 'Student', 'roles' => 'Student', 'registration_grade' => (string) ($row['registration_grade'] ?? ''), 'class_section_name' => $this->formatter->norm((string) ($row['class_section_name'] ?? '')), 'school_id' => $schoolId, 'school_year' => (string) ($row['school_year'] ?? $schoolYear ?? ''), ]; } return $out; } /** * Load students for badge printing: valid school_id (QR payload), ordered by name. * * @param int[] $studentIds * @return array */ public function fetchForBadges(array $studentIds, ?string $schoolYear): array { $studentIds = $this->formatter->normalizeIds($studentIds); if ($studentIds === []) { return []; } $rows = Student::query() ->whereIn('id', $studentIds) ->where('is_active', 1) ->orderBy('lastname') ->orderBy('firstname') ->get(); $out = []; foreach ($rows as $student) { $schoolId = trim((string) ($student->school_id ?? '')); if ($schoolId === '' || ! preg_match('/^[A-Za-z0-9_-]+$/', $schoolId)) { continue; } $fullname = Student::getFullName($student->toArray()); $grade = $this->resolveGrade((int) $student->id, (string) ($student->registration_grade ?? '')); $academicYear = $schoolYear ?? (string) ($student->school_year ?? ''); $academicYear = $this->formatter->norm($academicYear); $out[] = [ 'student_id' => (int) $student->id, 'fullname' => $fullname !== '' ? $fullname : 'Student', 'grade' => $grade, 'academic_year' => $academicYear, 'school_id' => $schoolId, ]; } return $out; } protected function resolveGrade(int $studentId, string $registrationGrade): string { $registrationGrade = $this->formatter->norm($registrationGrade); if ($registrationGrade !== '') { return $registrationGrade; } try { $g = StudentClass::getStudentGrade($studentId); $g = $this->formatter->norm((string) $g); return $g !== '' ? $g : '—'; } catch (\Throwable) { return '—'; } } }