Files
alrahma_sunday_school_api/app/Models/TeacherClass.php
T
2026-04-25 00:00:23 -04:00

413 lines
15 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\DB;
class TeacherClass extends BaseModel
{
protected $table = 'teacher_class';
protected $fillable = [
'teacher_id',
'class_section_id',
'school_year',
'position',
'updated_by',
'updated_at',
'created_at',
];
public $timestamps = true;
protected $casts = [
'teacher_id' => 'integer',
'class_section_id' => 'integer',
'updated_by' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
/* ============================================================
* Constants
* ============================================================
*/
public const POS_MAIN = 'main';
public const POS_TA = 'ta';
public static function allowedPositions(): array
{
return [self::POS_MAIN, self::POS_TA];
}
/* ============================================================
* Relationships (optional)
* ============================================================
*/
public function teacher(): BelongsTo
{
// your teachers are users in most of your joins
return $this->belongsTo(User::class, 'teacher_id');
}
public function classSection(): BelongsTo
{
// CI frequently joins on classSection.class_section_id
return $this->belongsTo(ClassSection::class, 'class_section_id', 'class_section_id');
}
public function updater(): BelongsTo
{
return $this->belongsTo(User::class, 'updated_by'); // change to Admin if needed
}
/* ============================================================
* Scopes
* ============================================================
*/
public function scopeForTeacher(Builder $q, int $teacherId): Builder
{
return $q->where('teacher_id', $teacherId);
}
public function scopeForSchoolYear(Builder $q, string $schoolYear): Builder
{
return $q->where('school_year', $schoolYear);
}
public function scopeForSectionCode(Builder $q, int $sectionCode): Builder
{
return $q->where('class_section_id', $sectionCode);
}
public function scopeMain(Builder $q): Builder
{
return $q->where('position', self::POS_MAIN);
}
public function scopeTa(Builder $q): Builder
{
return $q->where('position', self::POS_TA);
}
/* ============================================================
* CI-compatible methods (converted)
* ============================================================
*/
/** CI: getClassSectionIdByUserId() */
public static function getClassSectionIdByUserId(int $userId): ?self
{
return static::query()->forTeacher($userId)->first();
}
/**
* CI: getClassAssignmentsByUserId()
* Returns array rows with:
* class_section_pk, class_section_id, class_section_name, class_id, class_name, teacher_id, teacher_role, school_year, semester
*/
public static function getClassAssignmentsByUserId(int $userId, ?string $schoolYear = null, ?string $semester = null): array
{
$q = DB::table('teacher_class as tc')
->selectRaw('
cs.class_section_name,
cs.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 as cs', 'tc.class_section_id', '=', 'cs.class_section_id')
->leftJoin('classes as c', 'cs.class_id', '=', 'c.id')
->where('tc.teacher_id', $userId)
->whereNotNull('tc.class_section_id')
->orderBy('cs.class_section_name', 'asc');
if ($schoolYear) {
$q->where('tc.school_year', $schoolYear);
}
$rows = $q->get();
return $rows->map(function ($r) use ($semester) {
return [
'class_section_pk' => isset($r->class_section_pk) ? (int) $r->class_section_pk : null,
'class_section_id' => (int) $r->class_section_id,
'class_section_name' => (string) $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((string) $r->position), // Main / Ta
'school_year' => (string) $r->school_year,
'semester' => $semester,
];
})->all();
}
/**
* Distinct `class_section_id` values this teacher has ever been assigned to (`teacher_class`),
* ignoring school year / semester (covers legacy rows vs `class_progress_reports`).
*
* @return list<int>
*/
public static function distinctSectionIdsForTeacher(int $userId): array
{
return static::query()
->where('teacher_id', $userId)
->whereNotNull('class_section_id')
->where('class_section_id', '>', 0)
->distinct()
->orderBy('class_section_id')
->pluck('class_section_id')
->map(static fn ($id) => (int) $id)
->unique()
->values()
->all();
}
/** CI: getClassSectionsByTeacherId() */
public static function getClassSectionsByTeacherId(int $teacherId): array
{
$rows = DB::table('teacher_class as tc')
->selectRaw('cs.class_section_name, tc.teacher_id, tc.position')
->join('classSection as cs', 'tc.class_section_id', '=', 'cs.class_section_id')
->where('tc.teacher_id', $teacherId)
->whereNotNull('tc.class_section_id')
->get();
if ($rows->isEmpty()) {
return ['role' => '', 'class_section_name' => 'No class assigned'];
}
$sectionNames = $rows->pluck('class_section_name')->filter()->all();
$position = (string) ($rows[0]->position ?? '');
return [
'role' => ucfirst($position),
'class_section_name' => implode(', ', $sectionNames),
];
}
/** CI: getClassByTeacherId() */
public static function getClassByTeacherId(int $teacherId): array
{
return DB::table('teacher_class as tc')
->selectRaw('cs.class_section_id, cs.class_section_name')
->join('classSection as cs', 'tc.class_section_id', '=', 'cs.class_section_id')
->where('tc.teacher_id', $teacherId)
->whereNotNull('tc.class_section_id')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/** CI: getTeacherIdByClassSection($classSectionName, $semester, $schoolYear) */
public static function getTeacherIdByClassSection(string $classSectionName, string $semester, string $schoolYear): ?int
{
$classSectionName = trim($classSectionName);
if (str_contains($classSectionName, ',')) {
$parts = array_values(array_filter(array_map('trim', explode(',', $classSectionName))));
if (!empty($parts)) $classSectionName = $parts[0];
}
if ($classSectionName === '') return null;
$sectionCode = DB::table('classSection')
->where('class_section_name', $classSectionName)
->value('class_section_id');
if (!$sectionCode) return null;
$teacherId = DB::table('teacher_class')
->where('class_section_id', $sectionCode)
->where('school_year', $schoolYear)
->where('position', self::POS_MAIN)
->value('teacher_id');
return $teacherId ? (int) $teacherId : null;
}
/** CI: getMainTeacherBySection($classSectionName, $semester, $schoolYear) */
public static function getMainTeacherBySection(string $classSectionName, string $semester, string $schoolYear): ?array
{
$row = DB::table('classSection as cs')
->selectRaw('u.id AS teacher_id, u.firstname, u.lastname')
// IMPORTANT: your CI code had tc.class_section_id = cs.id in one place (PK join),
// but most of your code uses class_section_id code join.
// Here we keep the "code join" for consistency:
->join('teacher_class as tc', 'tc.class_section_id', '=', 'cs.class_section_id')
->join('users as u', 'u.id', '=', 'tc.teacher_id')
->where('cs.class_section_name', $classSectionName)
->where('tc.school_year', $schoolYear)
->where('tc.position', self::POS_MAIN)
->first();
return $row ? (array) $row : null;
}
/**
* CI: assignTeacherToClass()
* Upsert by (teacher_id, class_section_id, school_year, position) and touch updated_at.
*/
public static function assignTeacherToClass(
int $teacherId,
int $sectionCode,
string $semester,
string $schoolYear,
string $position = self::POS_MAIN,
?int $updatedBy = null
): bool {
$position = strtolower(trim($position));
if (!in_array($position, self::allowedPositions(), true)) {
throw new \InvalidArgumentException('Position must be either "main" or "ta".');
}
$updatedBy = $updatedBy ?? (auth()->id() ?? null);
static::query()->updateOrCreate(
[
'teacher_id' => $teacherId,
'class_section_id' => $sectionCode,
'school_year' => $schoolYear,
'position' => $position,
],
[
'updated_by' => $updatedBy,
'updated_at' => now(),
]
);
return true;
}
/**
* CI: getAssignedClassSections()
* NOTE: your CI joins classSection.id = teacher_class.class_section_id (PK join).
* If your teacher_class.class_section_id is actually a section CODE, change join to cs.class_section_id.
*/
public static function getAssignedClassSections(int $teacherId, string $schoolYear, string $semester): array
{
return DB::table('teacher_class as tc')
->selectRaw('tc.class_section_id, tc.position, cs.class_section_name')
->join('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
->where('tc.teacher_id', $teacherId)
->where('tc.school_year', $schoolYear)
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->get()
->map(fn ($r) => (array) $r)
->all();
}
/**
* CI: assignedForSectionTerm()
* Returns rows: class_section_id, teacher_id, position, firstname, lastname
*
* @param int|null $alsoSectionCode When teacher_class rows use the section PK while $sectionCode
* is the business code (or the reverse), pass the other id.
* @param bool $requireSemester When false, ignore semester (legacy DBs with blank/wrong tc.semester).
*/
public static function assignedForSectionTerm(
int $sectionCode,
string $semester,
string $schoolYear,
?int $alsoSectionCode = null,
bool $requireSemester = true,
): array {
$sectionIds = array_values(array_unique(array_filter(
[$sectionCode, $alsoSectionCode ?? 0],
static fn (int $v): bool => $v > 0,
)));
if ($sectionIds === []) {
return [];
}
$q = DB::table('teacher_class as tc')
->selectRaw('tc.class_section_id, tc.teacher_id, tc.position, u.firstname, u.lastname')
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->whereIn('tc.class_section_id', $sectionIds)
->where('tc.school_year', $schoolYear);
if ($requireSemester) {
$sem = trim($semester);
if ($sem !== '') {
$q->whereRaw('TRIM(tc.semester) = ?', [$sem]);
}
}
return $q->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->orderBy('u.firstname', 'asc')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/** CI: assignedMapForSectionTerm() */
public static function assignedMapForSectionTerm(
int $sectionCode,
string $semester,
string $schoolYear,
?int $alsoSectionCode = null,
bool $requireSemester = true,
): array {
$out = [];
foreach (static::assignedForSectionTerm($sectionCode, $semester, $schoolYear, $alsoSectionCode, $requireSemester) as $r) {
$out[(int) $r['teacher_id']] = $r;
}
return $out;
}
/**
* CI: assignedBySectionForTerm()
* Returns: [sectionCode] => [rows...]
*/
public static function assignedBySectionForTerm(string $semester, string $schoolYear, ?array $onlySectionCodes = null): array
{
$q = DB::table('teacher_class as tc')
->selectRaw('tc.class_section_id, tc.teacher_id, tc.position, u.firstname, u.lastname')
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
->where('tc.school_year', $schoolYear)
->orderBy('tc.class_section_id', 'asc')
->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END")
->orderBy('u.firstname', 'asc');
if (!empty($onlySectionCodes)) {
$ids = array_values(array_unique(array_filter(array_map('intval', $onlySectionCodes))));
if (!empty($ids)) $q->whereIn('tc.class_section_id', $ids);
}
$rows = $q->get()->map(fn ($r) => (array) $r)->all();
$out = [];
foreach ($rows as $r) {
$code = (int) ($r['class_section_id'] ?? 0);
if ($code <= 0) continue;
$out[$code][] = $r;
}
return $out;
}
/* ============================================================
* Optional validation helper (FormRequest/controller)
* ============================================================
*/
public static function rules(bool $updating = false): array
{
$req = $updating ? 'sometimes' : 'required';
return [
'teacher_id' => [$req, 'integer', 'min:1', 'exists:users,id'],
'class_section_id' => [$req, 'integer', 'min:1'],
'school_year' => [$req, 'string', 'max:9'],
'position' => [$req, 'string', 'in:' . implode(',', self::allowedPositions())],
'updated_by' => ['nullable', 'integer'],
];
}
}