get(TreatmentSessionRepository::class); } /** * @return array{Clinic, ServiceItem, Doctor, TreatmentProtocol} */ private function clinicWithProtocol(bool $namesStaff, ?ClinicStaff $allowed = null): array { $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر صف'); $this->em->persist($doctor); $clinic = new Clinic($this->createUser(['ROLE_CLINIC'])); $clinic->setName('کلینیک صف ' . uniqid()); $clinic->getDoctors()->add($doctor); $this->em->persist($clinic); $this->em->flush(); $section = new ServiceSection('clinic', (int) $clinic->getId(), 'لیزر'); $this->em->persist($section); // بدون دستهٔ کاتالوگ، نواحیِ پرونده جایی برای snapshot ندارند و باز کردن // پرونده خطا می‌دهد — همان چیزی که TreatmentCaseOpener انتظار دارد. $category = new CatalogCategory('clinic', (int) $clinic->getId(), 'صورت'); $this->em->persist($category); $service = new ServiceItem($section, 'لیزر صورت', 3_000_000); $service->setCatalogCategory($category); $this->em->persist($service); $this->em->flush(); $protocol = new TreatmentProtocol($service); $this->em->persist($protocol); $protocol->replaceSteps([ new TreatmentProtocolStep($protocol, 1, 0), new TreatmentProtocolStep($protocol, 2, 15), ]); if ($namesStaff && $allowed !== null) { $protocol->replaceAllowedStaff([new TreatmentProtocolStaff($protocol, $allowed)]); } $this->em->flush(); return [$clinic, $service, $doctor, $protocol]; } private function staffIn(Clinic $clinic, string $name): ClinicStaff { $staff = new ClinicStaff('clinic', (int) $clinic->getId(), $name); $this->em->persist($staff); $this->em->flush(); return $staff; } /** نوبتِ امروز روی سرویسِ پروتکل‌دار، قطعی‌شده تا پرونده و جلسه ساخته شود. */ private function bookToday(Clinic $clinic, Doctor $doctor, ServiceItem $service, ?ClinicStaff $staff = null): Appointment { $start = strtotime('today midnight') + 10 * 3600; $appointment = $this->newAppointment( $doctor, $this->createUser(['ROLE_USER']), $start, $start + 1800, $clinic, ); $appointment->addServiceItem($service); if ($staff !== null) { $appointment->setStaff($staff); } $appointment->transitionTo(Appointment::STATUS_CONFIRMED); $this->em->persist($appointment); $this->em->flush(); static::getContainer()->get(AppointmentConfirmationService::class)->onConfirmed($appointment); return $appointment; } private function today(ClinicStaff $staff): array { return $this->repo()->findTodayForStaff( $staff, strtotime('today midnight'), strtotime('tomorrow midnight') - 1, ); } public function testAnAllowedOperatorSeesUnclaimedWork(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $operator = $this->staffIn($clinic, 'اپراتور مجاز'); $this->clinicWithProtocolNamesStaff($service, $operator); $appointment = $this->bookToday($clinic, $doctor, $service); $sessions = $this->today($operator); self::assertCount(1, $sessions); self::assertSame($appointment->getId(), $sessions[0]->getAppointment()?->getId()); } /** پرسنلی که پروتکل نامش را نبرده، کارِ بی‌صاحب را هم نباید ببیند. */ public function testAnOperatorOutsideTheProtocolSeesNothing(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $allowed = $this->staffIn($clinic, 'اپراتور مجاز'); $other = $this->staffIn($clinic, 'اپراتور دیگر'); $this->clinicWithProtocolNamesStaff($service, $allowed); $this->bookToday($clinic, $doctor, $service); self::assertCount(1, $this->today($allowed)); self::assertSame([], $this->today($other)); } /** پروتکلِ بدون فهرست پرسنل یعنی همه مجازند، نه هیچ‌کس. */ public function testAProtocolWithNoStaffListIsOpenToEveryone(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $anyone = $this->staffIn($clinic, 'هر اپراتوری'); $this->bookToday($clinic, $doctor, $service); self::assertCount(1, $this->today($anyone)); } /** اختصاص از قبل توسط منشی: جلسه فقط مالِ همان نفر است. */ public function testPreAssignedWorkIsHiddenFromOthers(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $mine = $this->staffIn($clinic, 'اپراتور من'); $theirs = $this->staffIn($clinic, 'اپراتور آن‌ها'); $this->bookToday($clinic, $doctor, $service, staff: $mine); self::assertCount(1, $this->today($mine)); self::assertSame([], $this->today($theirs)); } /** جلسه‌ای که یکی برداشته از صفِ بقیه بیرون می‌رود. */ public function testClaimedWorkLeavesTheSharedQueue(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $first = $this->staffIn($clinic, 'اپراتور اول'); $second = $this->staffIn($clinic, 'اپراتور دوم'); $this->bookToday($clinic, $doctor, $service); self::assertCount(1, $this->today($first)); self::assertCount(1, $this->today($second)); /** @var TreatmentSession $session */ $session = $this->today($first)[0]; $session->setPerformedBy($first); $this->em->flush(); self::assertCount(1, $this->today($first)); self::assertSame([], $this->today($second)); } /** پرسنلِ کلینیک دیگر هیچ‌وقت این صف را نمی‌بیند. */ public function testTheQueueIsScopedToTheTenant(): void { [$clinic, $service, $doctor] = $this->clinicWithProtocol(namesStaff: false); $this->bookToday($clinic, $doctor, $service); [$otherClinic] = $this->clinicWithProtocol(namesStaff: false); $outsider = $this->staffIn($otherClinic, 'پرسنل کلینیک دیگر'); self::assertSame([], $this->today($outsider)); } /** فهرست پرسنلِ پروتکل بعد از ساختِ سرویس ست می‌شود؛ همان مسیر تنظیمات. */ private function clinicWithProtocolNamesStaff(ServiceItem $service, ClinicStaff $staff): void { $protocol = $this->em->getRepository(TreatmentProtocol::class)->findOneBy(['serviceItem' => $service]); self::assertNotNull($protocol); $protocol->replaceAllowedStaff([new TreatmentProtocolStaff($protocol, $staff)]); $this->em->flush(); } }