Files
alrahma_sunday_school_api/app/Models/Student.php
T
root e0dfc3ec82
API CI/CD / Validate (composer + pint) (push) Successful in 3m15s
API CI/CD / Test (PHPUnit) (push) Failing after 5m4s
API CI/CD / Build frontend assets (push) Successful in 1m3s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
Fixed many feature failures around preferences, route coverage, administrator enrollment, assignment section names, attendance tracking controller access, finance PDF generation, and finance notification logging.
2026-07-07 21:26:47 -04:00

407 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 Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;
class Student extends BaseModel
{
use HasFactory;
protected $table = 'students';
/**
* legacy: timestamps disabled
*/
public $timestamps = false;
protected $fillable = ['school_id', 'firstname', 'lastname', 'dob', 'age', 'gender', 'is_active', 'registration_grade', 'is_new', 'photo_consent', 'parent_id', 'registration_date', 'tuition_paid', 'rfid_tag', 'semester', 'year_of_registration', 'school_year'];
protected $casts = [
'school_id' => 'string',
'age' => 'integer',
'photo_consent' => 'boolean',
'is_new' => 'boolean',
'parent_id' => 'integer',
'tuition_paid' => 'boolean',
'is_active' => 'boolean',
'dob' => 'date',
'registration_date' => 'date',
'year_of_registration' => 'integer',
];
/* ============================================================
* Relationships (optional but useful)
* ============================================================
*/
public function parent(): BelongsTo
{
// Change User model if your parent lives elsewhere
return $this->belongsTo(User::class, 'parent_id');
}
public function classAssignments(): HasMany
{
return $this->hasMany(StudentClass::class, 'student_id');
}
public function allergies(): HasMany
{
return $this->hasMany(StudentAllergy::class, 'student_id');
}
public function medicalConditions(): HasMany
{
return $this->hasMany(StudentMedicalCondition::class, 'student_id');
}
/* ============================================================
* Scopes
* ============================================================
*/
public function scopeActive(Builder $q): Builder
{
return $q->where('is_active', 1);
}
public function scopeNew(Builder $q): Builder
{
return $q->where('is_new', 1);
}
public function scopeForSchoolYear(Builder $q, ?string $schoolYear): Builder
{
if ($schoolYear === null || strtolower(trim($schoolYear)) === 'all' || trim($schoolYear) === '') {
return $q;
}
return $q->whereExists(function ($sub) use ($schoolYear) {
$sub->selectRaw('1')
->from('student_class as sc')
->whereColumn('sc.student_id', 'students.id')
->where('sc.school_year', $schoolYear);
});
}
public function scopeOrderByName(Builder $q): Builder
{
return $q->orderBy('lastname', 'asc')->orderBy('firstname', 'asc');
}
/* ============================================================
* legacy-compatible methods (converted)
* ============================================================
*/
/** legacy: getNewStudents() */
public static function getNewStudents(): array
{
return static::query()
->new()
->orderByName()
->get()
->all();
}
/** legacy: getNewStudentsWithParents() */
public static function getNewStudentsWithParents(): array
{
return DB::table('students')
->selectRaw('
students.*,
u.id AS parent_id,
u.firstname AS parent_firstname,
u.lastname AS parent_lastname,
u.email AS parent_email,
u.cellphone AS parent_phone
')
->leftJoin('users as u', 'u.id', '=', 'students.parent_id')
->where('students.is_new', 1)
->orderBy('students.lastname', 'asc')
->orderBy('students.firstname', 'asc')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/**
* legacy: getNewStudentsWithParentsAndEmergency()
* Picks one emergency contact via MIN(...) with groupBy student.
*/
public static function getNewStudentsWithParentsAndEmergency(): array
{
return DB::table('students')
->selectRaw('
students.*,
u.firstname AS parent_firstname,
u.lastname AS parent_lastname,
u.email AS parent_email,
u.cellphone AS parent_phone,
MIN(ec.emergency_contact_name) AS emergency_name,
MIN(ec.relation) AS emergency_relationship,
MIN(ec.cellphone) AS emergency_phone
')
->leftJoin('users as u', 'u.id', '=', 'students.parent_id')
->leftJoin('emergency_contacts as ec', 'ec.parent_id', '=', 'students.parent_id')
->where('students.is_new', 1)
->groupBy('students.id')
->orderBy('students.lastname', 'asc')
->orderBy('students.firstname', 'asc')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/**
* legacy: getStudentsWithParentsAndEmergency(?schoolYear, ?isNew)
*/
public static function getStudentsWithParentsAndEmergency(?string $schoolYear = null, ?int $isNew = null): array
{
$q = DB::table('students')
->selectRaw('
students.*,
u.firstname AS parent_firstname,
u.lastname AS parent_lastname,
u.email AS parent_email,
u.cellphone AS parent_phone,
MIN(ec.emergency_contact_name) AS emergency_name,
MIN(ec.relation) AS emergency_relationship,
MIN(ec.cellphone) AS emergency_phone
')
->leftJoin('users as u', 'u.id', '=', 'students.parent_id')
->leftJoin('emergency_contacts as ec', 'ec.parent_id', '=', 'students.parent_id');
// is_new filter
if ($isNew === 0 || $isNew === 1) {
$q->where('students.is_new', $isNew);
} else {
$q->whereIn('students.is_new', [0, 1]);
}
// Students are global identities; year rosters come from yearly class/enrollment rows.
if ($schoolYear !== null && strtolower($schoolYear) !== 'all') {
$q->where(function ($yearQuery) use ($schoolYear) {
$yearQuery->whereExists(function ($sub) use ($schoolYear) {
$sub->selectRaw('1')
->from('student_class as sc')
->whereColumn('sc.student_id', 'students.id')
->where('sc.school_year', $schoolYear);
})->orWhereExists(function ($sub) use ($schoolYear) {
$sub->selectRaw('1')
->from('enrollments as e')
->whereColumn('e.student_id', 'students.id')
->where('e.school_year', $schoolYear);
});
});
}
return $q->groupBy('students.id')
->orderByDesc('students.is_new') // new first
->orderBy('students.lastname', 'asc')
->orderBy('students.firstname', 'asc')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/** legacy: getStudentSchoolIdByStudentId() */
public static function getStudentSchoolIdByStudentId(int $studentId): ?string
{
$v = static::query()->where('id', $studentId)->value('school_id');
return $v !== null ? (string) $v : null;
}
/** legacy: getAllStudents() */
public static function getAllStudents()
{
return static::query()->get();
}
/** legacy: getTotalStudents() */
public static function getTotalStudents(): int
{
return (int) static::query()->count();
}
/** legacy: getFullNameById() => returns [firstname, lastname] or null */
public static function getFullNameById($studentId): ?array
{
$student = static::query()->find($studentId);
if (! $student) {
return null;
}
$first = (string) ($student->firstname ?? '');
$last = (string) ($student->lastname ?? '');
if ($first === '' && $last === '') {
return null;
}
return ['firstname' => $first, 'lastname' => $last];
}
/** legacy: getFullName($student array) */
public static function getFullName(array $student): string
{
return trim(($student['firstname'] ?? '').' '.($student['lastname'] ?? ''));
}
/** legacy: getLastRegistrationDate($ParentId) */
public static function getLastRegistrationDate(int $parentId): ?array
{
$row = static::query()
->select('registration_date')
->where('parent_id', $parentId)
->orderByDesc('registration_date')
->first();
return $row ? ['registration_date' => $row->registration_date] : null;
}
/**
* legacy: getByClassAndYear($class_section_id, $school_year, ?semester ignored)
*/
public static function getByClassAndYear(int $classSectionId, string $schoolYear, ?string $semester = null): array
{
$studentIds = StudentClass::query()
->select('student_id')
->where('class_section_id', $classSectionId)
->where('school_year', $schoolYear)
->pluck('student_id')
->all();
if (empty($studentIds)) {
return [];
}
return static::query()
->whereIn('id', $studentIds)
->where('is_active', 1)
->orderBy('firstname', 'asc')
->orderBy('lastname', 'asc')
->get()
->all();
}
public static function getSchoolIdByStudentId($studentId)
{
return static::query()->where('id', $studentId)->value('school_id');
}
public static function getSchoolIdsByParentId(int $parentId): array
{
return static::query()
->where('parent_id', $parentId)
->distinct()
->pluck('school_id')
->filter(fn ($v) => $v !== null && $v !== '')
->values()
->all();
}
public static function getStudentIdsByParentId(int $parentId): array
{
return static::query()
->where('parent_id', $parentId)
->distinct()
->pluck('id')
->map(fn ($v) => (int) $v)
->values()
->all();
}
/**
* legacy: getStudentsWithClassAndEnrollment()
* Keeps your original grouping so behavior doesnt change.
*/
public static function getStudentsWithClassAndEnrollment(): array
{
return DB::table('students')
->selectRaw('
students.*,
student_class.class_section_id,
enrollments.enrollment_status,
enrollments.admission_status,
u.firstname AS parent_firstname,
u.lastname AS parent_lastname
')
->leftJoin('student_class', 'student_class.student_id', '=', 'students.id')
->leftJoin('enrollments', 'enrollments.student_id', '=', 'students.id')
->leftJoin('users as u', 'u.id', '=', 'students.parent_id')
->groupByRaw('students.id, student_class.class_section_id, enrollments.enrollment_status, enrollments.admission_status, u.firstname, u.lastname')
->get()
->map(fn ($r) => (array) $r)
->all();
}
/**
* legacy: getStudentInfoByClassSectionId($classSectionId, $semester ignored, $schoolYear optional)
* NOTE: In legacy you appended updated_by from session; here we accept actor id.
*/
public static function getStudentInfoByClassSectionId(int $classSectionId, ?string $semester = null, ?string $schoolYear = null, ?int $actorId = null): array
{
if ($classSectionId <= 0) {
return [];
}
$q = DB::table('students')
->selectRaw('students.id AS student_id, students.school_id, students.firstname, students.lastname, sc.class_section_id')
->join('student_class as sc', 'sc.student_id', '=', 'students.id')
->where('sc.class_section_id', $classSectionId)
->where('students.is_active', 1)
->distinct()
->orderBy('students.lastname', 'asc')
->orderBy('students.firstname', 'asc');
if ($schoolYear !== null) {
$q->where('sc.school_year', $schoolYear);
}
$rows = $q->get()->map(fn ($r) => (array) $r)->all();
$actorId = $actorId ?? (auth()->id() ?? 0);
foreach ($rows as &$row) {
$row['updated_by'] = (int) $actorId;
}
unset($row);
return $rows;
}
/* ============================================================
* Optional validation helper
* ============================================================
*/
public static function rules(bool $updating = false): array
{
$req = $updating ? 'sometimes' : 'required';
return [
'school_id' => ['nullable', 'integer', 'min:1'],
'firstname' => [$req, 'string', 'max:120'],
'lastname' => [$req, 'string', 'max:120'],
'dob' => ['nullable', 'date'],
'age' => ['nullable', 'integer', 'min:0'],
'gender' => ['nullable', 'string', 'max:20'],
'registration_grade' => ['nullable', 'string', 'max:50'],
'photo_consent' => ['nullable', 'boolean'],
'is_new' => ['nullable', 'boolean'],
'parent_id' => ['nullable', 'integer'],
'school_year' => ['nullable', 'string', 'max:20'],
'registration_date' => ['nullable', 'date'],
'tuition_paid' => ['nullable', 'boolean'],
'year_of_registration' => ['nullable', 'integer', 'min:0'],
'rfid_tag' => ['nullable', 'string', 'max:120'],
'is_active' => ['nullable', 'boolean'],
];
}
}