[[ 'active' => true, 'location_id' => $locationId, 'start_time' => $start, 'end_time' => $end, 'duration_per_patient' => 20, ]]]; return array_fill_keys(array_map('strval', range(0, 6)), $day); } private function makeDoctorWithSchedules(int $clinicCount): Doctor { $doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']); $doctor = new Doctor($doctorUser, 'دکتر پرمحل'); $doctor->setMobileNumber($doctorUser->getMobileNumber()); $this->em->persist($doctor); $this->em->flush(); $personalAddress = DoctorAddress::forDoctor($doctor); $this->em->persist($personalAddress); $this->em->flush(); $this->em->persist($this->newWeeklySchedule( $doctor, $this->weekOfSessions($personalAddress->getId(), '09:00', '13:00') )); for ($i = 0; $i < $clinicCount; $i++) { $clinic = new Clinic($this->createUser(['ROLE_USER', 'ROLE_CLINIC'])); $clinic->setName("کلینیک $i"); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $this->em->flush(); $address = DoctorAddress::forClinic($clinic->getId()); $this->em->persist($address); $this->em->flush(); $this->em->persist($this->newWeeklySchedule( $doctor, $this->weekOfSessions($address->getId(), '16:00', '20:00'), $clinic )); } $this->em->flush(); return $doctor; } public function testQueryCountGrowsOnlyPerLocation(): void { // Keep one kernel so the shared query logger stays consistent. $this->client->disableReboot(); $few = $this->makeDoctorWithSchedules(1); $many = $this->makeDoctorWithSchedules(5); $qFew = $this->countQueries(fn () => $this->client->request( 'GET', '/api/v1/appointment-booking-locations/' . $few->getUuid() )); self::assertSame(200, $this->responseCode()); $qMany = $this->countQueries(fn () => $this->client->request( 'GET', '/api/v1/appointment-booking-locations/' . $many->getUuid() )); self::assertSame(200, $this->responseCode()); // 2 locations -> 6 locations. Each extra one costs a fixed handful of // queries (its schedule, holidays, overrides, blocking intervals, // address). The regression this guards against is a per-day or per-slot // query, which on a schedule active every day would add hundreds. $extraLocations = 4; $budgetEach = 8; self::assertLessThanOrEqual( $qFew + $extraLocations * $budgetEach, $qMany, "next_available_at scales badly: $qFew queries for 2 locations, $qMany for 6" ); } public function testNextAvailableIsReportedPerLocation(): void { $doctor = $this->makeDoctorWithSchedules(1); $body = $this->authJson('GET', '/api/v1/appointment-booking-locations/' . $doctor->getUuid(), $doctor->getUser()); self::assertSame(200, $this->responseCode()); $locations = $body['data']['booking_locations'] ?? []; self::assertCount(2, $locations); foreach ($locations as $location) { self::assertNotNull( $location['next_available_at'], 'a schedule active every day must expose a next free slot' ); self::assertGreaterThanOrEqual(time(), $location['next_available_at']); } // Sorted by earliest opening — the site relies on booking_locations[0]. $starts = array_column($locations, 'next_available_at'); $sorted = $starts; sort($sorted); self::assertSame($sorted, $starts); } }