237 lines
6.4 KiB
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');
|
|
}
|
|
}
|