update api and add more features
This commit is contained in:
@@ -17,6 +17,7 @@ class AuthorizedUser extends BaseModel
|
||||
'firstname',
|
||||
'lastname',
|
||||
'phone_number',
|
||||
'gender',
|
||||
'email',
|
||||
'relation_to_user',
|
||||
'token',
|
||||
|
||||
@@ -14,6 +14,9 @@ class EventCharges extends BaseModel
|
||||
|
||||
protected $fillable = [
|
||||
'event_id',
|
||||
'event_name',
|
||||
'description',
|
||||
'amount',
|
||||
'parent_id',
|
||||
'student_id',
|
||||
'participation',
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Maps current level → next level so the promotion workflow does not
|
||||
* hard-code grade progression inside controller logic
|
||||
* (see plan section 10).
|
||||
*/
|
||||
class LevelProgression extends BaseModel
|
||||
{
|
||||
protected $table = 'level_progressions';
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = [
|
||||
'current_level_id',
|
||||
'current_level_name',
|
||||
'next_level_id',
|
||||
'next_level_name',
|
||||
'order_index',
|
||||
'is_terminal',
|
||||
'is_active',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'current_level_id' => 'integer',
|
||||
'next_level_id' => 'integer',
|
||||
'order_index' => 'integer',
|
||||
'is_terminal' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function scopeActive(Builder $q): Builder
|
||||
{
|
||||
return $q->where('is_active', 1);
|
||||
}
|
||||
|
||||
public function scopeOrdered(Builder $q): Builder
|
||||
{
|
||||
return $q->orderBy('order_index')->orderBy('current_level_name');
|
||||
}
|
||||
|
||||
public static function findByCurrentLevelId(int $levelId): ?self
|
||||
{
|
||||
return static::query()
|
||||
->where('current_level_id', $levelId)
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
}
|
||||
|
||||
public static function findByCurrentLevelName(string $name): ?self
|
||||
{
|
||||
return static::query()
|
||||
->whereRaw('LOWER(current_level_name) = ?', [strtolower(trim($name))])
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Append-only audit trail for promotion / enrollment workflow actions
|
||||
* (see plan section 18).
|
||||
*/
|
||||
class PromotionAuditLog extends BaseModel
|
||||
{
|
||||
protected $table = 'promotion_audit_log';
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public const ACTION_RECORD_CREATED = 'record_created';
|
||||
public const ACTION_STATUS_CHANGED = 'status_changed';
|
||||
public const ACTION_ELIGIBILITY_EVALUATED = 'eligibility_evaluated';
|
||||
public const ACTION_PARENT_NOTIFIED = 'parent_notified';
|
||||
public const ACTION_ENROLLMENT_STARTED = 'enrollment_started';
|
||||
public const ACTION_ENROLLMENT_STEP_COMPLETED = 'enrollment_step_completed';
|
||||
public const ACTION_ENROLLMENT_COMPLETED = 'enrollment_completed';
|
||||
public const ACTION_PROMOTION_FINALIZED = 'promotion_finalized';
|
||||
public const ACTION_DEADLINE_SET = 'deadline_set';
|
||||
public const ACTION_REMINDER_SENT = 'reminder_sent';
|
||||
public const ACTION_DEADLINE_EXPIRED = 'deadline_expired';
|
||||
public const ACTION_MANUAL_OVERRIDE = 'manual_override';
|
||||
|
||||
protected $fillable = [
|
||||
'promotion_id',
|
||||
'student_id',
|
||||
'user_id',
|
||||
'action_type',
|
||||
'field',
|
||||
'old_value',
|
||||
'new_value',
|
||||
'notes',
|
||||
'performed_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'promotion_id' => 'integer',
|
||||
'student_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'performed_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function promotion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StudentPromotionRecord::class, 'promotion_id', 'promotion_id');
|
||||
}
|
||||
|
||||
public function actor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function scopeForPromotion(Builder $q, int $promotionId): Builder
|
||||
{
|
||||
return $q->where('promotion_id', $promotionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Records each reminder dispatched to parents for an open promotion
|
||||
* record (see plan section 14).
|
||||
*/
|
||||
class PromotionReminderLog extends BaseModel
|
||||
{
|
||||
protected $table = 'promotion_reminders_log';
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public const TYPE_FIRST = 'first';
|
||||
public const TYPE_HALFWAY = 'halfway';
|
||||
public const TYPE_FINAL = 'final';
|
||||
public const TYPE_EXPIRATION = 'expiration';
|
||||
public const TYPE_MANUAL = 'manual';
|
||||
|
||||
public const ALLOWED_TYPES = [
|
||||
self::TYPE_FIRST,
|
||||
self::TYPE_HALFWAY,
|
||||
self::TYPE_FINAL,
|
||||
self::TYPE_EXPIRATION,
|
||||
self::TYPE_MANUAL,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'promotion_id',
|
||||
'student_id',
|
||||
'parent_id',
|
||||
'reminder_type',
|
||||
'channel',
|
||||
'subject',
|
||||
'message',
|
||||
'sent_at',
|
||||
'sent_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'promotion_id' => 'integer',
|
||||
'student_id' => 'integer',
|
||||
'parent_id' => 'integer',
|
||||
'sent_by' => 'integer',
|
||||
'sent_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function promotion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StudentPromotionRecord::class, 'promotion_id', 'promotion_id');
|
||||
}
|
||||
|
||||
public function scopeForPromotion(Builder $q, int $promotionId): Builder
|
||||
{
|
||||
return $q->where('promotion_id', $promotionId);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user