'integer', 'student_id' => 'integer', 'viewed_at' => 'datetime', 'signed_at' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; /* ============================================================ * Relationships (optional) * ============================================================ */ public function parent(): BelongsTo { // Change to your actual parent model/table (often User/Parent/Guardian) return $this->belongsTo(ParentModel::class, 'parent_id'); } public function student(): BelongsTo { return $this->belongsTo(Student::class, 'student_id'); } /* ============================================================ * Scopes * ============================================================ */ public function scopeForParent(Builder $q, int $parentId): Builder { return $q->where('parent_id', $parentId); } public function scopeForStudent(Builder $q, int $studentId): Builder { return $q->where('student_id', $studentId); } public function scopeForTerm(Builder $q, string $schoolYear, string $semester): Builder { return $q->where('school_year', $schoolYear) ->where('semester', $semester); } public function scopeSigned(Builder $q): Builder { return $q->whereNotNull('signed_at'); } public function scopeViewed(Builder $q): Builder { return $q->whereNotNull('viewed_at'); } /* ============================================================ * Helpers * ============================================================ */ /** * Mark as viewed (idempotent). */ public function markViewed(?string $ip = null): self { if ($this->viewed_at === null) { $this->viewed_at = now(); } // optional: store IP even on view (only if signer_ip is empty) if ($ip !== null && empty($this->signer_ip)) { $this->signer_ip = $ip; } $this->save(); return $this; } /** * Mark as signed (idempotent). */ public function markSigned(string $signedName, ?string $ip = null): self { $this->signed_name = $signedName; if ($this->signed_at === null) { $this->signed_at = now(); } if ($ip !== null) { $this->signer_ip = $ip; } // If they signed, it was definitely viewed. if ($this->viewed_at === null) { $this->viewed_at = now(); } $this->save(); return $this; } /* ============================================================ * Optional validation helper (FormRequest/controller) * ============================================================ */ public static function rules(bool $updating = false): array { return [ 'parent_id' => [$updating ? 'sometimes' : 'required', 'integer', 'min:1'], 'student_id' => [$updating ? 'sometimes' : 'required', 'integer', 'min:1'], 'school_year' => [$updating ? 'sometimes' : 'required', 'string', 'max:20'], 'semester' => [$updating ? 'sometimes' : 'required', 'string', 'max:20'], 'viewed_at' => ['nullable', 'date'], 'signed_at' => ['nullable', 'date', 'after_or_equal:viewed_at'], 'signed_name' => ['nullable', 'string', 'max:255'], 'signer_ip' => ['nullable', 'string', 'max:45'], // IPv4/IPv6 ]; } }