get(AppointmentConfirmationService::class); } private function scenario(bool $withProtocol, ?string $domainCode = 'beauty', bool $confirm = true): array { $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر آزمون'); $this->em->persist($doctor); $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک دورهٔ درمان'); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $this->em->flush(); if ($domainCode !== null) { $domain = $this->em->getRepository(PracticeDomain::class)->findOneBy(['code' => $domainCode]) ?? new PracticeDomain($domainCode, 'حوزهٔ آزمون'); $this->em->persist($domain); $this->em->flush(); $clinic->setPracticeDomain($domain); } $section = new ServiceSection('clinic', (int) $clinic->getId(), 'لیزر'); $this->em->persist($section); $category = new CatalogCategory('clinic', (int) $clinic->getId(), 'دست'); $this->em->persist($category); $service = new ServiceItem($section, 'لیزر دست', 5_000_000); $service->setCatalogCategory($category); $this->em->persist($service); $this->em->flush(); if ($withProtocol) { $staff = new ClinicStaff('clinic', (int) $clinic->getId(), 'اپراتور'); $this->em->persist($staff); $protocol = new TreatmentProtocol($service); $this->em->persist($protocol); $protocol->replaceSteps([ new TreatmentProtocolStep($protocol, 1, 0), new TreatmentProtocolStep($protocol, 2, 15), new TreatmentProtocolStep($protocol, 3, 30), ]); $protocol->replaceAllowedStaff([new TreatmentProtocolStaff($protocol, $staff)]); $this->em->flush(); } $appointment = $this->newAppointment( $doctor, $this->createUser(['ROLE_USER']), 1_790_000_000, 1_790_001_800, $clinic, ); $appointment->addServiceItem($service); if ($confirm) { $appointment->transitionTo(Appointment::STATUS_CONFIRMED); } $this->em->persist($appointment); $this->em->flush(); return [$appointment, $service, $clinic]; } /** @return TreatmentCase[] */ private function casesFor(ServiceItem $service): array { return $this->em->getRepository(TreatmentCase::class)->findBy(['serviceItem' => $service]); } public function testConfirmingAProtocolServiceOpensACase(): void { [$appointment, $service] = $this->scenario(withProtocol: true); $this->confirmation()->onConfirmed($appointment); $cases = $this->casesFor($service); self::assertCount(1, $cases); self::assertSame(3, $cases[0]->getTotalSessions()); self::assertCount(3, $cases[0]->getSessions()); self::assertSame(['دست'], array_map( static fn ($a) => $a->getName(), $cases[0]->getAreas()->toArray(), )); } /** * مسیر واقعیِ منشی: قطعی‌کردن از پنل با مودال پرداخت. * * این مسیر جدا از onConfirmed است و مدتی پروندهٔ درمان را نمی‌ساخت — نوبت قطعی * می‌شد، پول ثبت می‌شد، و پنل پرسنل خالی می‌ماند چون هیچ جلسه‌ای وجود نداشت. */ public function testConfirmingFromThePanelWithPaymentsOpensACase(): void { [$appointment, $service] = $this->scenario(withProtocol: true, confirm: false); $this->confirmation()->confirmWithPayments( $appointment, $appointment->getVersion(), [['method' => 'cash', 'amount_rials' => 5_000_000]], $this->createUser(['ROLE_CLINIC']), ); $cases = $this->casesFor($service); self::assertCount(1, $cases); self::assertCount(3, $cases[0]->getSessions()); $sessions = $cases[0]->getSessions()->toArray(); usort($sessions, static fn (TreatmentSession $a, TreatmentSession $b): int => $a->getSessionNumber() <=> $b->getSessionNumber()); self::assertSame($appointment->getId(), $sessions[0]->getAppointment()?->getId()); } /** سرویس بدون پروتکل همان نوبت تک‌جلسه‌ای است — نباید پرونده‌ای ساخته شود. */ public function testConfirmingAPlainServiceOpensNoCase(): void { [$appointment, $service] = $this->scenario(withProtocol: false); $this->confirmation()->onConfirmed($appointment); self::assertSame([], $this->casesFor($service)); } /** کلینیکِ بدون حوزهٔ فعالیت هم باید دوره‌اش کار کند — نال یعنی رفتار پیش‌فرض. */ public function testCaseOpensEvenWithoutAPracticeDomain(): void { [$appointment, $service] = $this->scenario(withProtocol: true, domainCode: null); $this->confirmation()->onConfirmed($appointment); self::assertCount(1, $this->casesFor($service)); } public function testFirstSessionIsAttachedToTheAppointment(): void { [$appointment, $service] = $this->scenario(withProtocol: true); $this->confirmation()->onConfirmed($appointment); $sessions = $this->casesFor($service)[0]->getSessions()->toArray(); usort($sessions, static fn (TreatmentSession $a, TreatmentSession $b): int => $a->getSessionNumber() <=> $b->getSessionNumber()); self::assertSame($appointment->getId(), $sessions[0]->getAppointment()?->getId()); self::assertSame(TreatmentSession::STATUS_BOOKED, $sessions[0]->getStatus()); self::assertSame($appointment->getSlotStart(), $sessions[0]->getDueAt()); // جلسات بعدی نه نوبت دارند نه سررسید قطعی — سررسیدشان از جلسهٔ قبل می‌آید. self::assertNull($sessions[1]->getAppointment()); self::assertNull($sessions[1]->getDueAt()); } /** بیمار وسط دوره نوبت دیگری می‌گیرد — باید جلسهٔ همان دوره باشد نه دورهٔ موازی. */ public function testASecondAppointmentDoesNotOpenASecondCase(): void { [$appointment, $service, $clinic] = $this->scenario(withProtocol: true); $this->confirmation()->onConfirmed($appointment); $second = $this->newAppointment( $appointment->getDoctor(), $appointment->getUser(), 1_791_000_000, 1_791_001_800, $clinic, ); $second->addServiceItem($service); $second->transitionTo(Appointment::STATUS_CONFIRMED); $this->em->persist($second); $this->em->flush(); $this->confirmation()->onConfirmed($second); $cases = $this->casesFor($service); self::assertCount(1, $cases); $sessions = $cases[0]->getSessions()->toArray(); usort($sessions, static fn (TreatmentSession $a, TreatmentSession $b): int => $a->getSessionNumber() <=> $b->getSessionNumber()); self::assertSame($second->getId(), $sessions[1]->getAppointment()?->getId()); } }