feat(appointments): a resource-first view on the timeline
The appointments page only ever showed one doctor's row, but in the resource-first model a single appointment can hold a room and a device at the same time, and that — not the doctor's schedule — is what runs the capacity out. An hour could look free on the doctor's lane while the only alexandrite laser was already taken. A third view, "منابع", draws one lane per resource for the selected day. Blocks come from resource_occupancy rather than the appointment: that range includes the device's setup and cleanup minutes and is the same range the availability engine treats as busy. A multi-segment appointment therefore shows up on every resource it holds, and each block links to the appointment it belongs to. GET /api/v1/resources/timeline keeps a fixed query count — one for occupancy, one for shifts, one for the patient names — instead of one per resource. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,4 +132,58 @@ class ResourceOccupancyRepository extends ServiceEntityRepository
|
||||
{
|
||||
return $this->findBy(['appointmentId' => $appointmentId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ردیفهای اشغالِ یک روز برای تایملاین منابع — **یک کوئری برای همهٔ منابع**.
|
||||
*
|
||||
* برخلاف `busyByResource()` که فقط بازه میخواهد، اینجا شناسهٔ نوبت و نام بخش هم
|
||||
* لازم است تا هر بلوک بگوید مالِ کدام بیمار و کدام مرحله است. آرایه برمیگردد نه
|
||||
* entity، چون هیچکدام از این ردیفها قرار نیست تغییر کند.
|
||||
*
|
||||
* @param int[] $resourceIds
|
||||
* @return array<int, list<array{uuid: string, starts_at: int, ends_at: int, status: string, segment_name: ?string, appointment_id: ?int}>>
|
||||
*/
|
||||
public function dayByResource(array $resourceIds, int $from, int $to): array
|
||||
{
|
||||
if ($resourceIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = $this->createQueryBuilder('o')
|
||||
->select(
|
||||
'IDENTITY(o.resource) AS resource_id',
|
||||
'o.uuid AS uuid',
|
||||
'o.startsAt AS starts_at',
|
||||
'o.endsAt AS ends_at',
|
||||
'o.status AS status',
|
||||
'o.segmentName AS segment_name',
|
||||
'o.appointmentId AS appointment_id',
|
||||
)
|
||||
->where('o.resource IN (:ids)')
|
||||
->andWhere('o.startsAt < :to')
|
||||
->andWhere('o.endsAt > :from')
|
||||
// ردیف آزادشده تاریخچه است؛ در تایملاینِ «الان چه چیزی گرفته است» جا ندارد.
|
||||
->andWhere('o.status IN (:blocking)')
|
||||
->setParameter('blocking', ResourceOccupancy::BLOCKING_STATUSES)
|
||||
->setParameter('ids', $resourceIds)
|
||||
->setParameter('from', $from)
|
||||
->setParameter('to', $to)
|
||||
->orderBy('o.startsAt', 'ASC')
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
$byResource = [];
|
||||
foreach ($rows as $row) {
|
||||
$byResource[(int) $row['resource_id']][] = [
|
||||
'uuid' => (string) $row['uuid'],
|
||||
'starts_at' => (int) $row['starts_at'],
|
||||
'ends_at' => (int) $row['ends_at'],
|
||||
'status' => (string) $row['status'],
|
||||
'segment_name' => $row['segment_name'] !== null ? (string) $row['segment_name'] : null,
|
||||
'appointment_id' => $row['appointment_id'] !== null ? (int) $row['appointment_id'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $byResource;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user