update api and add more features

This commit is contained in:
root
2026-06-04 02:24:41 -04:00
parent fa6c9519a0
commit 4e33882ac7
131 changed files with 34596 additions and 340 deletions
+194
View File
@@ -0,0 +1,194 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Per-student record tracking the promotion lifecycle for a single
* school year transition (see plan sections 3, 6, 9, 11).
*
* Promotion eligibility lives here separately from the actual class
* assignment the student is only "Promoted and Enrolled" once the
* parent finishes enrollment for the next school year.
*/
class StudentPromotionRecord extends BaseModel
{
protected $table = 'student_promotion_records';
protected $primaryKey = 'promotion_id';
public $timestamps = true;
public const STATUS_NOT_REVIEWED = 'not_reviewed';
public const STATUS_ELIGIBLE = 'eligible_for_promotion';
public const STATUS_AWAITING_PARENT = 'awaiting_parent_enrollment';
public const STATUS_ENROLLMENT_STARTED = 'enrollment_started';
public const STATUS_PROMOTED_AND_ENROLLED = 'promoted_and_enrolled';
public const STATUS_CONDITIONAL = 'conditional_promotion';
public const STATUS_REPEATED = 'repeated_level';
public const STATUS_ON_HOLD = 'on_hold';
public const STATUS_WITHDRAWN = 'withdrawn';
public const STATUS_GRADUATED = 'graduated';
public const STATUS_NOT_ENROLLED = 'not_enrolled_for_next_year';
public const ENROLLMENT_NOT_STARTED = 'not_started';
public const ENROLLMENT_IN_PROGRESS = 'in_progress';
public const ENROLLMENT_COMPLETED = 'completed';
public const ENROLLMENT_EXPIRED = 'expired';
public const ALL_STATUSES = [
self::STATUS_NOT_REVIEWED,
self::STATUS_ELIGIBLE,
self::STATUS_AWAITING_PARENT,
self::STATUS_ENROLLMENT_STARTED,
self::STATUS_PROMOTED_AND_ENROLLED,
self::STATUS_CONDITIONAL,
self::STATUS_REPEATED,
self::STATUS_ON_HOLD,
self::STATUS_WITHDRAWN,
self::STATUS_GRADUATED,
self::STATUS_NOT_ENROLLED,
];
protected $fillable = [
'student_id',
'parent_id',
'current_school_year',
'next_school_year',
'current_level_id',
'promoted_level_id',
'current_level_name',
'promoted_level_name',
'promotion_status',
'passed_current_level',
'final_average',
'attendance_ok',
'eligibility_notes',
'enrollment_required',
'enrollment_status',
'enrollment_id',
'parent_notified_at',
'enrollment_deadline',
'enrollment_started_at',
'enrollment_completed_at',
'promotion_finalized_at',
'info_confirmed',
'documents_uploaded',
'agreement_accepted',
'payment_completed',
'updated_by',
];
protected $casts = [
'student_id' => 'integer',
'parent_id' => 'integer',
'current_level_id' => 'integer',
'promoted_level_id' => 'integer',
'passed_current_level' => 'boolean',
'final_average' => 'float',
'attendance_ok' => 'boolean',
'enrollment_required' => 'boolean',
'enrollment_id' => 'integer',
'parent_notified_at' => 'datetime',
'enrollment_deadline' => 'date',
'enrollment_started_at' => 'datetime',
'enrollment_completed_at' => 'datetime',
'promotion_finalized_at' => 'datetime',
'info_confirmed' => 'boolean',
'documents_uploaded' => 'boolean',
'agreement_accepted' => 'boolean',
'payment_completed' => 'boolean',
'updated_by' => 'integer',
];
public function student(): BelongsTo
{
return $this->belongsTo(Student::class, 'student_id');
}
public function parent(): BelongsTo
{
return $this->belongsTo(User::class, 'parent_id');
}
public function enrollment(): BelongsTo
{
return $this->belongsTo(Enrollment::class, 'enrollment_id');
}
public function scopeForNextYear(Builder $q, string $year): Builder
{
return $q->where('next_school_year', $year);
}
public function scopeStatus(Builder $q, string $status): Builder
{
return $q->where('promotion_status', $status);
}
public function scopeForStudent(Builder $q, int $studentId): Builder
{
return $q->where('student_id', $studentId);
}
public function scopeForParent(Builder $q, int $parentId): Builder
{
return $q->where('parent_id', $parentId);
}
/**
* Statuses the parent portal should treat as "actionable" (parent
* can still complete enrollment).
*/
public static function parentActionableStatuses(): array
{
return [
self::STATUS_ELIGIBLE,
self::STATUS_AWAITING_PARENT,
self::STATUS_ENROLLMENT_STARTED,
];
}
/**
* Statuses considered "open" for the next school year (admin views
* may want to filter on these).
*/
public static function openStatuses(): array
{
return [
self::STATUS_NOT_REVIEWED,
self::STATUS_ELIGIBLE,
self::STATUS_AWAITING_PARENT,
self::STATUS_ENROLLMENT_STARTED,
self::STATUS_CONDITIONAL,
self::STATUS_ON_HOLD,
];
}
public function isFinalized(): bool
{
return in_array(
$this->promotion_status,
[
self::STATUS_PROMOTED_AND_ENROLLED,
self::STATUS_REPEATED,
self::STATUS_WITHDRAWN,
self::STATUS_GRADUATED,
self::STATUS_NOT_ENROLLED,
],
true
);
}
public function enrollmentChecklistComplete(): bool
{
// Mirrors plan section 8 parent-side completion criteria.
// Payment is only required when payment_completed has been
// explicitly set; treat null/false as still required by default.
return (bool) $this->info_confirmed
&& (bool) $this->documents_uploaded
&& (bool) $this->agreement_accepted
&& (bool) $this->payment_completed;
}
}