'required|integer', 'class_section_id' => 'required|integer', 'school_year' => 'required|string|max_length[9]', 'position' => 'required|in_list[main,ta]', ]; protected $validationMessages = [ 'teacher_id' => [ 'required' => 'The teacher ID is required.', 'integer' => 'The teacher ID must be an integer.', ], 'class_section_id' => [ 'required' => 'The class section ID is required.', 'integer' => 'The class section ID must be an integer.', ], 'school_year' => [ 'required' => 'The school year is required.', 'max_length' => 'The school year cannot exceed 9 characters.', ], 'position' => [ 'required' => 'The position (main or ta) is required.', 'in_list' => 'The position must be either "main" or "ta".', ], ]; public function getClassSectionIdByUserId($user_id) { $result = $this->where('teacher_id', $user_id)->first(); if (!$result) { log_message('error', "No class section found for user ID: $user_id"); return null; } return $result; } public function getClassAssignmentsByUserId(int $userId, ?string $schoolYear = null, ?string $semester = null): array { $builder = $this->db->table('teacher_class tc') ->select([ 'cs.class_section_name', 'cs.class_section_id AS class_section_pk', 'cs.class_section_id', 'c.id AS class_id', 'c.class_name', 'tc.teacher_id', 'tc.position', 'tc.school_year', ]) ->join('classSection cs', 'tc.class_section_id = cs.class_section_id', 'inner') ->join('classes c', 'cs.class_id = c.id', 'left') ->where('tc.teacher_id', $userId) ->where('tc.class_section_id IS NOT NULL', null, false) ->orderBy('cs.class_section_name', 'ASC'); if ($schoolYear) $builder->where('tc.school_year', $schoolYear); $rows = $builder->get()->getResultArray(); $out = []; foreach ($rows as $r) { $out[] = [ 'class_section_pk' => (int)$r['class_section_pk'], 'class_section_id' => (int)$r['class_section_id'], 'class_section_name' => $r['class_section_name'], 'class_id' => isset($r['class_id']) ? (int)$r['class_id'] : null, 'class_name' => $r['class_name'] ?? null, 'teacher_id' => (int)$r['teacher_id'], 'teacher_role' => ucfirst($r['position']), // "Main" or "Ta" 'school_year' => $r['school_year'], 'semester' => $semester, ]; } return $out; } public function getClassSectionsByTeacherId($teacherId) { $builder = $this->db->table('teacher_class') ->select('classSection.class_section_name, teacher_class.teacher_id, teacher_class.position') ->join('classSection', 'teacher_class.class_section_id = classSection.class_section_id') ->where('teacher_class.teacher_id', $teacherId) ->where('teacher_class.class_section_id IS NOT NULL', null, false); $result = $builder->get()->getResultArray(); if ($result) { $sectionNames = array_map(fn($row) => $row['class_section_name'], $result); $position = $result[0]['position'] ?? ''; return [ 'role' => ucfirst($position), 'class_section_name' => implode(', ', $sectionNames) ]; } else { return ['role' => '', 'class_section_name' => 'No class assigned']; } } public function getClassByTeacherId($teacherId) { $builder = $this->db->table('teacher_class') ->select('classSection.class_section_id, classSection.class_section_name') ->join('classSection', 'teacher_class.class_section_id = classSection.class_section_id') ->where('teacher_class.teacher_id', $teacherId) ->where('teacher_class.class_section_id IS NOT NULL', null, false); return $builder->get()->getResultArray() ?? []; } public function getTeacherIdByClassSection(string $classSectionName, string $semester, string $schoolYear) { $db = \Config\Database::connect(); $classSectionName = trim($classSectionName); if (strpos($classSectionName, ',') !== false) { $parts = array_filter(array_map('trim', explode(',', $classSectionName))); if (!empty($parts)) { $classSectionName = reset($parts); // pick the first section as primary } } if ($classSectionName === '') { return null; } $section = $db->table('classSection') ->select('class_section_id') ->where('class_section_name', $classSectionName) ->get() ->getRowArray(); if (!$section) return null; $result = $db->table('teacher_class') ->select('teacher_id') ->where([ 'class_section_id' => $section['class_section_id'], 'school_year' => $schoolYear, 'position' => 'main' ]) ->get() ->getRowArray(); return $result['teacher_id'] ?? null; } public function getMainTeacherBySection(string $classSectionName, string $semester, string $schoolYear): ?array { $db = \Config\Database::connect(); // Adjust table/column names if yours differ (e.g., cs.class_section_id vs cs.id) $row = $db->table('classSection cs') ->select('u.id AS teacher_id, u.firstname, u.lastname') ->join('teacher_class tc', 'tc.class_section_id = cs.class_section_id', 'inner') ->join('users u', 'u.id = tc.teacher_id', 'inner') ->where('cs.class_section_name', $classSectionName) ->where('tc.school_year', $schoolYear) ->where('tc.position', 'main') ->get() ->getRowArray(); return $row ?: null; // ['teacher_id' => ..., 'firstname' => ..., 'lastname' => ...] or null } /** * Return teacher + TAs for a class section in the current term. * Output: * [ * 'teacher_id' => int|null, * 'teacher_name' => string, // 'N/A' if none found * 'tas' => [ ['id'=>int,'name'=>string], ... ], * ] */ public function getTeacherTABySection(int|string $classSectionId, string $semester, string $schoolYear): array { $db = \Config\Database::connect(); // We can read everything straight from teacher_class; no need to join classSection. $rows = $db->table('teacher_class tc') ->select('u.id AS user_id, u.firstname, u.lastname, u.gender, tc.position') ->join('users u', 'u.id = tc.teacher_id', 'inner') ->where('tc.class_section_id', $classSectionId) ->where('tc.school_year', $schoolYear) // Prefer the most recent assignment and keep main/co-teachers ahead of assistants ->orderBy("CASE WHEN LOWER(tc.position) IN ('main','teacher','main teacher') THEN 0 ELSE 1 END", 'ASC', false) ->orderBy('COALESCE(tc.updated_at, tc.id)', 'DESC', false) ->orderBy('tc.id', 'DESC') ->get() ->getResultArray(); $teacherId = null; $teacherName = 'N/A'; $teacherNames = []; $tas = []; $firstSeen = null; // fallback if no explicit "main"/"teacher" $taSeen = []; $mainSeen = []; $withPrefix = static function (array $row): string { $first = trim((string)($row['firstname'] ?? '')); $last = trim((string)($row['lastname'] ?? '')); $gender = strtolower(trim((string)($row['gender'] ?? ''))); $name = trim($first . ' ' . $last); if ($name === '') return ''; $prefix = ($gender === 'female') ? 'Sr ' : (($gender === 'male') ? 'Br ' : ''); return trim($prefix . $name); }; foreach ($rows as $r) { $full = $withPrefix($r); if ($full === '') continue; if ($firstSeen === null) { $firstSeen = ['id' => (int)$r['user_id'], 'name' => $full]; } $pos = strtolower((string)($r['position'] ?? '')); // Treat either 'main' or legacy 'teacher' as the main teacher role. if (in_array($pos, ['main', 'teacher', 'main teacher'], true)) { $uid = (int)$r['user_id']; if (!isset($mainSeen[$uid])) { $teacherNames[] = $full; $mainSeen[$uid] = true; } if ($teacherId === null) { $teacherId = $uid; $teacherName = $full; } continue; } // Treat 'ta', 'assistant', 'teacher_assistant' (and similar) as TA roles. if (in_array($pos, ['ta', 'assistant', 'teacher_assistant'], true)) { $tid = (int)$r['user_id']; if (!isset($taSeen[$tid])) { $tas[] = ['id' => $tid, 'name' => $full]; $taSeen[$tid] = true; } continue; } // Unknown/missing position: don't discard; we’ll use firstSeen as a fallback later. } // Fallback: if no explicit main teacher found, use the first record seen. if ($teacherId === null && $firstSeen !== null) { $teacherId = $firstSeen['id']; $teacherName = $firstSeen['name']; $teacherNames[] = $firstSeen['name']; } // Ensure uniqueness/cleaning $teacherNames = array_values(array_unique(array_filter(array_map('trim', $teacherNames)))); return [ 'teacher_id' => $teacherId, 'teacher_name' => $teacherName, 'teacher_names' => $teacherNames, 'tas' => $tas, ]; } public function assignTeacherToClass($teacherId, $classSectionId, $semester, $schoolYear, $position = 'main') { if (!in_array($position, ['main', 'ta'])) { throw new \InvalidArgumentException('Position must be either "main" or "ta"'); } $existing = $this->where([ 'teacher_id' => $teacherId, 'class_section_id' => $classSectionId, 'school_year' => $schoolYear, 'position' => $position ])->first(); if ($existing) { return $this->update($existing['id'], ['updated_at' => utc_now()]); } return $this->insert([ 'teacher_id' => $teacherId, 'class_section_id' => $classSectionId, 'school_year' => $schoolYear, 'position' => $position, 'created_at' => utc_now(), 'updated_at' => utc_now() ]); } public function findAll($limit = 0, $offset = 0) { return parent::findAll($limit, $offset); } public function getAssignedClassSections(int $teacherId, string $schoolYear, string $semester) { return $this->select('teacher_class.class_section_id, teacher_class.position, classSection.class_section_name') ->join('classSection', 'classSection.id = teacher_class.class_section_id') ->where('teacher_class.teacher_id', $teacherId) ->where('teacher_class.school_year', $schoolYear) ->findAll(); } /** * Get assigned teachers (with names) for ONE section code + term. * Returns rows: class_section_id, teacher_id, position, firstname, lastname */ public function assignedForSectionTerm(int $sectionCode, string $semester, string $schoolYear): array { $key = $sectionCode . '|' . $semester . '|' . $schoolYear; if (isset($this->assignedCache[$key])) { return $this->assignedCache[$key]; } $rows = $this->db->table('teacher_class tc') ->select('tc.class_section_id, tc.teacher_id, tc.position, u.firstname, u.lastname') ->join('users u', 'u.id = tc.teacher_id', 'left') ->where('tc.class_section_id', $sectionCode) // SECTION CODE ->where('tc.school_year', $schoolYear) ->orderBy("FIELD(tc.position, 'main','ta')", '', false) // main → ta ->orderBy('u.firstname', 'ASC') ->get()->getResultArray(); return $this->assignedCache[$key] = $rows; } /** * Convenience: same as above but keyed by teacher_id. * Returns: [teacher_id] => row */ public function assignedMapForSectionTerm(int $sectionCode, string $semester, string $schoolYear): array { $out = []; foreach ($this->assignedForSectionTerm($sectionCode, $semester, $schoolYear) as $r) { $out[(int)$r['teacher_id']] = $r; } return $out; } /** * Batch helper for a whole term (optionally filtered to some sections). * Returns: [sectionCode] => [rows...] */ public function assignedBySectionForTerm(string $semester, string $schoolYear, ?array $onlySectionCodes = null): array { $cacheKey = ($onlySectionCodes ? implode(',', array_map('intval', $onlySectionCodes)) : '*') . '|' . $semester . '|' . $schoolYear; if (isset($this->assignedBySectionCache[$cacheKey])) { return $this->assignedBySectionCache[$cacheKey]; } $qb = $this->db->table('teacher_class tc') ->select('tc.class_section_id, tc.teacher_id, tc.position, u.firstname, u.lastname') ->join('users u', 'u.id = tc.teacher_id', 'left') ->where('tc.school_year', $schoolYear) ->orderBy('tc.class_section_id', 'ASC') ->orderBy("FIELD(tc.position, 'main','ta')", '', false) ->orderBy('u.firstname', 'ASC'); if (!empty($onlySectionCodes)) { $qb->whereIn('tc.class_section_id', array_map('intval', $onlySectionCodes)); } $rows = $qb->get()->getResultArray(); $out = []; foreach ($rows as $r) { $code = (int)$r['class_section_id']; $out[$code][] = $r; } return $this->assignedBySectionCache[$cacheKey] = $out; } }