libra/app/Services/AvailabilityService.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();
}
}