'School year to use as the controlling enrollment year.', '--dry-run' => 'Report changes without updating students.', ]; private BaseConnection $db; private EnrollmentStatusService $statusService; public function run(array $params) { $this->db = \Config\Database::connect(); $this->statusService = new EnrollmentStatusService($this->db); $schoolYear = $this->optionValue('school-year'); if ($schoolYear === '') { $schoolYear = $this->statusService->currentSchoolYear(); } if ($schoolYear === '') { CLI::error('Missing --school-year and no configured current school year was found.'); return EXIT_ERROR; } $dryRun = CLI::getOption('dry-run') !== null; $duplicates = $this->duplicates($schoolYear); $unknownStatuses = []; $withoutEnrollment = []; $mismatches = []; $updated = 0; $students = $this->db->table('students') ->select('id, school_id, firstname, lastname, is_active') ->orderBy('id', 'ASC') ->get() ->getResultArray(); foreach ($students as $student) { $studentId = (int) $student['id']; $enrollment = $this->statusService->controllingEnrollment($studentId, $schoolYear); if ($enrollment === null) { $expected = 0; $withoutEnrollment[] = $this->studentLabel($student); } else { try { $status = $this->statusService->normalizeStatus((string) ($enrollment['enrollment_status'] ?? '')); $expected = $this->statusService->activeFlagForStatus($status); } catch (\InvalidArgumentException $e) { $unknownStatuses[] = [ 'student' => $this->studentLabel($student), 'enrollment_id' => (int) ($enrollment['id'] ?? 0), 'status' => (string) ($enrollment['enrollment_status'] ?? ''), ]; continue; } } $actual = (int) ($student['is_active'] ?? 1); if ($actual !== $expected) { $mismatches[] = [ 'student' => $this->studentLabel($student), 'current' => $actual, 'expected' => $expected, 'status' => (string) ($enrollment['enrollment_status'] ?? 'no enrollment'), ]; if (! $dryRun) { $this->db->table('students')->where('id', $studentId)->update(['is_active' => $expected]); $updated++; } } } CLI::write('School year: ' . $schoolYear); CLI::write('Dry run: ' . ($dryRun ? 'yes' : 'no')); CLI::write('Duplicate enrollment groups: ' . count($duplicates)); foreach ($duplicates as $row) { CLI::write(sprintf( ' student_id=%d school_year=%s semester=%s count=%d', (int) $row['student_id'], (string) $row['school_year'], (string) ($row['semester'] ?? ''), (int) $row['row_count'] )); } CLI::write('Unknown statuses: ' . count($unknownStatuses)); foreach ($unknownStatuses as $row) { CLI::write(sprintf(' %s enrollment_id=%d status=%s', $row['student'], $row['enrollment_id'], $row['status'])); } CLI::write('Students without current enrollment: ' . count($withoutEnrollment)); foreach (array_slice($withoutEnrollment, 0, 50) as $label) { CLI::write(' ' . $label); } if (count($withoutEnrollment) > 50) { CLI::write(' ... ' . (count($withoutEnrollment) - 50) . ' more'); } CLI::write('Mismatches: ' . count($mismatches)); foreach ($mismatches as $row) { CLI::write(sprintf( ' %s current=%d expected=%d status=%s', $row['student'], $row['current'], $row['expected'], $row['status'] )); } if (! $dryRun) { CLI::write('Updated students: ' . $updated); } return EXIT_SUCCESS; } private function duplicates(string $schoolYear): array { return $this->db->table('enrollments') ->select('student_id, school_year, semester, COUNT(*) AS row_count') ->where('school_year', $schoolYear) ->groupBy('student_id, school_year, semester') ->having('COUNT(*) >', 1) ->orderBy('student_id', 'ASC') ->get() ->getResultArray(); } private function studentLabel(array $student): string { $name = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')); return '#' . (int) ($student['id'] ?? 0) . ($name !== '' ? ' ' . $name : ''); } private function optionValue(string $name): string { $value = CLI::getOption($name); if (is_string($value) && trim($value) !== '') { return trim($value); } $argv = $_SERVER['argv'] ?? []; $prefix = '--' . $name . '='; foreach ($argv as $index => $arg) { if (is_string($arg) && str_starts_with($arg, $prefix)) { return trim(substr($arg, strlen($prefix))); } if ($arg === '--' . $name && isset($argv[$index + 1])) { return trim((string) $argv[$index + 1]); } } return ''; } }