58726ee0e9
API CI/CD / Validate (composer + pint) (push) Successful in 3m6s
API CI/CD / Test (PHPUnit) (push) Failing after 5m48s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 1m33s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
418 lines
14 KiB
PHP
418 lines
14 KiB
PHP
<?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',
|
||
'registration_grade',
|
||
'photo_consent',
|
||
'is_new',
|
||
'parent_id',
|
||
'school_year',
|
||
'semester',
|
||
'registration_date',
|
||
'tuition_paid',
|
||
'year_of_registration',
|
||
'rfid_tag',
|
||
'is_active',
|
||
];
|
||
|
||
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->whereExists(function ($sub) use ($schoolYear) {
|
||
$sub->selectRaw('1')
|
||
->from('student_class as sc')
|
||
->whereColumn('sc.student_id', 'students.id')
|
||
->where('sc.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 doesn’t 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'],
|
||
];
|
||
}
|
||
}
|