createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر فیلدهای سرویسی'); $this->em->persist($doctor); // بخش به شناسهٔ پزشک نیاز دارد، پس پزشک باید پیش از ساختش flush شود. $this->em->flush(); $section = new ServiceSection('doctor', $doctor->getId(), 'بخش تست'); $this->em->persist($section); $items = []; for ($i = 1; $i <= $count; $i++) { $item = new ServiceItem($section, 'سرویس ' . $i, 0); $item->setDurationMinutes(10 * $i)->setBookable(true); $this->em->persist($item); $items[] = $item; } $this->em->flush(); return [$doctor, $items]; } private function appointmentFor(Doctor $doctor): \App\Appointment\Entity\Appointment { $patient = $this->createUser(['ROLE_USER']); $start = strtotime('+5 days 10:00'); return $this->newAppointment($doctor, $patient, $start, $start + 1800); } // ── ✅ موفق ────────────────────────────────────────────────────────────── public function testReplaceServiceItemsSyncsTheLegacySingleColumn(): void { [$doctor, $items] = $this->doctorWithServices(3); $appt = $this->appointmentFor($doctor); $appt->replaceServiceItems([$items[0], $items[1]]); self::assertCount(2, $appt->getServiceItems()); self::assertSame($items[0], $appt->getServiceItem(), 'سرویس تکی = اولین عضو'); // جایگزینی کامل: اعضای قبلی می‌روند و تکی هم به‌روز می‌شود. $appt->replaceServiceItems([$items[2]]); self::assertCount(1, $appt->getServiceItems()); self::assertSame($items[2], $appt->getServiceItem(), 'تکی باید با عضو جدید هم‌گام شود'); } public function testCurrentServiceUuidsKeepsInputOrder(): void { [$doctor, $items] = $this->doctorWithServices(3); $appt = $this->appointmentFor($doctor); $appt->replaceServiceItems([$items[2], $items[0], $items[1]]); self::assertSame( [$items[2]->getUuid(), $items[0]->getUuid(), $items[1]->getUuid()], $appt->currentServiceUuids(), ); } public function testSetServiceDurationIsExposedInToArray(): void { [$doctor] = $this->doctorWithServices(1); $appt = $this->appointmentFor($doctor); $appt->setServiceDuration(45, 10); self::assertSame(45, $appt->getServiceTotalMinutes()); self::assertSame(10, $appt->getServiceBufferMinutes()); $array = $appt->toArray(); self::assertSame(45, $array['service_total_minutes']); self::assertSame(10, $array['service_buffer_minutes']); } // ── ❌ خطا / مسیر نادرست ───────────────────────────────────────────────── public function testReplaceWithEmptyListClearsTheLegacySingleColumn(): void { [$doctor, $items] = $this->doctorWithServices(2); $appt = $this->appointmentFor($doctor); $appt->replaceServiceItems([$items[0]]); $appt->replaceServiceItems([]); self::assertCount(0, $appt->getServiceItems()); self::assertNull($appt->getServiceItem(), 'سرویس تکی نباید یادگارِ انتخاب قبلی بماند'); self::assertSame([], $appt->currentServiceUuids()); } // ── ⚠️ مرزی ────────────────────────────────────────────────────────────── public function testSlotModeAppointmentKeepsBothColumnsNull(): void { [$doctor] = $this->doctorWithServices(1); $appt = $this->appointmentFor($doctor); self::assertNull($appt->getServiceTotalMinutes()); self::assertNull($appt->getServiceBufferMinutes()); self::assertNull($appt->toArray()['service_total_minutes']); } public function testClearingDurationAlsoClearsBuffer(): void { [$doctor] = $this->doctorWithServices(1); $appt = $this->appointmentFor($doctor); $appt->setServiceDuration(45, 10); // بافرِ بی‌مدت معنا ندارد؛ حتی اگر مقدار بافر پاس داده شود باید NULL شود. $appt->setServiceDuration(null, 10); self::assertNull($appt->getServiceTotalMinutes()); self::assertNull($appt->getServiceBufferMinutes()); } public function testDuplicateItemsInReplaceAreDeduplicated(): void { [$doctor, $items] = $this->doctorWithServices(1); $appt = $this->appointmentFor($doctor); $appt->replaceServiceItems([$items[0], $items[0]]); self::assertCount(1, $appt->getServiceItems()); } public function testLegacyAppointmentWithOnlySingleItemStillReportsItsUuid(): void { [$doctor, $items] = $this->doctorWithServices(1); $appt = $this->appointmentFor($doctor); // نوبت پیش از چند-سرویسی‌شدن: فقط ستون تکی پر است. $appt->setServiceItem($items[0]); self::assertCount(0, $appt->getServiceItems()); self::assertSame([$items[0]->getUuid()], $appt->currentServiceUuids()); } public function testReplacePersistsThroughFlush(): void { [$doctor, $items] = $this->doctorWithServices(2); $appt = $this->appointmentFor($doctor); $appt->replaceServiceItems([$items[0], $items[1]])->setServiceDuration(30, 5); $this->em->persist($appt); $this->em->flush(); $this->em->clear(); $reloaded = static::getContainer() ->get(\App\Appointment\Repository\AppointmentRepository::class) ->findByUuid($appt->getUuid()); self::assertNotNull($reloaded); self::assertCount(2, $reloaded->getServiceItems()); self::assertSame(30, $reloaded->getServiceTotalMinutes()); self::assertSame(5, $reloaded->getServiceBufferMinutes()); } }