'integer', 'class_section_id' => 'integer', 'reviewed_by' => 'integer', 'commented_by' => 'integer', 'created_at' => 'datetime', // If comment_review is a tinyint(1) flag in DB, boolean cast helps 'comment_review' => 'boolean', ]; /* ============================================================ * Relationships (optional) * ============================================================ */ public function student(): BelongsTo { return $this->belongsTo(Student::class, 'student_id'); } public function classSection(): BelongsTo { return $this->belongsTo(ClassSection::class, 'class_section_id'); } public function reviewer(): BelongsTo { // Change Admin/User model to match your app return $this->belongsTo(Admin::class, 'reviewed_by'); } public function commenter(): BelongsTo { // Change Admin/User model to match your app return $this->belongsTo(Admin::class, 'commented_by'); } /* ============================================================ * Scopes * ============================================================ */ public function scopeForStudent(Builder $q, int $studentId): Builder { return $q->where('student_id', $studentId); } public function scopeForClassSection(Builder $q, int $classSectionId): Builder { return $q->where('class_section_id', $classSectionId); } public function scopeForTerm(Builder $q, string $schoolYear, string $semester): Builder { return $q->where('school_year', $schoolYear) ->where('semester', $semester); } public function scopeForScoreType(Builder $q, string $scoreType): Builder { return $q->where('score_type', $scoreType); } public function scopeReviewed(Builder $q): Builder { // supports either boolean flag or reviewed_by presence return $q->where(function (Builder $w) { $w->where('comment_review', 1) ->orWhereNotNull('reviewed_by'); }); } public function scopeUnreviewed(Builder $q): Builder { return $q->where(function (Builder $w) { $w->whereNull('comment_review')->orWhere('comment_review', 0); })->whereNull('reviewed_by'); } /* ============================================================ * Helpers * ============================================================ */ /** * Latest comment for a student/class/term/type. */ public static function latestFor( int $studentId, int $classSectionId, string $scoreType, string $schoolYear, string $semester ): ?self { return static::query() ->forStudent($studentId) ->forClassSection($classSectionId) ->forScoreType($scoreType) ->forTerm($schoolYear, $semester) ->orderByDesc('created_at') ->first(); } /** * Mark as reviewed (idempotent). */ public function markReviewed(int $reviewedBy): self { $this->comment_review = true; $this->reviewed_by = $reviewedBy; $this->save(); return $this; } /* ============================================================ * Optional validation rules helper (FormRequest/controller) * ============================================================ */ public static function rules(bool $updating = false): array { return [ 'student_id' => [$updating ? 'sometimes' : 'required', 'integer', 'min:1'], 'class_section_id' => [$updating ? 'sometimes' : 'required', 'integer', 'min:1'], 'score_type' => [$updating ? 'sometimes' : 'required', 'string', 'max:50'], 'school_year' => [$updating ? 'sometimes' : 'required', 'string', 'max:20'], 'semester' => [$updating ? 'sometimes' : 'required', 'string', 'max:20'], 'comment' => ['nullable', 'string', 'max:5000'], 'comment_review' => ['nullable', 'boolean'], 'reviewed_by' => ['nullable', 'integer'], 'commented_by' => ['nullable', 'integer'], // created_at is DB default; allow client to omit it 'created_at' => ['nullable', 'date'], ]; } }