Files
alrahma_sunday_school/app/Services/StaffDirectorySyncService.php
T
root 09ea144bb2
Tests / PHPUnit (push) Failing after 34s
fix issues related to school year
2026-07-31 18:52:25 -04:00

233 lines
7.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\ConfigurationModel;
use App\Models\StaffModel;
use App\Models\UserModel;
use CodeIgniter\Database\BaseConnection;
final class StaffDirectorySyncService
{
private const WORK_EMAIL_DOMAIN = '@alrahmaisgl.org';
public function __construct(
private readonly StaffModel $staffModel,
private readonly UserModel $userModel,
private readonly ConfigurationModel $configurationModel,
private readonly BaseConnection $db,
) {
}
public function syncAll(): void
{
$rows = $this->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;
}
}
}