172 lines
6.1 KiB
PHP
172 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Commands;
|
|
|
|
use App\Services\EnrollmentStatusService;
|
|
use CodeIgniter\CLI\BaseCommand;
|
|
use CodeIgniter\CLI\CLI;
|
|
use CodeIgniter\Database\BaseConnection;
|
|
|
|
class SyncStudentActivity extends BaseCommand
|
|
{
|
|
protected $group = 'Enrollment';
|
|
protected $name = 'students:sync-activity';
|
|
protected $description = 'Synchronize students.is_active from current-year enrollment_status.';
|
|
protected $usage = 'php spark students:sync-activity --school-year=YYYY-YYYY [--dry-run]';
|
|
protected $options = [
|
|
'--school-year' => '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 '';
|
|
}
|
|
}
|