Files
thrillwiki_laravel/app/Models/ParkArea.php

261 lines
6.5 KiB
PHP

<?php
namespace App\Models;
use App\Traits\HasSlugHistory;
use App\Traits\HasAreaStatistics;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class ParkArea extends Model
{
use HasFactory, HasSlugHistory, HasAreaStatistics;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
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<string, string>
*/
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();
}
});
}
}