- Added functionality to assign a single secretary to multiple doctors within a clinic, allowing for scoped access to appointments. - Introduced `SecretaryService` to handle the logic for assigning and syncing doctors for a secretary. - Updated `SecretaryController` to support multi-doctor assignment via new endpoints and modified existing ones. - Enhanced `DoctorSecretary` entity to include secretary UUID in its serialized output. - Implemented repository methods to facilitate the retrieval and management of doctor-secretary relationships. - Adjusted appointment filtering in `MyAppointmentsController` to ensure secretaries only see appointments for assigned doctors. - Created tests to validate the new multi-doctor assignment functionality and appointment access restrictions. - Updated frontend components to support multi-select for doctors in the secretary management UI.
123 lines
4.6 KiB
PHP
123 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Secretary;
|
|
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Auth\Entity\User;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* A clinic manager can assign one secretary to several of the clinic's doctors
|
|
* at once (many DoctorSecretary rows for one secretary User) and later re-sync
|
|
* that set. Doctors outside the clinic are rejected; foreign owners are 403.
|
|
*/
|
|
class ClinicSharedSecretaryTest extends ApiTestCase
|
|
{
|
|
/** @return array{0: User, 1: Clinic, 2: Doctor[]} */
|
|
private function makeClinicWithDoctors(int $count): array
|
|
{
|
|
$owner = $this->createUser(['ROLE_CLINIC']);
|
|
$clinic = new Clinic($owner);
|
|
$this->em->persist($clinic);
|
|
|
|
$doctors = [];
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), "دکتر $i");
|
|
$this->em->persist($doctor);
|
|
$clinic->getDoctors()->add($doctor);
|
|
$doctors[] = $doctor;
|
|
}
|
|
$this->em->flush();
|
|
|
|
return [$owner, $clinic, $doctors];
|
|
}
|
|
|
|
private function mobile(): string
|
|
{
|
|
return '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
public function testClinicOwnerAssignsSecretaryToMultipleDoctors(): void
|
|
{
|
|
[$owner, , $doctors] = $this->makeClinicWithDoctors(3);
|
|
|
|
$body = $this->authJson('POST', '/api/v1/secretary', $owner, [
|
|
'mobile_number' => $this->mobile(),
|
|
'name' => 'منشی مشترک',
|
|
'doctor_uuids' => [$doctors[0]->getUuid(), $doctors[1]->getUuid()],
|
|
]);
|
|
|
|
$this->assertSame(201, $this->responseCode());
|
|
$this->assertCount(2, $body['data']['created']);
|
|
$this->assertSame([], $body['data']['skipped_not_in_clinic']);
|
|
$this->assertNotEmpty($body['data']['secretary_uuid']);
|
|
}
|
|
|
|
public function testSkipsDuplicatesOnReassign(): void
|
|
{
|
|
[$owner, , $doctors] = $this->makeClinicWithDoctors(2);
|
|
$mobile = $this->mobile();
|
|
$uuids = [$doctors[0]->getUuid(), $doctors[1]->getUuid()];
|
|
|
|
$this->authJson('POST', '/api/v1/secretary', $owner, ['mobile_number' => $mobile, 'doctor_uuids' => $uuids]);
|
|
$body = $this->authJson('POST', '/api/v1/secretary', $owner, ['mobile_number' => $mobile, 'doctor_uuids' => $uuids]);
|
|
|
|
$this->assertSame(201, $this->responseCode());
|
|
$this->assertCount(0, $body['data']['created']);
|
|
$this->assertCount(2, $body['data']['skipped_duplicate']);
|
|
}
|
|
|
|
public function testRejectsDoctorOutsideClinic(): void
|
|
{
|
|
[$owner, , $doctors] = $this->makeClinicWithDoctors(1);
|
|
$foreignDoctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'خارج از کلینیک');
|
|
$this->em->persist($foreignDoctor);
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('POST', '/api/v1/secretary', $owner, [
|
|
'mobile_number' => $this->mobile(),
|
|
'doctor_uuids' => [$doctors[0]->getUuid(), $foreignDoctor->getUuid()],
|
|
]);
|
|
|
|
$this->assertSame(201, $this->responseCode());
|
|
$this->assertCount(1, $body['data']['created']);
|
|
$this->assertContains($foreignDoctor->getUuid(), $body['data']['skipped_not_in_clinic']);
|
|
}
|
|
|
|
public function testSyncAddsAndRemovesDoctors(): void
|
|
{
|
|
[$owner, $clinic, $doctors] = $this->makeClinicWithDoctors(3);
|
|
$mobile = $this->mobile();
|
|
|
|
$assigned = $this->authJson('POST', '/api/v1/secretary', $owner, [
|
|
'mobile_number' => $mobile,
|
|
'doctor_uuids' => [$doctors[0]->getUuid(), $doctors[1]->getUuid()],
|
|
]);
|
|
$secretaryUuid = $assigned['data']['secretary_uuid'];
|
|
|
|
// sync to {doctor1, doctor2} → drop doctor0, add doctor2
|
|
$body = $this->authJson('PUT', '/api/v1/secretaries/clinic/' . $clinic->getUuid() . '/doctors', $owner, [
|
|
'secretary_uuid' => $secretaryUuid,
|
|
'doctor_uuids' => [$doctors[1]->getUuid(), $doctors[2]->getUuid()],
|
|
]);
|
|
|
|
$this->assertSame(200, $this->responseCode());
|
|
$this->assertSame(1, $body['data']['added']); // doctor2
|
|
$this->assertSame(1, $body['data']['removed']); // doctor0
|
|
}
|
|
|
|
public function testForeignOwnerCannotSync(): void
|
|
{
|
|
[, $clinic, ] = $this->makeClinicWithDoctors(1);
|
|
$intruder = $this->createUser(['ROLE_CLINIC']);
|
|
|
|
$this->authJson('PUT', '/api/v1/secretaries/clinic/' . $clinic->getUuid() . '/doctors', $intruder, [
|
|
'secretary_uuid' => 'whatever',
|
|
'doctor_uuids' => [],
|
|
]);
|
|
|
|
$this->assertSame(403, $this->responseCode());
|
|
}
|
|
}
|