155 lines
4.1 KiB
PHP
155 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\ConsultationStatus;
|
|
use App\Models\BlockedTime;
|
|
use App\Models\Consultation;
|
|
use App\Models\WorkingHour;
|
|
use Carbon\Carbon;
|
|
|
|
class AvailabilityService
|
|
{
|
|
/**
|
|
* Get availability status for all days in a month.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public function getMonthAvailability(int $year, int $month): array
|
|
{
|
|
$startOfMonth = Carbon::create($year, $month, 1)->startOfMonth();
|
|
$endOfMonth = $startOfMonth->copy()->endOfMonth();
|
|
|
|
$availability = [];
|
|
$current = $startOfMonth->copy();
|
|
|
|
while ($current->lte($endOfMonth)) {
|
|
$availability[$current->format('Y-m-d')] = $this->getDateStatus($current);
|
|
$current->addDay();
|
|
}
|
|
|
|
return $availability;
|
|
}
|
|
|
|
/**
|
|
* Get the availability status for a specific date.
|
|
*/
|
|
public function getDateStatus(Carbon $date): string
|
|
{
|
|
// Past date
|
|
if ($date->lt(today())) {
|
|
return 'past';
|
|
}
|
|
|
|
// Check if fully blocked
|
|
if ($this->isDateFullyBlocked($date)) {
|
|
return 'blocked';
|
|
}
|
|
|
|
// Check working hours
|
|
$workingHour = WorkingHour::query()
|
|
->where('day_of_week', $date->dayOfWeek)
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (! $workingHour) {
|
|
return 'closed';
|
|
}
|
|
|
|
// Get available slots
|
|
$availableSlots = $this->getAvailableSlots($date);
|
|
|
|
if (empty($availableSlots)) {
|
|
return 'full';
|
|
}
|
|
|
|
$totalSlots = count($workingHour->getSlots(60));
|
|
if (count($availableSlots) < $totalSlots) {
|
|
return 'partial';
|
|
}
|
|
|
|
return 'available';
|
|
}
|
|
|
|
/**
|
|
* Get available time slots for a specific date.
|
|
*
|
|
* @return array<string>
|
|
*/
|
|
public function getAvailableSlots(Carbon $date): array
|
|
{
|
|
$workingHour = WorkingHour::query()
|
|
->where('day_of_week', $date->dayOfWeek)
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (! $workingHour) {
|
|
return [];
|
|
}
|
|
|
|
// All possible slots
|
|
$allSlots = $workingHour->getSlots(60);
|
|
|
|
// Booked slots (pending and approved consultations block the slot)
|
|
$bookedSlots = Consultation::query()
|
|
->whereDate('booking_date', $date)
|
|
->whereIn('status', [ConsultationStatus::Pending, ConsultationStatus::Approved])
|
|
->pluck('booking_time')
|
|
->map(fn ($t) => Carbon::parse($t)->format('H:i'))
|
|
->toArray();
|
|
|
|
// Blocked slots
|
|
$blockedSlots = $this->getBlockedSlots($date);
|
|
|
|
return array_values(array_diff($allSlots, $bookedSlots, $blockedSlots));
|
|
}
|
|
|
|
/**
|
|
* Get blocked time slots for a specific date.
|
|
*
|
|
* @return array<string>
|
|
*/
|
|
private function getBlockedSlots(Carbon $date): array
|
|
{
|
|
$blockedTimes = BlockedTime::query()
|
|
->whereDate('block_date', $date)
|
|
->get();
|
|
|
|
$blockedSlots = [];
|
|
|
|
foreach ($blockedTimes as $blocked) {
|
|
if ($blocked->isAllDay()) {
|
|
// Block all slots for the day
|
|
$workingHour = WorkingHour::query()
|
|
->where('day_of_week', $date->dayOfWeek)
|
|
->first();
|
|
|
|
return $workingHour ? $workingHour->getSlots(60) : [];
|
|
}
|
|
|
|
// Calculate blocked slots from time range
|
|
$start = Carbon::parse($blocked->start_time);
|
|
$end = Carbon::parse($blocked->end_time);
|
|
$current = $start->copy();
|
|
|
|
while ($current->lt($end)) {
|
|
$blockedSlots[] = $current->format('H:i');
|
|
$current->addMinutes(60);
|
|
}
|
|
}
|
|
|
|
return array_unique($blockedSlots);
|
|
}
|
|
|
|
/**
|
|
* Check if a date is fully blocked (all-day block).
|
|
*/
|
|
private function isDateFullyBlocked(Carbon $date): bool
|
|
{
|
|
return BlockedTime::query()
|
|
->whereDate('block_date', $date)
|
|
->whereNull('start_time')
|
|
->exists();
|
|
}
|
|
}
|