mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 05:31:10 -05:00
261 lines
6.5 KiB
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();
|
|
}
|
|
});
|
|
}
|
|
} |