56 lines
2.0 KiB
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;
|
|
}
|
|
} |