tenantEntityType(), $address->tenantEntityId(), 'بخش ' . $name); $this->em->persist($section); $this->em->flush(); $item = new ServiceItem($section, $name, $price); $this->em->persist($item); $this->em->flush(); return $item; } private function resource(DoctorAddress $address, ResourceType $type, string $name): ClinicResource { $resource = new ClinicResource($address, $type, $name); $this->em->persist($resource); $this->em->flush(); return $resource; } // ── ✅ موفق ────────────────────────────────────────────────────────────── public function testAnOfferingCarriesTheDurationAndPriceOfThatResource(): void { [, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address); $resource = $this->resource($address, $type, 'لیزر دایود ۲'); $service = $this->service($address, 'لیزر پا', 8_000_000); $offering = new ResourceServiceOffering($resource, $service); $offering->setDurationMinutes(15)->setPriceRials(9_500_000); $this->em->persist($offering); $this->em->flush(); $this->em->clear(); $stored = $this->em->getRepository(ResourceServiceOffering::class)->find($offering->getId()); self::assertSame(15, $stored->getDurationMinutes()); self::assertSame(9_500_000, (int) $stored->getPriceRials()); self::assertTrue($stored->isActive()); // قیمت خودِ سرویس دست‌نخورده می‌ماند؛ این ردیف رویش می‌نشیند، جایش را نمی‌گیرد. self::assertSame(8_000_000, $stored->getServiceItem()->getPriceRials()); } public function testNullMeansInheritNotZero(): void { [, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address); $resource = $this->resource($address, $type, 'لیزر بدون تنظیم'); $service = $this->service($address, 'لیزر دست'); $offering = new ResourceServiceOffering($resource, $service); $this->em->persist($offering); $this->em->flush(); self::assertNull($offering->getDurationMinutes()); self::assertNull($offering->getPriceRials()); // صفر «مدتی که وجود ندارد» است، نه «حرفی برای گفتن ندارم» — پس رد می‌شود. $this->expectException(\InvalidArgumentException::class); $offering->setDurationMinutes(0); } // ── ❌ خطا ─────────────────────────────────────────────────────────────── public function testTheSamePairCannotBeRegisteredTwice(): void { [, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address); $resource = $this->resource($address, $type, 'لیزر تکراری'); $service = $this->service($address, 'لیزر صورت'); $this->em->persist(new ResourceServiceOffering($resource, $service)); $this->em->flush(); $this->em->persist(new ResourceServiceOffering($resource, $service)); $this->expectException(UniqueConstraintViolationException::class); $this->em->flush(); } public function testAResourceCannotOfferAServiceFromAnotherEnvironment(): void { [, , $address] = $this->clinicWithAddress(); [, , $otherAddress] = $this->clinicWithAddress('شعبهٔ کلینیک دیگر'); $resource = $this->resource($address, $this->resourceType($address), 'لیزر محیط الف'); $foreign = $this->service($otherAddress, 'سرویس محیط ب'); // `TenantFilter` این را نمی‌گیرد: هر دو uuid از بدنهٔ درخواست می‌آیند و فیلتر // روی فرزند aggregate اعمال نمی‌شود. پس گارد باید در خودِ سازنده باشد. $this->expectException(\InvalidArgumentException::class); new ResourceServiceOffering($resource, $foreign); } // ── ⚠️ مرزی ────────────────────────────────────────────────────────────── public function testAServiceCanBeOfferedByMoreThanOneResourceWithDifferentNumbers(): void { [, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address); $service = $this->service($address, 'لیزر پا'); $first = $this->resource($address, $type, 'دستگاه شمارهٔ ۱'); $second = $this->resource($address, $type, 'دستگاه شمارهٔ ۲'); $this->em->persist((new ResourceServiceOffering($first, $service))->setDurationMinutes(20)->setPriceRials(8_000_000)); $this->em->persist((new ResourceServiceOffering($second, $service))->setDurationMinutes(15)->setPriceRials(9_500_000)); $this->em->flush(); $repo = $this->em->getRepository(ResourceServiceOffering::class); self::assertTrue($repo->hasAnyFor($service)); self::assertCount(2, $repo->activeResourceIdsFor($service)); // همان مثال سند: یک سرویس، دو دستگاه، دو عدد متفاوت. self::assertSame(20, $repo->findOneFor($first, $service)?->getDurationMinutes()); self::assertSame(15, $repo->findOneFor($second, $service)?->getDurationMinutes()); } public function testDeactivatingKeepsTheRowAndItsNumbers(): void { [, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address); $resource = $this->resource($address, $type, 'دستگاه خاموش'); $service = $this->service($address, 'لیزر بیکینی'); $offering = new ResourceServiceOffering($resource, $service); $offering->setDurationMinutes(25)->setPriceRials(7_000_000)->setActive(false); $this->em->persist($offering); $this->em->flush(); $repo = $this->em->getRepository(ResourceServiceOffering::class); // رابطه هست ولی فعال نیست: تنظیمات با خاموش/روشن کردن از دست نمی‌رود. self::assertTrue($repo->hasAnyFor($service)); self::assertSame([], $repo->activeResourceIdsFor($service)); self::assertSame(25, $repo->findOneFor($resource, $service)?->getDurationMinutes()); } /** مودال رزرو منبع سرویس‌ها را زیر بخششان گروه می‌کند، پس فهرست باید بخش را بدهد. */ public function testOfferingListCarriesTheServiceSection(): void { [$user, , $address] = $this->clinicWithAddress(); $resource = $this->resource($address, $this->resourceType($address), 'لیزر الکساندرایت'); $service = $this->service($address, 'لیزر زیربغل'); $this->authJson('PUT', "/api/v1/resource/{$resource->getUuid()}/services", $user, [ 'services' => [['service_uuid' => $service->getUuid()]], ]); self::assertSame(200, $this->responseCode()); $body = $this->authJson('GET', "/api/v1/resource/{$resource->getUuid()}/services", $user); self::assertSame(200, $this->responseCode()); self::assertSame( $service->getSection()->getUuid(), $body['data'][0]['service_section']['uuid'], ); self::assertSame('بخش لیزر زیربغل', $body['data'][0]['service_section']['name']); } }