'boolean', 'display_order' => 'integer', 'view_count' => 'integer', 'published_at' => 'datetime', 'expires_at' => 'datetime', 'archived_at' => 'datetime', ]; // ==================== Relationships ==================== /** * Get the user who created this announcement */ public function creator() { return $this->belongsTo(User::class, 'created_by_user_id'); } /** * Get the user who last updated this announcement */ public function lastUpdatedBy() { return $this->belongsTo(User::class, 'last_updated_by_user_id'); } // ==================== Status Check Methods ==================== /** * Check if announcement is draft */ public function isDraft(): bool { return $this->status === self::STATUS_DRAFT; } /** * Check if announcement is published */ public function isPublished(): bool { return $this->status === self::STATUS_PUBLISHED; } /** * Check if announcement is archived */ public function isArchived(): bool { return $this->status === self::STATUS_ARCHIVED; } /** * Check if announcement is pinned */ public function isPinned(): bool { return $this->is_pinned; } /** * Check if announcement is expired */ public function isExpired(): bool { if (!$this->expires_at) { return false; } return $this->expires_at->isPast(); } /** * Check if announcement is scheduled (published_at is in the future) */ public function isScheduled(): bool { if (!$this->published_at) { return false; } return $this->published_at->isFuture(); } /** * Check if announcement is currently active */ public function isActive(): bool { return $this->isPublished() && !$this->isExpired() && (!$this->published_at || $this->published_at->isPast()); } // ==================== Access Control Methods ==================== /** * Check if a user can view this announcement */ public function canBeViewedBy(?User $user): bool { // Draft announcements - only creator and admins can view if ($this->isDraft()) { if (!$user) { return false; } return $user->id === $this->created_by_user_id || $user->hasRole('admin') || $user->can('manage_all_announcements'); } // Archived announcements - only admins can view if ($this->isArchived()) { if (!$user) { return false; } return $user->hasRole('admin') || $user->can('manage_all_announcements'); } // Expired announcements - hidden from regular users if ($this->isExpired()) { if (!$user) { return false; } return $user->hasRole('admin') || $user->can('manage_all_announcements'); } // Scheduled announcements - not yet visible if ($this->isScheduled()) { if (!$user) { return false; } return $user->id === $this->created_by_user_id || $user->hasRole('admin') || $user->can('manage_all_announcements'); } // Check access level for published announcements if ($this->access_level === self::ACCESS_LEVEL_PUBLIC) { return true; } if (!$user) { return false; } if ($user->hasRole('admin')) { return true; } if ($this->access_level === self::ACCESS_LEVEL_MEMBERS) { return $user->member && $user->member->hasPaidMembership(); } if ($this->access_level === self::ACCESS_LEVEL_BOARD) { return $user->hasRole(['admin', 'finance_chair', 'finance_board_member']); } if ($this->access_level === self::ACCESS_LEVEL_ADMIN) { return $user->hasRole('admin'); } return false; } /** * Check if a user can edit this announcement */ public function canBeEditedBy(User $user): bool { // Admin and users with manage_all_announcements can edit all if ($user->hasRole('admin') || $user->can('manage_all_announcements')) { return true; } // User must have edit_announcements permission if (!$user->can('edit_announcements')) { return false; } // Can only edit own announcements return $user->id === $this->created_by_user_id; } // ==================== Query Scopes ==================== /** * Scope to only published announcements */ public function scopePublished(Builder $query): Builder { return $query->where('status', self::STATUS_PUBLISHED); } /** * Scope to only draft announcements */ public function scopeDraft(Builder $query): Builder { return $query->where('status', self::STATUS_DRAFT); } /** * Scope to only archived announcements */ public function scopeArchived(Builder $query): Builder { return $query->where('status', self::STATUS_ARCHIVED); } /** * Scope to only active announcements (published, not expired, not scheduled) */ public function scopeActive(Builder $query): Builder { return $query->where('status', self::STATUS_PUBLISHED) ->where(function ($q) { $q->whereNull('published_at') ->orWhere('published_at', '<=', now()); }) ->where(function ($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); } /** * Scope to only pinned announcements */ public function scopePinned(Builder $query): Builder { return $query->where('is_pinned', true); } /** * Scope to filter by access level */ public function scopeForAccessLevel(Builder $query, User $user): Builder { if ($user->hasRole('admin')) { return $query; } $accessLevels = [self::ACCESS_LEVEL_PUBLIC]; if ($user->member && $user->member->hasPaidMembership()) { $accessLevels[] = self::ACCESS_LEVEL_MEMBERS; } if ($user->hasRole(['finance_chair', 'finance_board_member'])) { $accessLevels[] = self::ACCESS_LEVEL_BOARD; } return $query->whereIn('access_level', $accessLevels); } // ==================== Helper Methods ==================== /** * Publish this announcement */ public function publish(?User $user = null): void { $updates = [ 'status' => self::STATUS_PUBLISHED, ]; if (!$this->published_at) { $updates['published_at'] = now(); } if ($user) { $updates['last_updated_by_user_id'] = $user->id; } $this->update($updates); } /** * Archive this announcement */ public function archive(?User $user = null): void { $updates = [ 'status' => self::STATUS_ARCHIVED, 'archived_at' => now(), ]; if ($user) { $updates['last_updated_by_user_id'] = $user->id; } $this->update($updates); } /** * Pin this announcement */ public function pin(?int $order = null, ?User $user = null): void { $updates = [ 'is_pinned' => true, 'display_order' => $order ?? 0, ]; if ($user) { $updates['last_updated_by_user_id'] = $user->id; } $this->update($updates); } /** * Unpin this announcement */ public function unpin(?User $user = null): void { $updates = [ 'is_pinned' => false, 'display_order' => 0, ]; if ($user) { $updates['last_updated_by_user_id'] = $user->id; } $this->update($updates); } /** * Increment view count */ public function incrementViewCount(): void { $this->increment('view_count'); } /** * Get the access level label in Chinese */ public function getAccessLevelLabel(): string { return match($this->access_level) { self::ACCESS_LEVEL_PUBLIC => '公開', self::ACCESS_LEVEL_MEMBERS => '會員', self::ACCESS_LEVEL_BOARD => '理事會', self::ACCESS_LEVEL_ADMIN => '管理員', default => '未知', }; } /** * Get status label in Chinese */ public function getStatusLabel(): string { return match($this->status) { self::STATUS_DRAFT => '草稿', self::STATUS_PUBLISHED => '已發布', self::STATUS_ARCHIVED => '已歸檔', default => '未知', }; } /** * Get status badge color */ public function getStatusBadgeColor(): string { return match($this->status) { self::STATUS_DRAFT => 'gray', self::STATUS_PUBLISHED => 'green', self::STATUS_ARCHIVED => 'yellow', default => 'gray', }; } /** * Get content excerpt (first 150 characters) */ public function getExcerpt(int $length = 150): string { return \Illuminate\Support\Str::limit($this->content, $length); } }