410 lines
15 KiB
PHP
410 lines
15 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use CodeIgniter\Model;
|
||
|
||
class TeacherClassModel extends Model
|
||
{
|
||
protected $table = 'teacher_class';
|
||
protected $primaryKey = 'id';
|
||
protected $allowedFields = [
|
||
'teacher_id',
|
||
'class_section_id',
|
||
'school_year',
|
||
'position', // NEW
|
||
'updated_by',
|
||
'updated_at',
|
||
'created_at'
|
||
];
|
||
|
||
protected $useTimestamps = true;
|
||
protected $createdField = 'created_at';
|
||
protected $updatedField = 'updated_at';
|
||
protected $returnType = 'array';
|
||
protected $useSoftDeletes = false;
|
||
protected $skipValidation = false;
|
||
/** Request-lifetime memo cache */
|
||
protected array $assignedCache = [];
|
||
protected array $assignedBySectionCache = [];
|
||
|
||
protected $validationRules = [
|
||
'teacher_id' => '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;
|
||
}
|
||
}
|