get(ClinicResourceRepository::class); } private function skillIdOf(string $uuid): int { return (int) $this->em->getRepository(Skill::class)->findOneBy(['uuid' => $uuid])->getId(); } private function assign(\App\Auth\Entity\User $user, string $resourceUuid, array $skillUuids): void { $this->authJson('PUT', "/api/v1/resource/$resourceUuid/skills", $user, [ 'skills' => array_map(static fn (string $u): array => ['skill_uuid' => $u], $skillUuids), ]); } /** * نیازمندی «مهارت الف و ب» یعنی هر دو. با یک `IN` ساده، منبعی که فقط یکی را * دارد هم برمی‌گشت — به همین دلیل `HAVING COUNT(DISTINCT …)` هست. */ public function testAllSkillsAreRequiredNotAny(): void { [$user, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address, 'operator', 'اپراتور'); $laser = $this->createSkill($user, 'لیزر آلکساندرایت'); $botox = $this->createSkill($user, 'بوتاکس'); $both = $this->createResource($user, $address, $type, ['name' => 'اپراتور کامل']); $one = $this->createResource($user, $address, $type, ['name' => 'اپراتور نصفه']); $this->assign($user, $both['data']['uuid'], [$laser['data']['uuid'], $botox['data']['uuid']]); $this->assign($user, $one['data']['uuid'], [$laser['data']['uuid']]); $this->em->clear(); $eligible = $this->repo()->findEligible( $this->em->getRepository(\App\Doctor\Entity\DoctorAddress::class)->find($address->getId()), $this->em->getRepository(\App\Resource\Entity\ResourceType::class)->find($type->getId()), [$this->skillIdOf($laser['data']['uuid']), $this->skillIdOf($botox['data']['uuid'])], ); self::assertCount(1, $eligible); self::assertSame('اپراتور کامل', $eligible[0]->getName()); } public function testSingleSkillMatchesEveryResourceThatHasIt(): void { [$user, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address, 'operator', 'اپراتور'); $laser = $this->createSkill($user, 'لیزر'); foreach (['اپراتور ۱', 'اپراتور ۲'] as $name) { $r = $this->createResource($user, $address, $type, ['name' => $name]); $this->assign($user, $r['data']['uuid'], [$laser['data']['uuid']]); } $this->createResource($user, $address, $type, ['name' => 'اپراتور بی‌مهارت']); $this->em->clear(); $eligible = $this->repo()->findEligible( $this->em->getRepository(\App\Doctor\Entity\DoctorAddress::class)->find($address->getId()), $this->em->getRepository(\App\Resource\Entity\ResourceType::class)->find($type->getId()), [$this->skillIdOf($laser['data']['uuid'])], ); self::assertCount(2, $eligible); } public function testInactiveResourceIsNeverEligible(): void { [$user, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address, 'operator', 'اپراتور'); $laser = $this->createSkill($user, 'لیزر'); $resource = $this->createResource($user, $address, $type, ['name' => 'اپراتور خاموش']); $this->assign($user, $resource['data']['uuid'], [$laser['data']['uuid']]); $this->authJson('PATCH', "/api/v1/resource/{$resource['data']['uuid']}", $user, ['active' => false]); $this->em->clear(); $eligible = $this->repo()->findEligible( $this->em->getRepository(\App\Doctor\Entity\DoctorAddress::class)->find($address->getId()), $this->em->getRepository(\App\Resource\Entity\ResourceType::class)->find($type->getId()), [$this->skillIdOf($laser['data']['uuid'])], ); self::assertSame([], $eligible); } /** بدون شرط مهارت، همهٔ منابع فعالِ آن شعبه و نوع برمی‌گردند. */ public function testNoSkillFilterReturnsEveryActiveResourceOfThatType(): void { [$user, $clinic, $address] = $this->clinicWithAddress(); $second = $this->extraAddress($clinic); $type = $this->resourceType($address, 'operator', 'اپراتور'); $this->createResource($user, $address, $type, ['name' => 'اینجا ۱']); $this->createResource($user, $address, $type, ['name' => 'اینجا ۲']); $this->createResource($user, $second, $type, ['name' => 'شعبهٔ دیگر']); $this->em->clear(); $eligible = $this->repo()->findEligible( $this->em->getRepository(\App\Doctor\Entity\DoctorAddress::class)->find($address->getId()), $this->em->getRepository(\App\Resource\Entity\ResourceType::class)->find($type->getId()), ); self::assertCount(2, $eligible); } /** فیلتر مهارت روی endpoint فهرست هم همان معنا را می‌دهد. */ public function testListEndpointFiltersBySkill(): void { [$user, , $address] = $this->clinicWithAddress(); $type = $this->resourceType($address, 'operator', 'اپراتور'); $laser = $this->createSkill($user, 'لیزر'); $withSkill = $this->createResource($user, $address, $type, ['name' => 'با مهارت']); $this->assign($user, $withSkill['data']['uuid'], [$laser['data']['uuid']]); $this->createResource($user, $address, $type, ['name' => 'بدون مهارت']); $body = $this->authJson('GET', "/api/v1/resources?skill_uuid={$laser['data']['uuid']}", $user); self::assertSame(200, $this->responseCode()); self::assertCount(1, $body['data']); self::assertSame('با مهارت', $body['data'][0]['name']); } /** مهارت محیط دیگر نباید بی‌صدا «هیچ نتیجه» بدهد. */ public function testListEndpointRejectsAForeignSkillFilter(): void { [$user] = $this->clinicWithAddress(); [$otherUser] = $this->clinicWithAddress('شعبهٔ دیگر'); $foreign = $this->createSkill($otherUser, 'مهارت بیگانه'); $this->authJson('GET', "/api/v1/resources?skill_uuid={$foreign['data']['uuid']}", $user); self::assertSame(404, $this->responseCode()); } }