Reservasi-Cafe/app/Models/Meja.php

237 lines
6.4 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Carbon\Carbon;
class Meja extends Model
{
use HasFactory;
protected $table = 'meja';
protected $fillable = [
'nomor_meja',
'kapasitas',
'status',
'kategori',
'deskripsi',
'gambar'
];
protected $attributes = [
'status' => 'tersedia',
'kategori' => 'outdoor'
];
/**
* Get all reservations for this table.
*/
public function reservasi()
{
return $this->hasMany(Reservasi::class, 'meja_id');
}
/**
* Get active reservations for this table.
*/
public function activeReservations(): HasMany
{
return $this->reservasi()
->where('status', '!=', 'cancelled')
->where('date', '>=', Carbon::today())
->orderBy('date')
->orderBy('start_time');
}
/**
* Check if table has any available slots for a specific date
*/
public function hasAvailableSlotsForDate($date): bool
{
$reservations = $this->getReservationsForDate($date);
$operatingHours = $this->getOperatingHours();
// If no reservations, the entire day is available
if ($reservations->isEmpty()) {
return true;
}
$reservedSlots = $reservations->map(function ($reservation) {
return [
'start' => Carbon::parse($reservation->start_time),
'end' => Carbon::parse($reservation->end_time)
];
})->sortBy('start');
// Check for any gaps between reservations
$previousEnd = Carbon::parse($operatingHours['start']);
foreach ($reservedSlots as $slot) {
if ($previousEnd->lt($slot['start'])) {
return true; // Found a gap
}
$previousEnd = max($previousEnd, $slot['end']);
}
// Check if there's time after the last reservation
$lastSlotEnd = $reservedSlots->last()['end'];
$closingTime = Carbon::parse($operatingHours['end']);
return $lastSlotEnd->lt($closingTime);
}
/**
* Get the next available date with open slots
*/
public function getNextAvailableDate($startDate = null): ?string
{
$date = $startDate ? Carbon::parse($startDate) : Carbon::today();
$maxDays = 30; // Limit search to next 30 days
$daysChecked = 0;
while ($daysChecked < $maxDays) {
if ($this->hasAvailableSlotsForDate($date->format('Y-m-d'))) {
return $date->format('Y-m-d');
}
$date->addDay();
$daysChecked++;
}
return null;
}
/**
* Get operating hours
*/
public function getOperatingHours(): array
{
return [
'start' => '10:00',
'end' => '22:00'
];
}
/**
* Get available time slots for a specific date
*/
public function getAvailableTimeSlots($date): array
{
$reservations = $this->getReservationsForDate($date);
$operatingHours = $this->getOperatingHours();
$slots = [];
$startTime = Carbon::parse($operatingHours['start']);
$endTime = Carbon::parse($operatingHours['end']);
$interval = 30; // 30 minutes interval
// If date is today, start from current time rounded up to next 30 minutes
if ($date === date('Y-m-d')) {
$now = Carbon::now();
$startTime = $now->copy()->addMinutes($interval - ($now->minute % $interval))->startOfMinute();
if ($startTime->lt($now)) {
$startTime->addMinutes($interval);
}
}
while ($startTime < $endTime) {
$timeSlot = $startTime->format('H:i');
$isAvailable = true;
// Check if this time slot conflicts with any existing reservations
foreach ($reservations as $reservation) {
$reservationStart = Carbon::parse($reservation->start_time);
$reservationEnd = Carbon::parse($reservation->end_time);
if ($startTime->between($reservationStart, $reservationEnd, true)) {
$isAvailable = false;
break;
}
}
if ($isAvailable) {
$slots[] = $timeSlot;
}
$startTime->addMinutes($interval);
}
return $slots;
}
/**
* Get reservations for a specific date
*/
public function getReservationsForDate($date)
{
return $this->reservasi()
->whereDate('date', $date)
->where('status', '!=', 'cancelled')
->get(['start_time', 'end_time']);
}
/**
* Check if table is available for a specific time slot.
*/
public function isAvailableForTimeSlot($date, $startTime, $endTime): bool
{
return Reservasi::isTimeSlotAvailable($this->id, $date, $startTime, $endTime);
}
/**
* Get the status badge class
*/
public function getStatusBadgeClass(): string
{
return match($this->status) {
'tersedia' => 'bg-green-100 text-green-800',
'dipesan' => 'bg-yellow-100 text-yellow-800',
'terisi' => 'bg-red-100 text-red-800',
default => 'bg-gray-100 text-gray-800',
};
}
/**
* Get formatted category name
*/
public function getFormattedCategory(): string
{
return match($this->kategori) {
'outdoor' => 'Outdoor',
'vip-outdoor' => 'VIP Outdoor',
'vip-indoor' => 'VIP Indoor',
default => ucfirst($this->kategori),
};
}
/**
* Check if the table is available
*/
public function getIsAvailableAttribute(): bool
{
return $this->status === 'tersedia';
}
/**
* Format nomor meja for display
*/
public function getNomorMejaFormattedAttribute(): string
{
return 'M' . str_pad($this->nomor_meja, 3, '0', STR_PAD_LEFT);
}
// Accessor untuk memastikan kapasitas selalu integer
public function getKapasitasAttribute($value)
{
return (int) $value;
}
// Scope untuk meja yang tersedia
public function scopeAvailable($query)
{
return $query->where('status', 'tersedia');
}
}