tutoring/app/Services/AvailabilityService.php

56 lines
2.0 KiB
PHP

<?php
namespace App\Services;
use App\Models\ExternalBusyWindow;
use Carbon\Carbon;
class AvailabilityService
{
public function __construct(
private readonly string $tz = 'Europe/London',
private readonly int $bufferMins = 40
) {}
/**
* Check if a single slot (date + time) is blocked by external busy windows,
* considering a travel buffer before start and after end.
*/
public function isSlotBlockedByExternalBusy(string $dateYmd, string $timeHi, int $durationMins = 60): bool
{
// Build slot window in UTC
$slotStartLocal = Carbon::createFromFormat('Y-m-d H:i', "{$dateYmd} {$timeHi}", $this->tz);
$slotStartUtc = $slotStartLocal->clone()->utc();
$slotEndUtc = $slotStartUtc->clone()->addMinutes($durationMins);
// Expand comparison bounds by buffer
$slotStartMinusBuffer = $slotStartUtc->clone()->subMinutes($this->bufferMins);
$slotEndPlusBuffer = $slotEndUtc->clone()->addMinutes($this->bufferMins);
// Overlap: busyStart < slotEnd+buffer AND busyEnd > slotStart-buffer
return ExternalBusyWindow::query()
->where('starts_at', '<', $slotEndPlusBuffer)
->where('ends_at', '>', $slotStartMinusBuffer)
->exists();
}
/**
* For block-booking: given a set of future candidate dates for a given time,
* return the subset that are blocked (with reasons).
*
* @param array<string> $dateListYmd e.g. ['2025-10-27','2025-11-03', ...]
* @return array<int, array{date:string, blocked:bool}>
*/
public function checkSeriesConflicts(array $dateListYmd, string $timeHi, int $durationMins = 60): array
{
$results = [];
foreach ($dateListYmd as $ymd) {
$blocked = $this->isSlotBlockedByExternalBusy($ymd, $timeHi, $durationMins);
$results[] = [
'date' => $ymd,
'blocked' => $blocked,
];
}
return $results;
}
}