feat: Implement resource booking functionality
- Add service timeline builder for appointments to manage available slots. - Create a hook to fetch resource booking services with effective durations. - Develop ResourceBookingSlotController to handle API requests for resource booking slots. - Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations. - Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
This commit is contained in:
@@ -113,6 +113,46 @@ class AppointmentRepository extends ServiceEntityRepository
|
||||
return $this->occupiedIntervals($doctor, $from, $to, true, $excludeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* بازههای اشغالشدهٔ یک **منبع** در پنجرهٔ [$from, $to).
|
||||
*
|
||||
* همان معیارِ نوبتِ پزشک، ولی محورش `resource_id` است: منبع میتواند نوبتهایی از
|
||||
* چند پزشک داشته باشد، پس فیلترِ پزشک اینجا هم غلط است و هم ناقص.
|
||||
*
|
||||
* ردیفهای `resource_occupancy` اینجا نمیآیند — آنها مسیرِ موتور منبعمحورند و
|
||||
* {@see \App\Resource\Service\ResourceBookingSlotService} هر دو را کنار هم میگذارد.
|
||||
*
|
||||
* @return list<array{start: int, end: int}> مرتبشده بر اساس start
|
||||
*/
|
||||
public function findResourceBusyIntervals(int $resourceId, int $from, int $to, ?int $excludeId = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('a')
|
||||
->select('a.slotStart AS start, a.slotEnd AS end')
|
||||
->where('IDENTITY(a.resource) = :resource')
|
||||
->andWhere('a.slotStart < :to')
|
||||
->andWhere('a.slotEnd > :from')
|
||||
->andWhere('a.isReserve = false')
|
||||
->andWhere(
|
||||
'a.status IN (:blocking) OR (a.status = :pending AND (a.expiresAt IS NULL OR a.expiresAt > :now))'
|
||||
)
|
||||
->setParameter('resource', $resourceId)
|
||||
->setParameter('blocking', Appointment::SLOT_BLOCKING_STATUSES)
|
||||
->setParameter('pending', Appointment::STATUS_PENDING)
|
||||
->setParameter('now', time())
|
||||
->setParameter('from', $from)
|
||||
->setParameter('to', $to)
|
||||
->orderBy('a.slotStart', 'ASC');
|
||||
|
||||
if ($excludeId !== null) {
|
||||
$qb->andWhere('a.id != :excludeId')->setParameter('excludeId', $excludeId);
|
||||
}
|
||||
|
||||
return array_map(
|
||||
static fn (array $r): array => ['start' => (int) $r['start'], 'end' => (int) $r['end']],
|
||||
$qb->getQuery()->getScalarResult(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* همان مجموعهای که isSlotTaken() یک اسلات را با آن میسنجد — شامل نوبتهای
|
||||
* رزروی. برای پیمایش چندروزه که نمیخواهد به ازای هر اسلات یک کوئری بزند.
|
||||
|
||||
Reference in New Issue
Block a user