mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-22 19:11:09 -05:00
Add models, enums, and services for user roles, theme preferences, slug history, and ID generation
This commit is contained in:
169
app/Traits/HasAreaStatistics.php
Normal file
169
app/Traits/HasAreaStatistics.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait HasAreaStatistics
|
||||
{
|
||||
/**
|
||||
* Get the total ride count including all types.
|
||||
*/
|
||||
public function getTotalRideCountAttribute(): int
|
||||
{
|
||||
return $this->ride_count ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentage of coasters among all rides.
|
||||
*/
|
||||
public function getCoasterPercentageAttribute(): float
|
||||
{
|
||||
if ($this->ride_count === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->coaster_count / $this->ride_count) * 100, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of ride types distribution.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getRideDistributionAttribute(): array
|
||||
{
|
||||
return [
|
||||
'coasters' => $this->coaster_count ?? 0,
|
||||
'flat_rides' => $this->flat_ride_count ?? 0,
|
||||
'water_rides' => $this->water_ride_count ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted daily capacity.
|
||||
*/
|
||||
public function getFormattedDailyCapacityAttribute(): string
|
||||
{
|
||||
if (!$this->daily_capacity) {
|
||||
return 'Unknown capacity';
|
||||
}
|
||||
|
||||
return number_format($this->daily_capacity) . ' riders/day';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted peak wait time.
|
||||
*/
|
||||
public function getFormattedPeakWaitTimeAttribute(): string
|
||||
{
|
||||
if (!$this->peak_wait_time) {
|
||||
return 'Unknown wait time';
|
||||
}
|
||||
|
||||
return $this->peak_wait_time . ' minutes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rating display with stars.
|
||||
*/
|
||||
public function getRatingDisplayAttribute(): string
|
||||
{
|
||||
if (!$this->average_rating) {
|
||||
return 'Not rated';
|
||||
}
|
||||
|
||||
$stars = str_repeat('★', floor($this->average_rating));
|
||||
$stars .= str_repeat('☆', 5 - floor($this->average_rating));
|
||||
|
||||
return $stars . ' (' . number_format($this->average_rating, 1) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get historical statistics summary.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getHistoricalStatsAttribute(): array
|
||||
{
|
||||
return [
|
||||
'total_operated' => $this->total_rides_operated,
|
||||
'retired_count' => $this->retired_rides_count,
|
||||
'last_addition' => $this->last_new_ride_added?->format('M Y') ?? 'Never',
|
||||
'retirement_rate' => $this->getRetirementRate(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the retirement rate (retired rides as percentage of total operated).
|
||||
*/
|
||||
protected function getRetirementRate(): float
|
||||
{
|
||||
if ($this->total_rides_operated === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->retired_rides_count / $this->total_rides_operated) * 100, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ride counts.
|
||||
*
|
||||
* @param array<string, int> $counts
|
||||
*/
|
||||
public function updateRideCounts(array $counts): void
|
||||
{
|
||||
$this->update([
|
||||
'ride_count' => $counts['total'] ?? 0,
|
||||
'coaster_count' => $counts['coasters'] ?? 0,
|
||||
'flat_ride_count' => $counts['flat_rides'] ?? 0,
|
||||
'water_ride_count' => $counts['water_rides'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visitor statistics.
|
||||
*/
|
||||
public function updateVisitorStats(int $dailyCapacity, int $peakWaitTime, float $rating): void
|
||||
{
|
||||
$this->update([
|
||||
'daily_capacity' => $dailyCapacity,
|
||||
'peak_wait_time' => $peakWaitTime,
|
||||
'average_rating' => round($rating, 2),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a new ride addition.
|
||||
*/
|
||||
public function recordNewRide(): void
|
||||
{
|
||||
$this->increment('total_rides_operated');
|
||||
$this->update(['last_new_ride_added' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a ride retirement.
|
||||
*/
|
||||
public function recordRetirement(): void
|
||||
{
|
||||
$this->increment('retired_rides_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all statistics to zero.
|
||||
*/
|
||||
public function resetStatistics(): void
|
||||
{
|
||||
$this->update([
|
||||
'ride_count' => 0,
|
||||
'coaster_count' => 0,
|
||||
'flat_ride_count' => 0,
|
||||
'water_ride_count' => 0,
|
||||
'daily_capacity' => null,
|
||||
'peak_wait_time' => null,
|
||||
'average_rating' => null,
|
||||
'total_rides_operated' => 0,
|
||||
'retired_rides_count' => 0,
|
||||
'last_new_ride_added' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
202
app/Traits/HasParkStatistics.php
Normal file
202
app/Traits/HasParkStatistics.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait HasParkStatistics
|
||||
{
|
||||
/**
|
||||
* Get the total ride count including all types.
|
||||
*/
|
||||
public function getTotalRideCountAttribute(): int
|
||||
{
|
||||
return $this->total_rides ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentage of coasters among all rides.
|
||||
*/
|
||||
public function getCoasterPercentageAttribute(): float
|
||||
{
|
||||
if ($this->total_rides === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->total_coasters / $this->total_rides) * 100, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of ride types distribution.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getRideDistributionAttribute(): array
|
||||
{
|
||||
return [
|
||||
'coasters' => $this->total_coasters ?? 0,
|
||||
'flat_rides' => $this->total_flat_rides ?? 0,
|
||||
'water_rides' => $this->total_water_rides ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of area statistics.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getAreaDistributionAttribute(): array
|
||||
{
|
||||
return [
|
||||
'total' => $this->total_areas ?? 0,
|
||||
'operating' => $this->operating_areas ?? 0,
|
||||
'closed' => $this->closed_areas ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted daily capacity.
|
||||
*/
|
||||
public function getFormattedDailyCapacityAttribute(): string
|
||||
{
|
||||
if (!$this->total_daily_capacity) {
|
||||
return 'Unknown capacity';
|
||||
}
|
||||
|
||||
return number_format($this->total_daily_capacity) . ' riders/day';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted average wait time.
|
||||
*/
|
||||
public function getFormattedWaitTimeAttribute(): string
|
||||
{
|
||||
if (!$this->average_wait_time) {
|
||||
return 'Unknown wait time';
|
||||
}
|
||||
|
||||
return $this->average_wait_time . ' minutes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rating display with stars.
|
||||
*/
|
||||
public function getRatingDisplayAttribute(): string
|
||||
{
|
||||
if (!$this->average_rating) {
|
||||
return 'Not rated';
|
||||
}
|
||||
|
||||
$stars = str_repeat('★', floor($this->average_rating));
|
||||
$stars .= str_repeat('☆', 5 - floor($this->average_rating));
|
||||
|
||||
return $stars . ' (' . number_format($this->average_rating, 1) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get historical statistics summary.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getHistoricalStatsAttribute(): array
|
||||
{
|
||||
return [
|
||||
'total_operated' => $this->total_rides_operated,
|
||||
'total_retired' => $this->total_rides_retired,
|
||||
'last_expansion' => $this->last_expansion_date?->format('M Y') ?? 'Never',
|
||||
'last_update' => $this->last_major_update?->format('M Y') ?? 'Never',
|
||||
'retirement_rate' => $this->getRetirementRate(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get performance metrics summary.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getPerformanceMetricsAttribute(): array
|
||||
{
|
||||
return [
|
||||
'utilization' => $this->utilization_rate ? $this->utilization_rate . '%' : 'Unknown',
|
||||
'peak_attendance' => $this->peak_daily_attendance ? number_format($this->peak_daily_attendance) : 'Unknown',
|
||||
'satisfaction' => $this->guest_satisfaction ? number_format($this->guest_satisfaction, 1) . '/5.0' : 'Unknown',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the retirement rate (retired rides as percentage of total operated).
|
||||
*/
|
||||
protected function getRetirementRate(): float
|
||||
{
|
||||
if ($this->total_rides_operated === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->total_rides_retired / $this->total_rides_operated) * 100, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update area counts.
|
||||
*/
|
||||
public function updateAreaCounts(): void
|
||||
{
|
||||
$this->update([
|
||||
'total_areas' => $this->areas()->count(),
|
||||
'operating_areas' => $this->areas()->operating()->count(),
|
||||
'closed_areas' => $this->areas()->whereNotNull('closing_date')->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ride statistics.
|
||||
*/
|
||||
public function updateRideStatistics(): void
|
||||
{
|
||||
$areas = $this->areas;
|
||||
|
||||
$this->update([
|
||||
'total_rides' => $areas->sum('ride_count'),
|
||||
'total_coasters' => $areas->sum('coaster_count'),
|
||||
'total_flat_rides' => $areas->sum('flat_ride_count'),
|
||||
'total_water_rides' => $areas->sum('water_ride_count'),
|
||||
'total_daily_capacity' => $areas->sum('daily_capacity'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visitor statistics.
|
||||
*/
|
||||
public function updateVisitorStats(): void
|
||||
{
|
||||
$areas = $this->areas()->whereNotNull('average_rating');
|
||||
|
||||
$this->update([
|
||||
'average_wait_time' => $areas->avg('peak_wait_time'),
|
||||
'average_rating' => $areas->avg('average_rating'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an expansion.
|
||||
*/
|
||||
public function recordExpansion(): void
|
||||
{
|
||||
$this->update(['last_expansion_date' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a major update.
|
||||
*/
|
||||
public function recordMajorUpdate(): void
|
||||
{
|
||||
$this->update(['last_major_update' => now()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all statistics.
|
||||
*/
|
||||
public function refreshStatistics(): void
|
||||
{
|
||||
$this->updateAreaCounts();
|
||||
$this->updateRideStatistics();
|
||||
$this->updateVisitorStats();
|
||||
}
|
||||
}
|
||||
106
app/Traits/HasSlugHistory.php
Normal file
106
app/Traits/HasSlugHistory.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\SlugHistory;
|
||||
|
||||
trait HasSlugHistory
|
||||
{
|
||||
/**
|
||||
* Boot the trait.
|
||||
*/
|
||||
protected static function bootHasSlugHistory(): void
|
||||
{
|
||||
static::saving(function ($model) {
|
||||
if (!$model->slug) {
|
||||
$model->slug = $model->generateSlug();
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function ($model) {
|
||||
if ($model->isDirty('slug') && $model->getOriginal('slug')) {
|
||||
static::addToSlugHistory($model->getOriginal('slug'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all slug histories for this model.
|
||||
*/
|
||||
public function slugHistories(): MorphMany
|
||||
{
|
||||
return $this->morphMany(SlugHistory::class, 'sluggable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a slug to the history.
|
||||
*/
|
||||
protected function addToSlugHistory(string $slug): void
|
||||
{
|
||||
$this->slugHistories()->create(['slug' => $slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique slug.
|
||||
*/
|
||||
protected function generateSlug(): string
|
||||
{
|
||||
$slug = Str::slug($this->name);
|
||||
$count = 2;
|
||||
|
||||
while (
|
||||
static::where('slug', $slug)
|
||||
->where('id', '!=', $this->id)
|
||||
->exists() ||
|
||||
SlugHistory::where('slug', $slug)
|
||||
->where('sluggable_type', get_class($this))
|
||||
->where('sluggable_id', '!=', $this->id)
|
||||
->exists()
|
||||
) {
|
||||
$slug = Str::slug($this->name) . '-' . $count++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its current or historical slug.
|
||||
*
|
||||
* @param string $slug
|
||||
* @return static|null
|
||||
*/
|
||||
public static function findBySlug(string $slug)
|
||||
{
|
||||
// Try current slug
|
||||
$model = static::where('slug', $slug)->first();
|
||||
if ($model) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
// Try historical slug
|
||||
$slugHistory = SlugHistory::where('slug', $slug)
|
||||
->where('sluggable_type', static::class)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($slugHistory) {
|
||||
return static::find($slugHistory->sluggable_id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its current or historical slug or fail.
|
||||
*
|
||||
* @param string $slug
|
||||
* @return static
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public static function findBySlugOrFail(string $slug)
|
||||
{
|
||||
return static::findBySlug($slug) ?? throw new \Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user