db->table('users') ->select('id') ->get() ->getResultArray(); foreach ($rows as $row) { $userId = (int) ($row['id'] ?? 0); if ($userId > 0) { $this->syncUser($userId); } } } public function syncUser(int $userId): void { if ($userId <= 0) { return; } $user = $this->userModel->find($userId); if (! $user) { return; } $roles = $this->rolesForUser($userId); $staffRoles = array_values(array_filter($roles, static function (array $role): bool { return strtolower((string) ($role['name'] ?? '')) !== 'parent'; })); $now = utc_now(); $schoolYear = (string) ($this->configurationModel->getConfig('school_year') ?? ''); $roleNames = array_values(array_unique(array_map( static fn (array $role): string => strtolower((string) ($role['name'] ?? '')), $roles ))); $activeRole = ! empty($staffRoles) ? strtolower((string) ($staffRoles[0]['name'] ?? '')) : 'inactive'; $this->staffModel->upsert([ 'user_id' => $userId, 'firstname' => (string) ($user['firstname'] ?? ''), 'lastname' => (string) ($user['lastname'] ?? ''), 'email' => $this->generateUniqueStaffEmail( (string) ($user['firstname'] ?? ''), (string) ($user['lastname'] ?? ''), $userId ), 'phone' => (string) ($user['cellphone'] ?? ''), 'role_name' => implode(', ', $roleNames), 'active_role' => $activeRole, 'school_year' => $schoolYear, 'updated_at' => $now, ]); } public function activeStaffForSchoolYear(string $schoolYear): array { $this->syncAll(); $staffRows = $this->db->table('users u') ->select([ 'u.id AS user_id', 'u.school_id', 'u.firstname', 'u.lastname', 'u.email AS personal_email', 'u.cellphone', 's.id AS staff_id', 's.email AS work_email', 's.active_role', 's.role_name', "GROUP_CONCAT(DISTINCT r.name ORDER BY COALESCE(r.priority, 999), r.name SEPARATOR ', ') AS roles", ]) ->join('user_roles ur', 'ur.user_id = u.id', 'inner') ->join('roles r', 'r.id = ur.role_id', 'inner') ->join('staff s', 's.user_id = u.id', 'left') ->where("LOWER(r.name) != 'parent'", null, false) ->where('COALESCE(r.is_active, 1) = 1', null, false); if ($this->hasField('user_roles', 'deleted_at')) { $staffRows->where('ur.deleted_at', null); } $rows = $staffRows ->groupBy('u.id, u.school_id, u.firstname, u.lastname, u.email, u.cellphone, s.id, s.email, s.active_role, s.role_name') ->orderBy('u.lastname', 'ASC') ->orderBy('u.firstname', 'ASC') ->get() ->getResultArray(); $assignments = $this->assignmentsByTeacher($schoolYear); $issuesCount = 0; foreach ($rows as &$row) { $userId = (int) ($row['user_id'] ?? 0); $activeRole = strtolower((string) ($row['active_role'] ?? '')); $roleText = strtolower((string) ($row['roles'] ?? '')); $isTeacher = in_array($activeRole, ['teacher', 'teacher_assistant'], true) || str_contains($roleText, 'teacher') || preg_match('/(^|, )ta($|,)/', $roleText); $labels = $assignments[$userId] ?? []; if ($isTeacher) { if ($labels !== []) { $row['class_section'] = implode(', ', array_unique($labels)); $row['verification_issue'] = false; } else { $row['class_section'] = 'No class assigned'; $row['verification_issue'] = true; $issuesCount++; } } else { $row['class_section'] = '-'; $row['verification_issue'] = false; } } unset($row); return [ 'staff' => $rows, 'issues_count' => $issuesCount, ]; } private function rolesForUser(int $userId): array { $builder = $this->db->table('user_roles ur') ->select('r.name, r.priority') ->join('roles r', 'r.id = ur.role_id', 'inner') ->where('ur.user_id', $userId) ->where('COALESCE(r.is_active, 1) = 1', null, false) ->orderBy('COALESCE(r.priority, 999)', 'ASC', false) ->orderBy('r.name', 'ASC'); if ($this->hasField('user_roles', 'deleted_at')) { $builder->where('ur.deleted_at', null); } return $builder->get()->getResultArray(); } private function assignmentsByTeacher(string $schoolYear): array { if ($schoolYear === '' || ! $this->db->tableExists('teacher_class')) { return []; } $rows = $this->db->table('teacher_class tc') ->select('tc.teacher_id, tc.position, cs.class_section_name') ->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'left') ->where('tc.school_year', $schoolYear) ->get() ->getResultArray(); $assignments = []; foreach ($rows as $row) { $teacherId = (int) ($row['teacher_id'] ?? 0); $name = trim((string) ($row['class_section_name'] ?? '')); $position = strtolower((string) ($row['position'] ?? '')); if ($teacherId <= 0 || $name === '' || ! in_array($position, ['main', 'ta'], true)) { continue; } $assignments[$teacherId][] = $name . ' (' . $position . ')'; } return $assignments; } private function generateUniqueStaffEmail(string $firstname, string $lastname, int $userId): string { $first = strtolower((string) preg_replace('/[^a-z]/i', '', $firstname)); $last = strtolower((string) preg_replace('/[^a-z]/i', '', $lastname)); if ($last === '') { $last = 'user' . $userId; } $base = $last; $max = max(1, strlen($first)); for ($i = 1; $i <= $max; $i++) { $base = substr($first, 0, $i) . $last; $email = $base . self::WORK_EMAIL_DOMAIN; $exists = $this->staffModel ->where('email', $email) ->where('user_id !=', $userId) ->first(); if (! $exists) { return $email; } } return $base . $userId . self::WORK_EMAIL_DOMAIN; } private function hasField(string $table, string $field): bool { try { return $this->db->tableExists($table) && $this->db->fieldExists($field, $table); } catch (\Throwable) { return false; } } }