Files
alrahma_sunday_school_api/app/Models/TeacherClass.php
T
2026-03-05 12:29:37 -05:00

380 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Models;
use App\Models\BaseModel;
class TeacherClass extends BaseModel
{
protected $table = 'teacher_class';
protected $primaryKey = 'id';
protected $fillable = [
'class_section_id',
'teacher_id',
'position',
'semester',
'school_year',
'description',
'created_at',
'updated_at',
'updated_by',
];
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
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',
'semester' => 'required|string|max_length[255]',
'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.',
],
'semester' => [
'required' => 'The semester is required.',
],
'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.id AS class_section_pk',
'cs.class_section_id',
'tc.teacher_id',
'tc.position',
'tc.school_year',
'tc.semester',
])
->join('classSection cs', 'tc.class_section_id = cs.class_section_id', 'inner')
->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);
if ($semester) $builder->where('tc.semester', $semester);
$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'],
'teacher_id' => (int)$r['teacher_id'],
'teacher_role' => ucfirst($r['position']), // "Main" or "Ta"
'school_year' => $r['school_year'],
'semester' => $r['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();
$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'],
'semester' => $semester,
'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.id vs cs.class_section_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.id', 'inner')
->join('users u', 'u.id = tc.teacher_id', 'inner')
->where('cs.class_section_name', $classSectionName)
->where('tc.semester', $semester)
->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.
// The previous version failed because it filtered on cs.class_section_id even though
// the PK is cs.id. We avoid that by filtering on tc.class_section_id directly.
$rows = $db->table('teacher_class tc')
->select('u.id AS user_id, u.firstname, u.lastname, tc.position')
->join('users u', 'u.id = tc.teacher_id', 'inner')
->where('tc.class_section_id', $classSectionId)
->where('tc.semester', $semester)
->where('tc.school_year', $schoolYear)
->get()
->getResultArray();
$teacherId = null;
$teacherName = 'N/A';
$tas = [];
$firstSeen = null; // fallback if no explicit "main"/"teacher"
foreach ($rows as $r) {
$full = trim(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? ''));
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'], true)) {
// Keep the first "main/teacher" as the teacher, ignore additional conflicts.
if ($teacherId === null) {
$teacherId = (int)$r['user_id'];
$teacherName = $full;
}
continue;
}
// Treat 'ta', 'assistant', 'teacher_assistant' (and similar) as TA roles.
if (in_array($pos, ['ta', 'assistant', 'teacher_assistant'], true)) {
$tas[] = ['id' => (int)$r['user_id'], 'name' => $full];
continue;
}
// Unknown/missing position: don't discard; well 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'];
}
return [
'teacher_id' => $teacherId,
'teacher_name' => $teacherName,
'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,
'semester' => $semester,
'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,
'semester' => $semester,
'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)
->where('teacher_class.semester', $semester)
->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.semester', $semester)
->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.semester', $semester)
->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;
}
}