db->tableExists('student_year_status')) { return true; } $row = $this->yearStatusModel ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->first(); if (! is_array($row)) { return true; } return (int) ($row['is_new'] ?? 1) === 1; } public function upsert(int $studentId, string $schoolYear, bool $isNew): bool { if ($studentId <= 0) { return false; } $schoolYear = trim($schoolYear); if (! preg_match('/^\d{4}-\d{4}$/', $schoolYear) || ! $this->db->tableExists('student_year_status')) { return false; } $flag = $isNew ? 1 : 0; $existing = $this->yearStatusModel ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->first(); if (is_array($existing) && isset($existing['id'])) { return (bool) $this->yearStatusModel->update((int) $existing['id'], [ 'is_new' => $flag, ]); } return (bool) $this->yearStatusModel->insert([ 'student_id' => $studentId, 'school_year' => $schoolYear, 'is_new' => $flag, ]); } public function hasStatus(int $studentId, string $schoolYear): bool { if ($studentId <= 0) { return false; } $schoolYear = trim($schoolYear); if (! preg_match('/^\d{4}-\d{4}$/', $schoolYear) || ! $this->db->tableExists('student_year_status')) { return false; } return $this->yearStatusModel ->where('student_id', $studentId) ->where('school_year', $schoolYear) ->countAllResults() > 0; } /** * Mark students who existed in a source school year as returning in the target year. * * This is intentionally idempotent so school-year activation/closing and repair * migrations can call it safely. */ public function markReturningStudentsForTargetYear(string $sourceSchoolYear, string $targetSchoolYear): int { $sourceSchoolYear = trim($sourceSchoolYear); $targetSchoolYear = trim($targetSchoolYear); if ( ! preg_match('/^\d{4}-\d{4}$/', $sourceSchoolYear) || ! preg_match('/^\d{4}-\d{4}$/', $targetSchoolYear) || $sourceSchoolYear === $targetSchoolYear || ! $this->db->tableExists('student_year_status') ) { return 0; } $studentIds = $this->returningStudentIds($sourceSchoolYear); foreach ($studentIds as $studentId) { $this->upsert($studentId, $targetSchoolYear, false); } return count($studentIds); } /** * @param list> $students */ public function attachToStudents(array &$students, string $schoolYear): void { $schoolYear = trim($schoolYear); if ($students === [] || ! preg_match('/^\d{4}-\d{4}$/', $schoolYear)) { return; } $ids = []; foreach ($students as $student) { $id = (int) ($student['id'] ?? $student['student_id'] ?? 0); if ($id > 0) { $ids[$id] = true; } } $flags = $this->flagsForStudents(array_keys($ids), $schoolYear); foreach ($students as &$student) { $id = (int) ($student['id'] ?? $student['student_id'] ?? 0); if ($id <= 0) { continue; } $student['is_new'] = $flags[$id] ?? 1; } unset($student); } /** * @param list $studentIds * @return array */ public function flagsForStudents(array $studentIds, string $schoolYear): array { $studentIds = array_values(array_unique(array_filter( array_map('intval', $studentIds), static fn(int $id): bool => $id > 0 ))); $schoolYear = trim($schoolYear); if ($studentIds === [] || ! preg_match('/^\d{4}-\d{4}$/', $schoolYear)) { return []; } $flags = []; foreach ($studentIds as $studentId) { $flags[$studentId] = 1; } if (! $this->db->tableExists('student_year_status')) { return $flags; } $rows = $this->yearStatusModel ->select('student_id, is_new') ->whereIn('student_id', $studentIds) ->where('school_year', $schoolYear) ->findAll(); foreach ($rows as $row) { $id = (int) ($row['student_id'] ?? 0); if ($id > 0) { $flags[$id] = (int) ($row['is_new'] ?? 1) === 1 ? 1 : 0; } } return $flags; } /** * @return list */ private function returningStudentIds(string $sourceSchoolYear): array { $ids = []; foreach (['student_year_status', 'student_class', 'enrollments'] as $table) { if (! $this->db->tableExists($table) || ! $this->db->fieldExists('school_year', $table)) { continue; } $rows = $this->db->table($table) ->select('student_id') ->where('school_year', $sourceSchoolYear) ->where('student_id IS NOT NULL', null, false) ->groupBy('student_id') ->get() ->getResultArray(); foreach ($rows as $row) { $studentId = (int) ($row['student_id'] ?? 0); if ($studentId > 0) { $ids[$studentId] = true; } } } return array_keys($ids); } public function activeSchoolYear(): ?string { if ($this->db->tableExists('school_years')) { $row = $this->db->table('school_years') ->select('name') ->where('status', 'active') ->orderBy('id', 'DESC') ->get(1) ->getRowArray(); $name = trim((string) ($row['name'] ?? '')); if (preg_match('/^\d{4}-\d{4}$/', $name)) { return $name; } } if (! $this->db->tableExists('configuration')) { return null; } $row = $this->db->table('configuration') ->select('config_value') ->where('config_key', 'school_year') ->orderBy('id', 'DESC') ->get(1) ->getRowArray(); $name = trim((string) ($row['config_value'] ?? '')); return preg_match('/^\d{4}-\d{4}$/', $name) ? $name : null; } }