createUser(['ROLE_USER', 'ROLE_CLINIC']); $this->clinic = new Clinic($owner); $this->clinic->setName('کلینیک منبع‌محور'); $this->em->persist($this->clinic); $this->em->flush(); $this->doctor = new Doctor($this->createUser(['ROLE_USER', 'ROLE_DOCTOR']), 'مینا یوسفی'); $this->em->persist($this->doctor); $this->address = DoctorAddress::forClinic($this->clinic->getId()); $this->address->setName('شعبهٔ مرکزی'); $this->em->persist($this->address); $this->section = new ServiceSection('clinic', (int) $this->clinic->getId(), 'لیزر'); $this->em->persist($this->section); $this->em->flush(); } private function service(string $name, int $price, int $minutes): ServiceItem { $item = new ServiceItem($this->section, $name, $price); $item->setDurationMinutes($minutes); $this->em->persist($item); $this->em->flush(); return $item; } private function resource(string $name): ClinicResource { $type = new ResourceType('clinic', (int) $this->clinic->getId(), 'device_' . bin2hex(random_bytes(3)), 'دستگاه'); $this->em->persist($type); $this->em->flush(); $resource = new ClinicResource($this->address, $type, $name); $this->em->persist($resource); $this->em->flush(); return $resource; } private function resolver(): ResourceServiceResolver { return new ResourceServiceResolver( $this->em->getRepository(ResourceServiceOffering::class), $this->em->getRepository(ServiceBranchOverride::class), ); } private function snapshots(): PriceSnapshotService { return new PriceSnapshotService($this->em->getRepository(PriceSnapshot::class), $this->em); } // ── ✅ موفق ────────────────────────────────────────────────────────────── public function testAnAppointmentRemembersItsResourceAndOption(): void { $laser = $this->resource('دستگاه شمارهٔ ۲'); $parent = $this->service('لیزر', 10_000_000, 60); $option = $this->service('لیزر پا', 8_000_000, 30); $start = time() + 86_400; $appointment = $this->newAppointment($this->doctor, $this->createUser(['ROLE_USER']), $start, $start + 1_800); $appointment->setClinic($this->clinic); $appointment->setResource($laser); $appointment->setServiceOptionItem($option); $appointment->replaceServiceItems([$parent]); $this->em->persist($appointment); $this->em->flush(); $this->em->clear(); $stored = $this->em->getRepository(Appointment::class)->findOneBy(['uuid' => $appointment->getUuid()]); $row = $stored->toArray(); self::assertSame('دستگاه شمارهٔ ۲', $row['resource']['name']); self::assertSame('لیزر پا', $row['service_option']['name']); } public function testTheStoredMinutesComeFromTheResolverNotTheServiceItself(): void { $laser = $this->resource('دستگاه شمارهٔ ۲'); $option = $this->service('لیزر پا', 8_000_000, 30); // این دستگاه همان کار را در ۱۵ دقیقه انجام می‌دهد، نه ۳۰. $offering = new ResourceServiceOffering($laser, $option); $offering->setDurationMinutes(15)->setPriceRials(9_500_000); $this->em->persist($offering); $this->em->flush(); $spec = $this->resolver()->resolve($laser, $option, $this->address); $start = time() + 86_400; $appointment = $this->newAppointment($this->doctor, $this->createUser(['ROLE_USER']), $start, $start + $spec->durationMinutes * 60); $appointment->setClinic($this->clinic); $appointment->setResource($laser); $appointment->setServiceOptionItem($option); $appointment->setServiceDuration($spec->durationMinutes, 0); $this->em->persist($appointment); $this->em->flush(); self::assertSame(15, $appointment->getServiceTotalMinutes()); self::assertSame($start + 15 * 60, $appointment->getSlotEnd()); } // ── ⚠️ مرزی ────────────────────────────────────────────────────────────── public function testChangingTheServicePriceLaterLeavesTheSnapshotAlone(): void { $laser = $this->resource('لیزر CO2'); $option = $this->service('لیزر پا', 8_000_000, 30); $spec = $this->resolver()->resolve($laser, $option, $this->address); self::assertSame(8_000_000, $spec->priceRials); $start = time() + 86_400; $appointment = $this->newAppointment($this->doctor, $this->createUser(['ROLE_USER']), $start, $start + 1_800); $appointment->setClinic($this->clinic); $appointment->setResource($laser); $this->em->persist($appointment); $this->em->flush(); $this->snapshots()->record($appointment, new PriceQuote( baseRials: $spec->priceRials, itemsRials: $spec->priceRials, discountRials: 0, insuranceBaseRials: 0, insuranceSupplementaryRials: 0, taxRials: 0, finalRials: $spec->priceRials, depositRials: 0, )); // فردا تعرفه بالا می‌رود… $option->setPriceRials(12_000_000); $this->em->flush(); $this->em->clear(); $snapshot = $this->em->getRepository(PriceSnapshot::class) ->findOneBy(['appointment' => $appointment->getId()]); // …ولی صورتحساب این نوبت همان چیزی می‌ماند که بیمار پذیرفته بود. self::assertSame(8_000_000, $snapshot->getFinalRials()); } public function testAnAppointmentWithoutAResourceStillWorks(): void { $start = time() + 86_400; $appointment = $this->newAppointment($this->doctor, $this->createUser(['ROLE_USER']), $start, $start + 1_200); $appointment->setClinic($this->clinic); $this->em->persist($appointment); $this->em->flush(); $row = $appointment->toArray(); // ۷۲ نوبتِ موجود منبع ندارند؛ کلاینت باید با null کنار بیاید نه با خطا. self::assertNull($row['resource']); self::assertNull($row['service_option']); } }