*/ protected $fillable = [ 'name', 'slug', 'description', 'opening_date', 'closing_date', 'position', 'parent_id', 'ride_count', 'coaster_count', 'flat_ride_count', 'water_ride_count', 'daily_capacity', 'peak_wait_time', 'average_rating', 'total_rides_operated', 'retired_rides_count', 'last_new_ride_added', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'opening_date' => 'date', 'closing_date' => 'date', 'position' => 'integer', 'ride_count' => 'integer', 'coaster_count' => 'integer', 'flat_ride_count' => 'integer', 'water_ride_count' => 'integer', 'daily_capacity' => 'integer', 'peak_wait_time' => 'integer', 'average_rating' => 'decimal:2', 'total_rides_operated' => 'integer', 'retired_rides_count' => 'integer', 'last_new_ride_added' => 'date', ]; /** * Get the park that owns the area. */ public function park(): BelongsTo { return $this->belongsTo(Park::class); } /** * Get the parent area if this is a sub-area. */ public function parent(): BelongsTo { return $this->belongsTo(ParkArea::class, 'parent_id'); } /** * Get the sub-areas of this area. */ public function children(): HasMany { return $this->hasMany(ParkArea::class, 'parent_id') ->orderBy('position'); } /** * Get a brief description suitable for cards and previews. */ public function getBriefDescriptionAttribute(): string { $description = $this->description ?? ''; return strlen($description) > 150 ? substr($description, 0, 150) . '...' : $description; } /** * Get the opening year of the area. */ public function getOpeningYearAttribute(): ?string { return $this->opening_date?->format('Y'); } /** * Check if the area is currently operating. */ public function isOperating(): bool { if ($this->closing_date) { return false; } return true; } /** * Check if this area has sub-areas. */ public function hasChildren(): bool { return $this->children()->exists(); } /** * Check if this is a top-level area. */ public function isTopLevel(): bool { return is_null($this->parent_id); } /** * Get the next available position for a new area. */ public function getNextPosition(): int { $maxPosition = static::where('park_id', $this->park_id) ->where('parent_id', $this->parent_id) ->max('position'); return ($maxPosition ?? -1) + 1; } /** * Move this area to a new position. */ public function moveToPosition(int $newPosition): void { if ($newPosition === $this->position) { return; } $oldPosition = $this->position; if ($newPosition > $oldPosition) { // Moving down: decrement positions of items in between static::where('park_id', $this->park_id) ->where('parent_id', $this->parent_id) ->whereBetween('position', [$oldPosition + 1, $newPosition]) ->decrement('position'); } else { // Moving up: increment positions of items in between static::where('park_id', $this->park_id) ->where('parent_id', $this->parent_id) ->whereBetween('position', [$newPosition, $oldPosition - 1]) ->increment('position'); } $this->update(['position' => $newPosition]); } /** * Scope query to only include operating areas. */ public function scopeOperating($query) { return $query->whereNull('closing_date'); } /** * Scope query to only include top-level areas. */ public function scopeTopLevel($query) { return $query->whereNull('parent_id'); } /** * Override parent method to ensure unique slugs within a park. */ protected function generateSlug(): string { $slug = \Str::slug($this->name); $count = 2; while ( static::where('park_id', $this->park_id) ->where('slug', $slug) ->where('id', '!=', $this->id) ->exists() || static::whereHas('slugHistories', function ($query) use ($slug) { $query->where('slug', $slug); }) ->where('park_id', $this->park_id) ->where('id', '!=', $this->id) ->exists() ) { $slug = \Str::slug($this->name) . '-' . $count++; } return $slug; } /** * Get the route key for the model. */ public function getRouteKeyName(): string { return 'slug'; } /** * Find an area by its slug within a specific park. */ public static function findByParkAndSlug(Park $park, string $slug): ?self { // Try current slug $area = static::where('park_id', $park->id) ->where('slug', $slug) ->first(); if ($area) { return $area; } // Try historical slug $slugHistory = SlugHistory::where('slug', $slug) ->where('sluggable_type', static::class) ->whereHas('sluggable', function ($query) use ($park) { $query->where('park_id', $park->id); }) ->latest() ->first(); return $slugHistory ? static::find($slugHistory->sluggable_id) : null; } /** * Boot the model. */ protected static function boot() { parent::boot(); static::creating(function (ParkArea $area) { if (is_null($area->position)) { $area->position = $area->getNextPosition(); } }); } }