Files
alrahma_sunday_school/app/Commands/RefreshEnrollmentAdminTables.php
2026-08-15 15:07:16 -04:00

218 lines
7.2 KiB
PHP

<?php
namespace App\Commands;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Database\BaseConnection;
use Throwable;
class RefreshEnrollmentAdminTables extends BaseCommand
{
protected $group = 'Registration';
protected $name = 'registration:refresh-admin-tables';
protected $description = 'Refresh enrollment admin dashboard data from the transition service.';
protected $usage = 'php spark registration:refresh-admin-tables [--school-year=2026-2027] [--limit=50] [--dry-run]';
protected $options = [
'--school-year' => 'Target school year name. Defaults to the active/current configured year.',
'--limit' => 'Maximum number of source-year students to process.',
'--dry-run' => 'Preview target/source years and source student count without writing.',
];
private BaseConnection $db;
public function run(array $params)
{
$this->db = \Config\Database::connect();
$options = $this->parseOptions($params);
$schoolYear = trim((string) ($options['school-year'] ?? ''));
if ($schoolYear === '') {
$schoolYear = $this->currentSchoolYear();
}
$sourceYear = $this->previousSchoolYearName($schoolYear);
if ($schoolYear === '' || $sourceYear === null) {
CLI::error('Unable to determine target/source school year.');
return;
}
$limit = (int) ($options['limit'] ?? 0);
$dryRun = ! empty($options['dry-run']);
$students = $this->sourceStudents($sourceYear, $limit);
CLI::write('Enrollment admin refresh', 'cyan');
CLI::write('Target year: ' . $schoolYear);
CLI::write('Source year: ' . $sourceYear);
CLI::write('Source students: ' . count($students));
$before = $this->dashboardCounts($schoolYear);
$this->printCounts('Before', $before);
if ($dryRun) {
CLI::write('Dry-run complete. Re-run without --dry-run to write updates.', 'yellow');
return;
}
$processed = 0;
$eligible = 0;
$blocked = 0;
$errors = 0;
$transitionService = service('enrollmentTransition');
foreach ($students as $student) {
$studentId = (int) ($student['student_id'] ?? 0);
if ($studentId <= 0) {
continue;
}
try {
$evaluation = $transitionService->applyInitialTransition(
$studentId,
$sourceYear,
$schoolYear,
is_numeric($student['parent_id'] ?? null) ? (int) $student['parent_id'] : null,
null,
'admin'
);
$processed++;
if (! empty($evaluation['academic_eligible']) && ($evaluation['blockers'] ?? []) === []) {
$eligible++;
} else {
$blocked++;
}
} catch (Throwable $e) {
$errors++;
CLI::error('Student #' . $studentId . ' failed: ' . $e->getMessage());
}
}
$after = $this->dashboardCounts($schoolYear);
$this->printCounts('After', $after);
CLI::write('Processed: ' . $processed, 'white');
CLI::write('Eligible/applied: ' . $eligible, 'green');
CLI::write('Blocked/flagged: ' . $blocked, $blocked > 0 ? 'yellow' : 'white');
CLI::write('Errors: ' . $errors, $errors > 0 ? 'red' : 'white');
}
private function parseOptions(array $params): array
{
$options = [
'school-year' => '',
'limit' => 0,
'dry-run' => false,
];
$rawParams = array_merge($params, array_slice($_SERVER['argv'] ?? [], 2));
foreach ($rawParams as $param) {
$value = trim((string) $param);
if ($value === '--dry-run') {
$options['dry-run'] = true;
continue;
}
if (! str_starts_with($value, '--') || ! str_contains($value, '=')) {
continue;
}
[$key, $raw] = explode('=', substr($value, 2), 2);
if (array_key_exists($key, $options)) {
$options[$key] = $raw;
}
}
return $options;
}
private function sourceStudents(string $sourceYear, int $limit): array
{
if (! $this->db->tableExists('student_class')) {
return [];
}
$builder = $this->db->table('student_class sc')
->select('sc.student_id, s.parent_id')
->join('students s', 's.id = sc.student_id', 'left')
->where('sc.school_year', $sourceYear)
->groupBy('sc.student_id, s.parent_id')
->orderBy('sc.student_id', 'ASC');
if ($limit > 0) {
$builder->limit($limit);
}
return $builder->get()->getResultArray();
}
private function dashboardCounts(string $schoolYear): array
{
return [
'enrollments' => $this->countRows('enrollments', ['school_year' => $schoolYear]),
'open_flags' => $this->countRows('enrollment_flags', ['school_year' => $schoolYear, 'status' => 'open']),
'audits' => $this->countRows('enrollment_transition_audits', ['school_year' => $schoolYear]),
'exceptions' => $this->countRows('enrollment_exceptions', ['school_year' => $schoolYear]),
'email_records' => $this->countRows('enrollment_email_records', ['school_year' => $schoolYear]),
];
}
private function countRows(string $table, array $where): int
{
if (! $this->db->tableExists($table)) {
return 0;
}
$builder = $this->db->table($table);
foreach ($where as $field => $value) {
$builder->where($field, $value);
}
return $builder->countAllResults();
}
private function printCounts(string $label, array $counts): void
{
CLI::write($label . ' counts:', 'white');
foreach ($counts as $key => $value) {
CLI::write(' ' . $key . ': ' . $value);
}
}
private function currentSchoolYear(): string
{
if ($this->db->tableExists('school_years')) {
$row = $this->db->table('school_years')
->select('name')
->where('status', 'active')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if (! empty($row['name'])) {
return (string) $row['name'];
}
}
if ($this->db->tableExists('configuration')) {
$row = $this->db->table('configuration')
->select('config_value')
->where('config_key', 'school_year')
->limit(1)
->get()
->getRowArray();
if (! empty($row['config_value'])) {
return (string) $row['config_value'];
}
}
return '';
}
private function previousSchoolYearName(string $schoolYear): ?string
{
return preg_match('/^(\d{4})-(\d{4})$/', trim($schoolYear), $matches)
? ((int) $matches[1] - 1) . '-' . ((int) $matches[2] - 1)
: null;
}
}