fix test and add invocie to parent enrollment
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 49s
Tests / PHPUnit (push) Successful in 1m23s

This commit is contained in:
root
2026-08-29 15:25:17 -04:00
parent d2cb159451
commit 611a5c8e4b
9 changed files with 523 additions and 188 deletions
+207 -17
View File
@@ -34,14 +34,13 @@ public function metrics(string $schoolYear, string $semester): array
$totalAdmins = (int) ($this->userModel->countAdminsBySchoolYear($this->schoolYear) ?? 0);
$teachers = $this->userModel->getUsersByRoleAndSchoolYear('teacher', $this->schoolYear);
$teachers = $this->userModel->getUsersByRole('teacher');
$totalTeachers = $this->countUniqueEntities($teachers);
$teacherAssistants = $this->userModel->getUsersByRoleAndSchoolYear('teacher_assistant', $this->schoolYear);
$teacherAssistants = $this->userModel->getUsersByRole('teacher_assistant');
$totalTeacherAssistants = $this->countUniqueEntities($teacherAssistants);
$parents = $this->userModel->getUsersByRoleAndSchoolYear('parent', $this->schoolYear);
$totalParents = $this->countUniqueEntities($parents);
$totalParents = $this->countParentsWithEnrolledStudents($this->schoolYear);
// Count only students that have a class assigned and exist in student_class for the current school year
$totalStudents = (int) (
@@ -103,6 +102,36 @@ private function countUniqueEntities($rows): int
return count(array_unique($ids));
}
private function countParentsWithEnrolledStudents(string $schoolYear): int
{
$schoolYear = trim($schoolYear);
if ($schoolYear === '') {
return 0;
}
return (int) (
$this->db->table('students')
->select('COUNT(DISTINCT students.parent_id) AS cnt')
->join('student_class', 'student_class.student_id = students.id', 'inner')
->join('users', 'users.id = students.parent_id', 'inner')
->join('user_roles', 'user_roles.user_id = users.id', 'inner')
->join('roles', 'roles.id = user_roles.role_id', 'inner')
->where('student_class.school_year', $schoolYear)
->where('student_class.class_section_id IS NOT NULL', null, false)
->where('students.is_active', 1)
->where('students.parent_id IS NOT NULL', null, false)
->where('students.parent_id >', 0)
->where('user_roles.deleted_at', null)
->groupStart()
->where('LOWER(roles.name)', 'parent')
->orWhere('roles.slug', 'parent')
->groupEnd()
->get()
->getRow('cnt')
?? 0
);
}
public function search(string $query): array
{
$q = trim($query);
@@ -122,6 +151,15 @@ public function search(string $query): array
// 1) Tokenize input: split by whitespace and punctuation, keep meaningful pieces
$rawTokens = preg_split('/[,\s]+/u', $q, -1, PREG_SPLIT_NO_EMPTY) ?: [];
$tokens = array_values(array_filter(array_map('trim', $rawTokens)));
if ($tokens === []) {
return [
'query' => $q,
'results' => [],
'scope_used' => 'unscoped-merged',
'scope_label' => 'all years/semesters (merged)',
'total_found' => 0,
];
}
// 2) Build phone variants for any token that looks numeric-ish
$phoneMap = []; // token => variants[]
@@ -229,22 +267,174 @@ public function search(string $query): array
$applyMultiTokenLike($ecQB, $ecCols, $tokens, ['cellphone']);
$emergency = $ecQB->limit(150)->get()->getResultArray();
$raw = [
'users' => $users,
'students' => $students,
'parents' => $parents,
'staff' => $staff,
'emergency_contacts' => $emergency,
];
$total = count($users) + count($students) + count($parents) + count($staff) + count($emergency);
$results = $this->mergeSearchResults($users, $students, $parents, $staff, $emergency);
return [
'query' => $q,
'results' => $raw,
'scope_used' => 'unscoped-raw',
'scope_label' => 'all years/semesters (raw, tokenized)',
'total_found' => $total,
'results' => $results,
'scope_used' => 'unscoped-merged',
'scope_label' => 'all years/semesters (merged, tokenized)',
'total_found' => count($results),
];
}
private function mergeSearchResults(array $users, array $students, array $parents, array $staff, array $emergency): array
{
$bundles = [];
$userIds = [];
$ensureBundle = static function (int $userId) use (&$bundles, &$userIds): void {
if ($userId <= 0) {
return;
}
if (!isset($bundles[$userId])) {
$bundles[$userId] = [
'user' => null,
'students' => [],
'parents' => [],
'staff' => [],
'emergency_contacts' => [],
];
}
$userIds[$userId] = $userId;
};
foreach ($users as $user) {
$userId = (int) ($user['id'] ?? 0);
$ensureBundle($userId);
if ($userId > 0) {
$bundles[$userId]['user'] = $user;
}
}
foreach ($students as $student) {
$parentId = (int) ($student['parent_id'] ?? 0);
$ensureBundle($parentId);
if ($parentId > 0) {
$bundles[$parentId]['students'][(int) ($student['id'] ?? 0)] = $student;
}
}
foreach ($parents as $parent) {
$firstParentId = (int) ($parent['firstparent_id'] ?? 0);
$ensureBundle($firstParentId);
if ($firstParentId > 0) {
$bundles[$firstParentId]['parents'][(int) ($parent['id'] ?? 0)] = $parent;
}
}
foreach ($staff as $staffRow) {
$userId = (int) ($staffRow['user_id'] ?? 0);
$ensureBundle($userId);
if ($userId > 0) {
$bundles[$userId]['staff'][(int) ($staffRow['id'] ?? 0)] = $staffRow;
}
}
foreach ($emergency as $emergencyRow) {
$parentId = (int) ($emergencyRow['parent_id'] ?? 0);
$ensureBundle($parentId);
if ($parentId > 0) {
$bundles[$parentId]['emergency_contacts'][(int) ($emergencyRow['id'] ?? 0)] = $emergencyRow;
}
}
if ($userIds === []) {
return [];
}
$this->hydrateSearchBundles($bundles, array_values($userIds));
$results = array_values($bundles);
usort($results, static function (array $a, array $b): int {
$aUser = $a['user'] ?? [];
$bUser = $b['user'] ?? [];
$aName = trim((string) ($aUser['lastname'] ?? '') . ' ' . (string) ($aUser['firstname'] ?? ''));
$bName = trim((string) ($bUser['lastname'] ?? '') . ' ' . (string) ($bUser['firstname'] ?? ''));
return strcasecmp($aName, $bName);
});
return $results;
}
private function hydrateSearchBundles(array &$bundles, array $userIds): void
{
$userRows = $this->db->table('users')
->select('id, firstname, lastname, email, cellphone, school_id, city, state')
->whereIn('id', $userIds)
->get()
->getResultArray();
foreach ($userRows as $user) {
$userId = (int) ($user['id'] ?? 0);
if ($userId > 0 && isset($bundles[$userId]) && empty($bundles[$userId]['user'])) {
$bundles[$userId]['user'] = $user;
}
}
$studentRows = $this->db->table('students')
->select('id, parent_id, school_id, firstname, lastname, dob, gender, rfid_tag, is_active')
->whereIn('parent_id', $userIds)
->orderBy('lastname', 'ASC')
->orderBy('firstname', 'ASC')
->get()
->getResultArray();
foreach ($studentRows as $student) {
$parentId = (int) ($student['parent_id'] ?? 0);
if ($parentId > 0 && isset($bundles[$parentId])) {
$bundles[$parentId]['students'][(int) ($student['id'] ?? 0)] = $student;
}
}
$parentRows = $this->db->table('parents')
->select('id, firstparent_id, secondparent_firstname, secondparent_lastname, secondparent_email, secondparent_phone')
->whereIn('firstparent_id', $userIds)
->get()
->getResultArray();
foreach ($parentRows as $parent) {
$firstParentId = (int) ($parent['firstparent_id'] ?? 0);
if ($firstParentId > 0 && isset($bundles[$firstParentId])) {
$bundles[$firstParentId]['parents'][(int) ($parent['id'] ?? 0)] = $parent;
}
}
$staffRows = $this->db->table('staff')
->select('id, user_id, firstname, lastname, email, phone, role_name, active_role')
->whereIn('user_id', $userIds)
->get()
->getResultArray();
foreach ($staffRows as $staffRow) {
$userId = (int) ($staffRow['user_id'] ?? 0);
if ($userId > 0 && isset($bundles[$userId])) {
$bundles[$userId]['staff'][(int) ($staffRow['id'] ?? 0)] = $staffRow;
}
}
$emergencyRows = $this->db->table('emergency_contacts')
->select('id, parent_id, emergency_contact_name, relation, cellphone, email')
->whereIn('parent_id', $userIds)
->get()
->getResultArray();
foreach ($emergencyRows as $emergencyRow) {
$parentId = (int) ($emergencyRow['parent_id'] ?? 0);
if ($parentId > 0 && isset($bundles[$parentId])) {
$bundles[$parentId]['emergency_contacts'][(int) ($emergencyRow['id'] ?? 0)] = $emergencyRow;
}
}
foreach ($bundles as &$bundle) {
$bundle['students'] = array_values($bundle['students']);
$bundle['parents'] = array_values($bundle['parents']);
$bundle['staff'] = array_values($bundle['staff']);
$bundle['emergency_contacts'] = array_values($bundle['emergency_contacts']);
}
unset($bundle);
}
}