fix issues related to school year
Tests / PHPUnit (push) Failing after 34s

This commit is contained in:
root
2026-07-31 18:52:25 -04:00
parent 8d7ee3b9fc
commit 09ea144bb2
23 changed files with 537 additions and 218 deletions
@@ -2309,6 +2309,8 @@ class AdministratorController extends BaseController
]);
}
service('staffDirectorySync')->syncUser((int) $userId);
return redirect()->to('/administrator/manage-users');
}
@@ -72,13 +72,18 @@ class HomeworkTrackingController extends BaseController
}
$limitToSemester = $this->hasTeacherAssignments($this->schoolYear, $this->semester);
$semesterVariants = $this->getSemesterVariants($this->semester);
// Aggregate homework presence + first entered date per class_section_id + homework_index
// Aggregate submitted homework per class_section_id + homework_index.
// Adding a homework column creates blank rows for every student, so only
// indexes with at least one non-null score count as submitted.
$hwQ = $this->db->table('homework')
->select('class_section_id, homework_index, MIN(created_at) AS first_created, COUNT(*) AS cnt')
->select('class_section_id, homework_index')
->select('MIN(CASE WHEN score IS NOT NULL THEN updated_at ELSE NULL END) AS first_submitted', false)
->select('COUNT(score) AS scored_count', false)
->where('school_year', $this->schoolYear);
if ($limitToSemester && $this->semester !== '') {
$hwQ->where('semester', $this->semester);
if ($limitToSemester && $semesterVariants !== []) {
$hwQ->whereIn('semester', $semesterVariants);
}
$rows = $hwQ->groupBy('class_section_id, homework_index')
->get()
@@ -90,60 +95,32 @@ class HomeworkTrackingController extends BaseController
foreach ($rows as $r) {
$csid = (int)($r['class_section_id'] ?? 0);
$hi = (int)($r['homework_index'] ?? 0);
$cnt = (int)($r['cnt'] ?? 0);
$cnt = (int)($r['scored_count'] ?? 0);
if ($csid > 0 && $hi > 0 && $cnt > 0) {
$hasHomework[$csid][$hi] = true;
$dateStr = substr((string)($r['first_created'] ?? ''), 0, 10);
$dateStr = substr((string)($r['first_submitted'] ?? ''), 0, 10);
$hwEnteredAt[$csid][$hi] = $dateStr ?: null;
$homeworkSubmissionCounts[$csid] = ($homeworkSubmissionCounts[$csid] ?? 0) + 1;
}
}
// Build date-based presence mapped to the nearest prior non-NoSchool Sunday.
$hb = $this->db->table('homework')
->select("class_section_id, DATE(created_at) AS hw_date, MIN(created_at) AS first_created, COUNT(*) AS cnt", false)
->where('school_year', $this->schoolYear);
if ($limitToSemester && $this->semester !== '') {
$hb->where('semester', $this->semester);
$indexToDate = [];
foreach ($dateToIndex as $ymd => $homeworkIndex) {
if ($homeworkIndex !== null) {
$indexToDate[(int) $homeworkIndex] = $ymd;
}
}
$rowsByDate = $hb->groupBy('class_section_id, DATE(created_at)', '', false)
->orderBy('hw_date', 'ASC', false)
->get()
->getResultArray();
$hasHomeworkByDate = [];
$hwEnteredAtByDate = [];
foreach ($rowsByDate as $r) {
$csid = (int)($r['class_section_id'] ?? 0);
$d = substr((string)($r['hw_date'] ?? ''), 0, 10);
$cnt = (int)($r['cnt'] ?? 0);
$firstCreated = substr((string)($r['first_created'] ?? ''), 0, 10);
if ($csid <= 0 || !$d || $cnt <= 0) { continue; }
// Find the index of the last Sunday on or before $d
$baseIndex = -1;
for ($i = count($sundays) - 1; $i >= 0; $i--) {
if ($sundays[$i] <= $d) { $baseIndex = $i; break; }
}
if ($baseIndex < 0) { continue; }
// Map this homework day to only the nearest prior nonNo School Sunday
$j = $baseIndex;
while ($j >= 0 && !empty($eventDays[$sundays[$j]])) { $j--; }
if ($j < 0) { continue; }
$sd = $sundays[$j];
// Mark homework on that Sunday for this class_section (single mapping)
$hasHomeworkByDate[$csid][$sd] = true;
if (empty($hwEnteredAtByDate[$csid][$sd])) {
$hwEnteredAtByDate[$csid][$sd] = $firstCreated ?: $d;
} else {
// Keep the earliest date for display
$existing = (string)$hwEnteredAtByDate[$csid][$sd];
$candidate = $firstCreated ?: $d;
if ($candidate && (!$existing || $candidate < $existing)) {
$hwEnteredAtByDate[$csid][$sd] = $candidate;
foreach ($hasHomework as $csid => $indexes) {
foreach ($indexes as $homeworkIndex => $_submitted) {
$ymd = $indexToDate[(int) $homeworkIndex] ?? null;
if ($ymd === null) {
continue;
}
$hasHomeworkByDate[(int) $csid][$ymd] = true;
$hwEnteredAtByDate[(int) $csid][$ymd] = $hwEnteredAt[(int) $csid][(int) $homeworkIndex] ?? null;
}
}
@@ -252,6 +229,21 @@ class HomeworkTrackingController extends BaseController
return $this->teacherAssignmentCache[$year];
}
private function getSemesterVariants(string $semester): array
{
$trimmed = trim($semester);
if ($trimmed === '') {
return [];
}
return array_values(array_unique([
$trimmed,
ucfirst(strtolower($trimmed)),
strtolower($trimmed),
strtoupper($trimmed),
]));
}
/**
* Compute start/end dates for the given semester within the school year.
* Fall: 09/21/{startYear} to 01/18/{startYear+1}
@@ -9,6 +9,7 @@ use App\Models\UserModel;
use App\Models\UserRoleModel;
use App\Models\ConfigurationModel;
use App\Models\StaffModel;
use App\Services\StaffDirectorySyncService;
use CodeIgniter\Controller;
class RolePermissionController extends Controller
@@ -20,6 +21,7 @@ class RolePermissionController extends Controller
protected $configModel;
protected $permissionModel;
protected $rolePermissionModel;
protected StaffDirectorySyncService $staffDirectorySync;
protected $request;
protected $db;
protected $schoolYear;
@@ -31,6 +33,7 @@ class RolePermissionController extends Controller
$this->userModel = new UserModel();
$this->userRoleModel = new UserRoleModel();
$this->staffModel = new StaffModel();
$this->staffDirectorySync = service('staffDirectorySync');
$this->configModel = new ConfigurationModel();
$this->permissionModel = new PermissionModel();
$this->rolePermissionModel = new RolePermissionModel();
@@ -224,49 +227,7 @@ class RolePermissionController extends Controller
private function updateStaffRecord(int $userId, array $newRoleNames): void
{
$excludedRoles = ['parent', 'student', 'guest'];
$loweredNewRoles = array_map('strtolower', $newRoleNames);
$user = $this->userModel->find($userId);
if (!$user) {
log_message('error', "updateStaffRecord: user_id {$userId} not found");
return;
}
// Fetch existing staff record if it exists
$existingStaff = $this->staffModel->where('user_id', $userId)->first();
// Extract existing roles from DB if any
$existingRoles = [];
if (!empty($existingStaff['role_name'])) {
$existingRoles = array_map('strtolower', array_map('trim', explode(',', $existingStaff['role_name'])));
}
// Merge and deduplicate roles
$allRoles = array_unique(array_merge($existingRoles, $loweredNewRoles));
// Determine staff roles (excluding parent/student/guest)
$staffRoles = array_filter($newRoleNames, fn($role) => !in_array($role, $excludedRoles));
// Determine active role
$activeRole = !empty($staffRoles) ? $staffRoles[0] : 'inactive';
$email = $this->generateUniqueStaffEmail($user['firstname'], $user['lastname'], $userId);
$now = utc_now();
$row = [
'user_id' => $userId,
'firstname' => $user['firstname'],
'lastname' => $user['lastname'],
'email' => $email,
'phone' => $user['cellphone'],
'role_name' => implode(', ', $allRoles), // historical roles
'active_role' => $activeRole,
'school_year' => $this->schoolYear,
'updated_at' => $now,
];
$this->staffModel->upsert($row);
$this->staffDirectorySync->syncUser($userId);
}
private function generateUniqueStaffEmail(string $firstname, string $lastname, int $userId): string
+6 -53
View File
@@ -7,6 +7,7 @@ use App\Models\StaffModel;
use App\Models\UserModel;
use App\Models\TeacherClassModel;
use App\Models\ConfigurationModel;
use App\Services\StaffDirectorySyncService;
class StaffController extends BaseController
{
@@ -16,6 +17,7 @@ class StaffController extends BaseController
protected $teacherClassModel;
protected $userModel;
protected $staffModel;
protected StaffDirectorySyncService $staffDirectorySync;
public function __construct()
{
@@ -24,6 +26,7 @@ class StaffController extends BaseController
$this->teacherClassModel = new TeacherClassModel();
$this->userModel = new UserModel();
$this->staffModel = new StaffModel();
$this->staffDirectorySync = service('staffDirectorySync');
// Retrieve the configuration values
$this->semester = $this->configModel->getConfig('semester');
@@ -38,61 +41,11 @@ class StaffController extends BaseController
$schoolYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? ''));
// roles we never show
$excludedRoles = ['student', 'parent', 'guest', 'inactive']; // ← add inactive here
$staffList = $this->staffModel
->whereNotIn('LOWER(active_role)', array_map('strtolower', $excludedRoles))
->orderBy('created_at', 'DESC')
->findAll();
// Preload assignments for the selected school year (across all semesters),
// mirroring the logic used in teacher_class_assignment.
$assignRows = $this->teacherClassModel
->select('teacher_class.teacher_id, teacher_class.position, classSection.class_section_name')
->join('classSection', 'classSection.class_section_id = teacher_class.class_section_id', 'left')
->where('teacher_class.school_year', $schoolYear)
->findAll();
$assignByTeacher = [];
foreach ($assignRows as $r) {
$tid = (int)($r['teacher_id'] ?? 0);
if ($tid <= 0) { continue; }
$name = trim((string)($r['class_section_name'] ?? ''));
if ($name === '') { continue; }
$pos = strtolower((string)($r['position'] ?? ''));
if (!in_array($pos, ['main','ta'], true)) { continue; }
$assignByTeacher[$tid][] = $name . ' (' . $pos . ')';
}
$issuesCount = 0;
foreach ($staffList as &$staff) {
// attach school_id
$staff['school_id'] = $this->userModel->getSchoolIdByUserId($staff['user_id'] ?? null);
// Verify and attach class assignments for teacher/TA roles for the selected school year
$role = strtolower((string)($staff['active_role'] ?? ''));
if (in_array($role, ['teacher', 'teacher_assistant'], true)) {
$tid = (int)($staff['user_id'] ?? 0);
$labels = $assignByTeacher[$tid] ?? [];
if (!empty($labels)) {
$staff['class_section'] = implode(', ', array_unique($labels));
$staff['verification_issue'] = false;
} else {
$staff['class_section'] = 'No class assigned';
$staff['verification_issue'] = true;
$issuesCount++;
}
} else {
$staff['class_section'] = '—';
$staff['verification_issue'] = false;
}
}
unset($staff); // break reference
$directory = $this->staffDirectorySync->activeStaffForSchoolYear($schoolYear);
return view('staff/index', [
'staff' => $staffList,
'issues_count' => $issuesCount,
'staff' => $directory['staff'],
'issues_count' => $directory['issues_count'],
'semester' => $this->semester,
'school_year' => $schoolYear,
]);
+6
View File
@@ -15,6 +15,7 @@ use App\Models\IpAttemptModel;
use CodeIgniter\Controller;
use App\Controllers\View\EmailController;
use App\Models\LoginActivityModel; // Make sure this import is present
use App\Services\StaffDirectorySyncService;
require_once APPPATH . 'Helpers/pbkdf2_helper.php';
@@ -29,6 +30,7 @@ class UserController extends BaseController
protected $passwordResetModel;
protected $loginActivityModel;
protected $resetRequestModel;
protected StaffDirectorySyncService $staffDirectorySync;
public function __construct()
{
@@ -48,6 +50,7 @@ class UserController extends BaseController
$this->passwordResetModel = new PasswordResetModel();
$this->loginActivityModel = new LoginActivityModel();
$this->resetRequestModel = new PasswordResetRequestModel();
$this->staffDirectorySync = service('staffDirectorySync');
}
private function denyAccess(string $message)
@@ -382,6 +385,8 @@ class UserController extends BaseController
return redirect()->back()->withInput()->with('errors', $this->userRoleModel->errors());
}
$this->staffDirectorySync->syncUser((int) $userId);
return redirect()->to('/user');
}
@@ -412,6 +417,7 @@ class UserController extends BaseController
// Delete the user's roles from the user_roles table
$this->userRoleModel->where('user_id', $id)->delete();
$this->staffDirectorySync->syncUser((int) $id);
return redirect()->to('/user');
}