*/ protected $casts = [ 'capacity' => 'integer', 'signup_start_at' => 'datetime', 'signup_end_at' => 'datetime', 'is_final' => 'boolean', 'sort' => 'integer', 'enabled' => 'boolean', ]; public function competition(): BelongsTo { return $this->belongsTo(Competition::class); } public function registrations(): HasMany { return $this->hasMany(AudienceRegistration::class); } public function registeredCount(): int { if (array_key_exists('registrations_count', $this->attributes)) { return (int) $this->attributes['registrations_count']; } return (int) $this->registrations()->count(); } public function remainingCount(): int { return max(0, (int) $this->capacity - $this->registeredCount()); } public function checkedInCount(): int { if (array_key_exists('checked_in_count', $this->attributes)) { return (int) $this->attributes['checked_in_count']; } return (int) $this->registrations()->whereNotNull('checked_in_at')->count(); } public function windowStatus(?CarbonInterface $now = null): string { $now = ($now ?? now())->timezone(BizDateTime::TIMEZONE); $start = $this->signup_start_at?->copy()->timezone(BizDateTime::TIMEZONE); $end = $this->signup_end_at?->copy()->timezone(BizDateTime::TIMEZONE); if ($start && $now->lt($start)) { return self::STATUS_NOT_STARTED; } if ($end && $now->gt($end)) { return self::STATUS_EXPIRED; } if ($this->remainingCount() <= 0) { return self::STATUS_FULL; } return self::STATUS_OPEN; } public function isAvailable(?CarbonInterface $now = null): bool { return $this->enabled && $this->windowStatus($now) === self::STATUS_OPEN; } public function statusLabel(?CarbonInterface $now = null): string { return match ($this->windowStatus($now)) { self::STATUS_NOT_STARTED => $this->openFromLabel(), self::STATUS_EXPIRED => '已截止', self::STATUS_FULL => '已满额', default => '报名中', }; } public function openFromLabel(): string { $start = $this->signup_start_at?->copy()->timezone(BizDateTime::TIMEZONE); if ($start === null) { return '即将开放'; } return $start->format('m.d').' 开放'; } }