'boolean', 'last_login_at' => 'datetime', 'password' => 'hashed', 'two_factor_confirmed_at' => 'datetime', ]; } protected function fullName(): Attribute { return Attribute::get(fn () => $this->profile?->full_name ?? $this->username); } public function lecturer(): HasOne { return $this->hasOne(Lecturer::class); } public function profile(): HasOne { return $this->hasOne(UserProfile::class); } public function student(): HasOne { return $this->hasOne(Student::class); } public function courseRegistrationSubmissionLogs(): HasMany { return $this->hasMany(CourseRegistrationSubmissionLog::class, 'actor_id'); } public function createdAnnouncements(): HasMany { return $this->hasMany(Announcement::class, 'created_by'); } public function createdNotifications(): HasMany { return $this->hasMany(Notification::class, 'created_by'); } public function feedbacks(): HasMany { return $this->hasMany(Feedback::class); } public function handledFeedbacks(): HasMany { return $this->hasMany(Feedback::class, 'handled_by'); } public function letterRequests(): HasMany { return $this->hasMany(LetterRequest::class); } public function notifications(): HasMany { return $this->hasMany(Notification::class); } public function processedLetterRequests(): HasMany { return $this->hasMany(LetterRequest::class, 'processed_by'); } public function recordedTuitionPayments(): HasMany { return $this->hasMany(TuitionPayment::class, 'recorded_by'); } public function reviewedCourseRegistrationSubmissions(): HasMany { return $this->hasMany(CourseRegistrationSubmission::class, 'reviewed_by'); } public function uploads(): HasMany { return $this->hasMany(EditorUpload::class, 'uploaded_by'); } /** * A dosen may only manage (update/delete) materials of classes they lecture. */ public function canManageMaterial(Material $material): bool { if ($this->hasRole(UserRole::Dosen->value)) { return $material->courseClass->lecturer_id === $this->lecturer?->id; } return true; } public function isAdvisorOf(Student $student): bool { return $this->hasRole(UserRole::Dosen->value) && $this->lecturer && $student->academic_advisor_id === $this->lecturer->id; } public function isDepartmentLeaderOf(Student $student): bool { return $this->hasRole(UserRole::Kaprodi->value) && $this->lecturer && $student->department?->currentLeader?->lecturer_id === $this->lecturer->id; } /** * Gates page access: the student's academic advisor, the department * leader of their department, or staff roles may view a student's KRS. */ public function canAccessCourseRegistrationOf(Student $student): bool { if ($this->hasRole(UserRole::Dosen->value) || $this->hasRole(UserRole::Kaprodi->value)) { return $this->isAdvisorOf($student) || $this->isDepartmentLeaderOf($student); } return true; } /** * Only the student's academic advisor may approve/reject; the department * leader signs but does not decide. Other staff roles retain override access. */ public function canReviewCourseRegistrationOf(Student $student): bool { if ($this->hasRole(UserRole::Dosen->value)) { return $this->isAdvisorOf($student); } if ($this->hasRole(UserRole::Kaprodi->value)) { return false; } return true; } /** * @return array */ public function ledDepartmentIds(): array { return $this->lecturer ? $this->lecturer->leaderships()->whereNull('ended_at')->pluck('department_id')->all() : []; } }