Supervision now lives on the resource itself instead of being asked for again at booking time, so one relation answers it everywhere. The column is deliberately separate from the existing doctor_id bridge. That bridge means "this resource IS this doctor" and isPerson() uses it to pin capacity at 1; a supervised three-seat device must not become a person resource. The FK is SET NULL rather than CASCADE because deleting a doctor should not take the clinic's laser with it. Required on create and non-clearable on update, enforced in the API where it can give a Persian message. Ownership is checked through Clinic::hasDoctor so a secretary cannot put their device under a doctor of another clinic; that returns 404, not 403, keeping foreign data invisible. The 13 existing resources are backfilled deterministically: a practice resource gets its own doctor, a clinic resource gets that clinic's first doctor. Both are editable from the resource form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
126 lines
4.5 KiB
PHP
126 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Resource;
|
|
|
|
use App\Auth\Entity\User;
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Doctor\Entity\DoctorAddress;
|
|
use App\Resource\Entity\ResourceType;
|
|
use App\Staff\Entity\ClinicStaff;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* فیکسچرهای مشترک دامنهٔ منبع. هر تست به یک محیط با آدرس و یک نوع منبع نیاز دارد،
|
|
* و برای سنجش مرز ۴۰۴ به یک محیط بیگانه.
|
|
*/
|
|
abstract class ResourceTestCase extends ApiTestCase
|
|
{
|
|
/** @return array{0: User, 1: Clinic, 2: DoctorAddress} */
|
|
protected function clinicWithAddress(string $addressName = 'شعبهٔ مرکزی'): array
|
|
{
|
|
$user = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
|
|
$clinic = new Clinic($user);
|
|
$clinic->setName('کلینیک منابع');
|
|
$this->em->persist($clinic);
|
|
$this->em->flush();
|
|
|
|
$address = DoctorAddress::forClinic($clinic->getId());
|
|
$address->setName($addressName);
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
return [$user, $clinic, $address];
|
|
}
|
|
|
|
/** آدرس دوم همان محیط — برای سنجش قاعدهٔ «همهٔ اعضای استخر در یک شعبه». */
|
|
protected function extraAddress(Clinic $clinic, string $name = 'شعبهٔ دوم'): DoctorAddress
|
|
{
|
|
$address = DoctorAddress::forClinic($clinic->getId());
|
|
$address->setName($name);
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
return $address;
|
|
}
|
|
|
|
/** @return array{0: User, 1: Doctor, 2: DoctorAddress} */
|
|
protected function doctorWithAddress(): array
|
|
{
|
|
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
|
$doctor = new Doctor($user, 'دکتر منبع');
|
|
$doctor->setMobileNumber($user->getMobileNumber());
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$address = DoctorAddress::forDoctor($doctor);
|
|
$address->setName('مطب شخصی');
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
return [$user, $doctor, $address];
|
|
}
|
|
|
|
/**
|
|
* پزشکِ ناظرِ محیطِ همین آدرس — از وقتی ناظر روی منبع الزامی شد، هر ساختِ منبع
|
|
* یکی لازم دارد. برای کلینیک یک پزشک عضو ساخته میشود و برای مطب، خودِ پزشک.
|
|
*/
|
|
protected function supervisorFor(DoctorAddress $address): Doctor
|
|
{
|
|
if ($address->tenantEntityType() === 'doctor') {
|
|
return $this->em->getRepository(Doctor::class)->find($address->tenantEntityId());
|
|
}
|
|
|
|
$clinic = $this->em->getRepository(Clinic::class)->find($address->tenantEntityId());
|
|
|
|
foreach ($clinic->getDoctors() as $existing) {
|
|
return $existing;
|
|
}
|
|
|
|
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
|
$doctor = new Doctor($user, 'پزشک ناظر');
|
|
$doctor->setMobileNumber($user->getMobileNumber());
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$clinic->getDoctors()->add($doctor);
|
|
$this->em->flush();
|
|
|
|
return $doctor;
|
|
}
|
|
|
|
protected function resourceType(DoctorAddress $address, string $code = 'device', string $name = 'دستگاه'): ResourceType
|
|
{
|
|
$type = new ResourceType($address->tenantEntityType(), $address->tenantEntityId(), $code, $name);
|
|
$this->em->persist($type);
|
|
$this->em->flush();
|
|
|
|
return $type;
|
|
}
|
|
|
|
protected function staff(DoctorAddress $address, string $name = 'اپراتور یک'): ClinicStaff
|
|
{
|
|
$staff = new ClinicStaff($address->tenantEntityType(), $address->tenantEntityId(), $name);
|
|
$this->em->persist($staff);
|
|
$this->em->flush();
|
|
|
|
return $staff;
|
|
}
|
|
|
|
/** @param array<string, mixed> $body */
|
|
protected function createResource(User $user, DoctorAddress $address, ResourceType $type, array $body = []): array
|
|
{
|
|
return $this->authJson('POST', '/api/v1/resource', $user, $body + [
|
|
'address_uuid' => $address->getUuid(),
|
|
'type_uuid' => $type->getUuid(),
|
|
'name' => 'لیزر آلکساندرایت ۱',
|
|
'supervisor_doctor_uuid' => $this->supervisorFor($address)->getUuid(),
|
|
]);
|
|
}
|
|
|
|
protected function createSkill(User $user, string $name = 'لیزر آلکساندرایت'): array
|
|
{
|
|
return $this->authJson('POST', '/api/v1/skills', $user, ['name' => $name]);
|
|
}
|
|
}
|