perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one resource cost 10 queries, six cost 33 — about five per resource, because the available-minutes figure walked each resource's calendar on its own. Holidays, tenant overrides and branch hours are identical for every resource in a report, so they now load once outside the loop; shifts and exceptions load for all resources in one query each. The batched path is a new method rather than a change to rawAvailability, which the booking engine also calls. The test pins the shape of the growth, not an exact count. Also landed: - app:segment:seed-templates with beauty, dental and physio presets. Building four segments and their requirements by hand is the first thing a new clinic must do and the most tedious; this gives them something to edit instead of an empty page. It refuses to touch a service that already has segments unless --force, and it will not invent resource types the tenant never defined. - book-all is all-or-nothing, proven rather than asserted: with a calendar open one day a week and a 1-2 day protocol gap, session one finds a slot and session two cannot, and every session must come back planned. - credit_refundable: false takes the credit back with a negative adjustment and deletes nothing — the ledger stays append-only. - the segments editor has frontend tests, including that it sends back what the user sees and renders read-only without the permission. useBranches now returns [] for a non-array payload instead of throwing "branches.map is not a function" and taking the page down with it. BookingLocationsScanTest built a Clinic around a Doctor loaded from a different manager, which Doctrine treats as a new entity; it flushed fine most runs and failed on cascade in others. It now loads the doctor from the same manager. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,75 @@ final class ResourceAvailabilityService
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* همان `rawAvailability` برای چند منبع، ولی با خواندنِ دستهای.
|
||||
*
|
||||
* تعطیلات و استثناهای محیط و ساعت شعبه برای همهٔ منابع یکیاند و بیرون حلقه خوانده
|
||||
* میشوند؛ شیفت و استثنای هر منبع هم با یک کوئری برای همه میآید. بدون این، گزارشِ
|
||||
* چهل منبع دویست کوئری میزد.
|
||||
*
|
||||
* @param ClinicResource[] $resources همهٔ آنها باید یک شعبه داشته باشند
|
||||
* @return array<int, list<DayAvailability>> کلید: شناسهٔ منبع
|
||||
*/
|
||||
public function rawAvailabilityForAll(array $resources, int $from, int $to): array
|
||||
{
|
||||
if ($resources === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$first = $resources[array_key_first($resources)];
|
||||
$timezone = new \DateTimeZone($first->getAddress()->getTimezone());
|
||||
$startDay = $this->midnight($from, $timezone);
|
||||
$endDay = $this->midnight($to, $timezone);
|
||||
|
||||
$holidayMap = $this->holidays->mapForRange($startDay, $endDay);
|
||||
$overrideMap = $this->overrides->mapForRange(
|
||||
$first->getEntityType(),
|
||||
$first->getEntityId(),
|
||||
$startDay,
|
||||
$endDay,
|
||||
);
|
||||
|
||||
$branchByDay = $this->branchHoursByDay($first);
|
||||
|
||||
$ids = array_map(static fn (ClinicResource $r): int => (int) $r->getId(), $resources);
|
||||
$shiftsById = $this->calendars->findForResources($ids);
|
||||
$exceptionsById = $this->exceptions->findOverlappingForResources($ids, $startDay, $endDay + self::DAY_SECONDS);
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($resources as $resource) {
|
||||
$id = (int) $resource->getId();
|
||||
$byDay = [];
|
||||
|
||||
foreach ($shiftsById[$id] ?? [] as $shift) {
|
||||
if ($shift->isActive()) {
|
||||
$byDay[$shift->getDayOfWeek()][] = new TimeInterval($shift->getStartMinute(), $shift->getEndMinute());
|
||||
}
|
||||
}
|
||||
|
||||
$shiftsByDay = array_map(TimeInterval::mergeAll(...), $byDay);
|
||||
$days = [];
|
||||
|
||||
for ($day = $startDay; $day <= $endDay; $day = $this->nextMidnight($day, $timezone)) {
|
||||
$days[] = $this->buildDay(
|
||||
$resource,
|
||||
$day,
|
||||
$timezone,
|
||||
$shiftsByDay,
|
||||
$branchByDay,
|
||||
$holidayMap,
|
||||
$overrideMap,
|
||||
$exceptionsById[$id] ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
$out[$id] = $days;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, list<TimeInterval>> $shiftsByDay
|
||||
* @param array<int, list<TimeInterval>>|null $branchByDay
|
||||
|
||||
Reference in New Issue
Block a user