diff --git a/docs/api/appointment.md b/docs/api/appointment.md index 40e08616..f33bd01f 100644 --- a/docs/api/appointment.md +++ b/docs/api/appointment.md @@ -459,7 +459,13 @@ Get appointment detail. } } ``` -> `doctor.specialties` آرایه (ممکن است خالی)؛ `address` اولین آدرس پزشک است (ممکن است `null` اگر پزشک آدرسی ندارد). `address.map.latitude/longitude` رشته یا `null`. تاریخ‌ها Unix. + +> ℹ️ `address` is the venue recorded on this appointment (`address_id`) — the doctor's own +> office or the clinic branch, whichever the booking was made at — and it is the only place +> `telephone` is returned. When that record carries no number, the clinic's own number takes +> its place. Public doctor and clinic responses never carry a phone number. + +> `doctor.specialties` آرایه (ممکن است خالی)؛ `address` آدرسِ ثبت‌شدهٔ همین نوبت است و برای نوبت‌های قدیمیِ بدون `address_id` ممکن است `null` باشد. `address.map.latitude/longitude` رشته یا `null`. تاریخ‌ها Unix. ### انتخاب بیمهٔ نوبت diff --git a/docs/api/clinic.md b/docs/api/clinic.md index aec2239b..2a355ed6 100644 --- a/docs/api/clinic.md +++ b/docs/api/clinic.md @@ -167,6 +167,8 @@ limited to the whitelist under `PATCH /api/v1/clinic/{uuid}`. } ``` +> 🔒 `phone`/`phone_number` are `null` unless the caller may edit the clinic (`can_edit: true`). The number is not public data; the patient sees the venue phone on their own appointment instead. +> > `city`/`state`/`map`/`location`/`phone`/`phone_number` are all resolved from the clinic's **address** (`DoctorAddress` linked by `clinic_id`), not from columns on the clinic. `location` and `phone`/`phone_number` fall back to the deprecated `clinics.address` / `clinics.telephone` columns only when the address record has no value — reading them from different rows made one response describe two different places. Each is an array with a single object (or empty `[]` if the clinic has no address). `doctors` is a **count**; the actual doctor list comes from `GET /api/v1/clinic/doctor-list/{clinicUuid}` (`doctor_list` here is always `null`). ### معنای `is_active` @@ -330,7 +332,7 @@ List clinics with pagination. | `doctors_count` | integer | Number of doctors linked to the clinic | | `city` | string\|null | City name, resolved from the clinic's address (`DoctorAddress`) | | `state` | string\|null | Province name, resolved from the clinic's address (`DoctorAddress`) | -| `phone` / `phone_number` | string\|null | Contact number from the clinic's address (`DoctorAddress`), falling back to the deprecated `clinics.telephone` column | +| `phone` / `phone_number` | string\|null | Contact number from the clinic's address (`DoctorAddress`), falling back to the deprecated `clinics.telephone` column. `null` for anyone who cannot edit the clinic, and always `null` in the public list. | | `24_7` | boolean | Open 24/7 flag | | `field_working_days` | string\|null | Working days/hours description | diff --git a/docs/api/doctor.md b/docs/api/doctor.md index 6e62a816..e9dc456e 100644 --- a/docs/api/doctor.md +++ b/docs/api/doctor.md @@ -133,7 +133,7 @@ limited to the whitelist under `PATCH /api/v1/doctor/{uuid}`. "address": [], "state": [], "city": [], - "clinics": [{ "uuid": "...", "name": "کلینیک الوند", "address": "...", "telephone": "..." }], + "clinics": [{ "uuid": "...", "name": "کلینیک الوند", "address": "...", "telephone": null }], "representation": { "id": 12, "uuid": "9c1...", "full_name": "علی محمدی" } } } @@ -143,6 +143,13 @@ limited to the whitelist under `PATCH /api/v1/doctor/{uuid}`. > ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data` > > ℹ️ `representation` نماینده‌ی مالکِ پزشک است؛ برای پزشکِ بدون نماینده `null`. +> +> 🔒 **Phone numbers are not public.** `address[].telephone` and `clinics[].telephone` +> are `null` for anonymous callers and only carry a value when `can_edit` is `true` +> (the profile owner, its representative, or an admin). Street address and map +> coordinates stay public — a patient needs them to find the place. The venue phone +> reaches the patient through their own appointment (`GET /api/v1/appointments/user`), +> not through the public profile. ### Errors | Code | HTTP | Description | @@ -171,7 +178,7 @@ Get doctor detail for clinic owner — only doctors who are members of the authe "uuid": "...", "title": "علی احمدی", "specialties": [...], - "clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "..." }] + "clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "021..." }] } } } diff --git a/src/Appointment/Controller/AppointmentController.php b/src/Appointment/Controller/AppointmentController.php index 6e436162..d26aa68a 100644 --- a/src/Appointment/Controller/AppointmentController.php +++ b/src/Appointment/Controller/AppointmentController.php @@ -725,7 +725,7 @@ class AppointmentController extends BaseController return $this->error(ErrorCodes::ERR_ACCESS_DENIED, 'دسترسی ممنوع', 403); } - $payload = $appointment->toArray(); + $payload = $this->appointmentWithVenue($appointment); /** * نوبتِ سرویسیِ کلینیک `service_section_id` را روی خودِ نوبت نمی‌نویسد؛ فقط @@ -893,13 +893,49 @@ class AppointmentController extends BaseController // `toArray()` هم داخل محدوده است: proxyهای پزشک و کلینیک آنجا باز می‌شوند و // با فیلترِ برگشته، Doctrine `EntityNotFoundException` می‌دهد. $rows = $this->tenantScope->withoutFilter(fn () => array_map( - fn(Appointment $a) => $a->toArray(), + fn(Appointment $a) => $this->appointmentWithVenue($a), $this->appointmentRepo->findByUser($user, $status), )); return $this->success(['data' => $rows]); } + /** + * نوبت به‌همراه نشانیِ واقعیِ همان نوبت — با شمارهٔ تماس. + * + * `Appointment::toArray()` نشانی را از «اولین آدرسِ پزشک» برمی‌داشت، پس بیماری که + * نوبتش در کلینیک یا در شعبهٔ دوم بود، نشانیِ مطبِ شخصیِ پزشک را می‌دید. آدرسِ درست + * روی خودِ نوبت ثبت شده (`address_id`) و همان‌جا برای مطب و کلینیک یکسان کار می‌کند. + * + * شماره هم اینجا برمی‌گردد و نه در پاسخ‌های عمومی: مخاطبِ این متد یا صاحب نوبت است + * یا کسی که `canView()` تأییدش کرده. اگر خودِ آدرس شماره نداشته باشد، شمارهٔ کلینیکِ + * همان نوبت جایش می‌نشیند تا بیمار بی‌راه‌ارتباطی نماند. + */ + private function appointmentWithVenue(Appointment $appointment): array + { + $payload = $appointment->toArray(); + + $addressId = $appointment->getAddressId(); + $address = $addressId !== null ? $this->addressRepo->find($addressId) : null; + + if ($address !== null) { + $payload['address'] = $address->toArray(null, true); + } elseif ($payload['address'] !== null) { + // نوبت‌های قدیمی `address_id` ندارند؛ همان آدرسِ حدسیِ toArray() می‌ماند، + // ولی دست‌کم بدون شمارهٔ اشتباه نمایش داده نمی‌شود. + $payload['address']['telephone'] = null; + } + + if (($payload['address']['telephone'] ?? null) === null && $payload['address'] !== null) { + $clinic = $appointment->getClinic(); + if ($clinic !== null) { + $payload['address']['telephone'] = $clinic->getTelephone(); + } + } + + return $payload; + } + private function canView(Appointment $a, User $user): bool { return $this->accessChecker->canView($a, $user); diff --git a/src/Clinic/Controller/ClinicController.php b/src/Clinic/Controller/ClinicController.php index 8adac218..be9b0569 100644 --- a/src/Clinic/Controller/ClinicController.php +++ b/src/Clinic/Controller/ClinicController.php @@ -131,7 +131,7 @@ class ClinicController extends BaseController $this->userRepo->save($user); } - return $this->success(['data' => $clinic->toDetailArray()], 201); + return $this->success(['data' => $clinic->toDetailArray(withContact: true)], 201); } #[OA\Get( @@ -177,7 +177,7 @@ class ClinicController extends BaseController ); return $this->success(['data' => array_merge( - $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone), + $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone, $canEdit), ['can_edit' => $canEdit], )]); } @@ -277,7 +277,7 @@ class ClinicController extends BaseController [$stateData, $cityData, $map, $street, $telephone] = $this->loadLocationData($clinic); - return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone)]); + return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone, true)]); } #[OA\Get( @@ -795,7 +795,7 @@ class ClinicController extends BaseController $this->editLogger->logEdit($user, 'clinic', $clinic->getUuid(), $data); } - return $this->success(['data' => $address->toArray()], 201); + return $this->success(['data' => $address->toArray(null, true)], 201); } #[Route('/api/v1/clinic/{clinicUuid}/address/{addressUuid}', methods: ['PATCH'])] @@ -824,7 +824,7 @@ class ClinicController extends BaseController $this->editLogger->logEdit($user, 'clinic', $clinic->getUuid(), $data); } - return $this->success(['data' => $address->toArray()]); + return $this->success(['data' => $address->toArray(null, true)]); } #[Route('/api/v1/clinic/{clinicUuid}/address/{addressUuid}', methods: ['DELETE'])] diff --git a/src/Clinic/Entity/Clinic.php b/src/Clinic/Entity/Clinic.php index 1c6dd536..dbb0f693 100644 --- a/src/Clinic/Entity/Clinic.php +++ b/src/Clinic/Entity/Clinic.php @@ -209,9 +209,11 @@ class Clinic array $cityData = [], ?array $map = null, ?string $street = null, - ?string $telephone = null + ?string $telephone = null, + bool $withContact = false ): array { - $phone = $this->firstFilled($telephone, $this->telephone); + // شمارهٔ کلینیک هم مثل شمارهٔ مطب عمومی نیست. {@see DoctorAddress::toArray()} + $phone = $withContact ? $this->firstFilled($telephone, $this->telephone) : null; $address = $this->firstFilled($street, $this->address); return [ @@ -267,9 +269,9 @@ class Clinic return null; } - public function toListArray(?string $city = null, ?string $state = null, ?string $telephone = null): array + public function toListArray(?string $city = null, ?string $state = null, ?string $telephone = null, bool $withContact = false): array { - $phone = $this->firstFilled($telephone, $this->telephone); + $phone = $withContact ? $this->firstFilled($telephone, $this->telephone) : null; return [ 'id' => (string) $this->id, diff --git a/src/Doctor/Controller/AddressController.php b/src/Doctor/Controller/AddressController.php index f19700ea..4ccf0c71 100644 --- a/src/Doctor/Controller/AddressController.php +++ b/src/Doctor/Controller/AddressController.php @@ -41,7 +41,8 @@ class AddressController extends BaseController $this->clinicDoctorAccess->denyUnlessGranted($user, 'addresses', 'view'); return $this->success(array_map( - static fn (DoctorAddress $a): array => $a->toArray(), + // فهرست محیطِ خودِ کاربر است، پس شمارهٔ تماس هم برمی‌گردد. + static fn (DoctorAddress $a): array => $a->toArray(null, true), $this->addresses->listForContext($user), )); } diff --git a/src/Doctor/Controller/DoctorController.php b/src/Doctor/Controller/DoctorController.php index 904acf5a..87b54820 100644 --- a/src/Doctor/Controller/DoctorController.php +++ b/src/Doctor/Controller/DoctorController.php @@ -127,7 +127,7 @@ class DoctorController extends BaseController $this->userRepo->save($user); } - return $this->success(['data' => $doctor->toDetailArray($this->scheduleRepo->findAllByDoctor($doctor))], 201); + return $this->success(['data' => $doctor->toDetailArray($this->scheduleRepo->findAllByDoctor($doctor), true)], 201); } #[OA\Get( @@ -159,12 +159,14 @@ class DoctorController extends BaseController } $clinics = $this->clinicRepo->findByDoctor($doctor); + // شمارهٔ کلینیک بعد از محاسبهٔ can_edit اضافه می‌شود؛ اینجا هنوز معلوم نیست + // درخواست‌دهنده صاحب پروفایل است یا یک بازدیدکنندهٔ عمومی. $clinicData = array_map(fn(Clinic $c) => [ 'id' => (string) $c->getId(), 'uuid' => $c->getUuid(), 'name' => $c->getName(), 'address' => $c->getAddress(), - 'telephone' => $c->getTelephone(), + 'telephone' => null, 'city_id' => $c->getCityId(), 'province_id' => $c->getProvinceId(), 'map' => [ @@ -187,8 +189,14 @@ class DoctorController extends BaseController || $this->editPolicy->ownsDoctor($user, $doctor) ); + if ($canEdit) { + foreach ($clinics as $index => $clinic) { + $clinicData[$index]['telephone'] = $clinic->getTelephone(); + } + } + $schedules = $this->scheduleRepo->findAllByDoctor($doctor); - return $this->success(['data' => array_merge($doctor->toDetailArray($schedules), [ + return $this->success(['data' => array_merge($doctor->toDetailArray($schedules, $canEdit), [ 'clinics' => $clinicData, 'representation' => $representation, 'can_edit' => $canEdit, @@ -214,7 +222,7 @@ class DoctorController extends BaseController } $schedules = $this->scheduleRepo->findAllByDoctor($doctor); - return $this->success(['data' => array_merge($doctor->toDetailArray($schedules), ['clinics' => [[ + return $this->success(['data' => array_merge($doctor->toDetailArray($schedules, true), ['clinics' => [[ 'id' => (string) $clinic->getId(), 'uuid' => $clinic->getUuid(), 'name' => $clinic->getName(), @@ -393,7 +401,7 @@ class DoctorController extends BaseController $this->editLogger->logEdit($user, 'doctor', $doctor->getUuid(), $data); } - return $this->success(['data' => $doctor->toDetailArray($this->scheduleRepo->findAllByDoctor($doctor))]); + return $this->success(['data' => $doctor->toDetailArray($this->scheduleRepo->findAllByDoctor($doctor), true)]); } #[OA\Delete( @@ -613,7 +621,7 @@ class DoctorController extends BaseController $this->editLogger->logEdit($user, 'doctor', $doctor->getUuid(), $data); } - return $this->success(['data' => $address->toArray()], 201); + return $this->success(['data' => $address->toArray(null, true)], 201); } #[OA\Get( @@ -651,7 +659,7 @@ class DoctorController extends BaseController return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403); } - return $this->success(['data' => $address->toArray()]); + return $this->success(['data' => $address->toArray(null, true)]); } #[OA\Patch( @@ -715,7 +723,7 @@ class DoctorController extends BaseController $this->editLogger->logEdit($user, 'doctor', $doctor->getUuid(), $data); } - return $this->success(['data' => $address->toArray()]); + return $this->success(['data' => $address->toArray(null, true)]); } #[OA\Delete( diff --git a/src/Doctor/Entity/Doctor.php b/src/Doctor/Entity/Doctor.php index 814151b6..f2b31398 100644 --- a/src/Doctor/Entity/Doctor.php +++ b/src/Doctor/Entity/Doctor.php @@ -601,7 +601,11 @@ class Doctor } /** @param WeeklySchedule[] $schedules همهٔ برنامه‌های پزشک (شخصی + کلینیک‌ها) */ - public function toDetailArray(array $schedules = []): array + /** + * `$withContact` فقط برای مخاطبی روشن می‌شود که حق دیدن شمارهٔ مطب را دارد — + * صاحب پروفایل یا ادمین. پاسخ عمومی بدون شماره است. {@see DoctorAddress::toArray()} + */ + public function toDetailArray(array $schedules = [], bool $withContact = false): array { $sf = $this->computeScheduleFields($schedules); return [ @@ -638,7 +642,7 @@ class Doctor 'owner_status' => $this->ownerStatus, 'free_turn' => $sf['free_turn'], 'hours_of_work' => $sf['hours_of_work'], - 'address' => array_map(fn(DoctorAddress $a) => $a->toArray(), $this->addresses->toArray()), + 'address' => array_map(fn(DoctorAddress $a) => $a->toArray(null, $withContact), $this->addresses->toArray()), 'average_rate' => ['total_rates' => null], 'state' => array_map(fn(Province $p) => [ 'uuid' => $p->getUuid(), diff --git a/src/Doctor/Entity/DoctorAddress.php b/src/Doctor/Entity/DoctorAddress.php index 59c5ec8c..14560cea 100644 --- a/src/Doctor/Entity/DoctorAddress.php +++ b/src/Doctor/Entity/DoctorAddress.php @@ -151,7 +151,16 @@ class DoctorAddress private function touch(): void { $this->updatedAt = time(); } - public function toArray(?string $clinicName = null): array + /** + * شمارهٔ تماسِ محل، دادهٔ عمومی نیست. + * + * صفحهٔ پزشک شمارهٔ مطب را کنار آدرس نشان می‌داد و همان شماره در پاسخ عمومیِ API + * هم می‌آمد — یعنی برداشت انبوهِ شمارهٔ همهٔ مطب‌ها فقط یک درخواست فاصله داشت. + * حالا پیش‌فرض حذف است و فقط جایی که مخاطب حق دیدنش را دارد — صاحب همان محل، یا + * بیماری که نوبتش آنجاست — با `$withContact` برمی‌گردد. موقعیت مکانی و آدرس + * عمومی می‌مانند؛ آن‌ها همان چیزی‌اند که بیمار برای پیدا کردن مطب لازم دارد. + */ + public function toArray(?string $clinicName = null, bool $withContact = false): array { return [ 'id' => (string) $this->id, @@ -165,7 +174,7 @@ class DoctorAddress 'longitude' => $this->longitude !== null ? (string) $this->longitude : null, ], 'address' => $this->address, - 'telephone' => $this->telephone, + 'telephone' => $withContact ? $this->telephone : null, 'active' => $this->active, 'timezone' => $this->timezone, 'city' => $this->city !== null ? [ diff --git a/tests/Clinic/ClinicContactSourceTest.php b/tests/Clinic/ClinicContactSourceTest.php index c9ea33ad..99847637 100644 --- a/tests/Clinic/ClinicContactSourceTest.php +++ b/tests/Clinic/ClinicContactSourceTest.php @@ -15,6 +15,10 @@ use App\Tests\ApiTestCase; * A clinic whose address record said Karaj then rendered a Yazd street on the * public site. Contact fields must all resolve from the address record, and only * fall back to the legacy columns when that record has nothing to offer. + * + * The phone is a separate matter: it is not public data, so the anonymous + * response must never carry it. The resolution order is still asserted, just + * through the owner's view of the same endpoint. */ class ClinicContactSourceTest extends ApiTestCase { @@ -26,10 +30,23 @@ class ClinicContactSourceTest extends ApiTestCase $data = $this->fetchClinic($clinic); $this->assertSame('کرج، بلوار طالقانی', $data['location']); - $this->assertSame('02634567890', $data['phone']); - $this->assertSame('02634567890', $data['phone_number']); $this->assertSame('کرج', $data['city'][0]['name'] ?? null); $this->assertSame('البرز', $data['state'][0]['name'] ?? null); + + $owner = $this->fetchClinicAsOwner($clinic); + $this->assertSame('02634567890', $owner['phone']); + $this->assertSame('02634567890', $owner['phone_number']); + } + + public function testThePublicResponseCarriesNoPhoneNumber(): void + { + $clinic = $this->createClinic('یزد، خیابان قدیمی', '03531234567'); + $this->createAddressFor($clinic, 'کرج، بلوار طالقانی', '02634567890'); + + $data = $this->fetchClinic($clinic); + + $this->assertNull($data['phone'], 'شمارهٔ کلینیک نباید در پاسخ عمومی بیاید'); + $this->assertNull($data['phone_number']); } public function testLegacyColumnsAreUsedWhenTheClinicHasNoAddressRecord(): void @@ -39,8 +56,8 @@ class ClinicContactSourceTest extends ApiTestCase $data = $this->fetchClinic($clinic); $this->assertSame('یزد، خیابان قدیمی', $data['location']); - $this->assertSame('03531234567', $data['phone']); $this->assertSame([], $data['city']); + $this->assertSame('03531234567', $this->fetchClinicAsOwner($clinic)['phone']); } public function testBlankAddressFieldsFallBackInsteadOfBlankingTheContactBlock(): void @@ -51,8 +68,8 @@ class ClinicContactSourceTest extends ApiTestCase $data = $this->fetchClinic($clinic); $this->assertSame('یزد، خیابان قدیمی', $data['location']); - $this->assertSame('03531234567', $data['phone']); $this->assertSame('کرج', $data['city'][0]['name'] ?? null); + $this->assertSame('03531234567', $this->fetchClinicAsOwner($clinic)['phone']); } private function createClinic(?string $legacyAddress, ?string $legacyPhone): Clinic @@ -92,4 +109,13 @@ class ClinicContactSourceTest extends ApiTestCase return json_decode($this->client->getResponse()->getContent(), true)['data']['data']; } + + /** همان اندپوینت، این‌بار با توکنِ صاحب کلینیک — جایی که شماره دیده می‌شود. */ + private function fetchClinicAsOwner(Clinic $clinic): array + { + $body = $this->authJson('GET', '/api/v1/clinic/' . $clinic->getUuid(), $clinic->getUser()); + $this->assertSame(200, $this->responseCode()); + + return $body['data']['data']; + } } diff --git a/tests/Doctor/AddressPhoneIsNotPublicTest.php b/tests/Doctor/AddressPhoneIsNotPublicTest.php new file mode 100644 index 00000000..0b579873 --- /dev/null +++ b/tests/Doctor/AddressPhoneIsNotPublicTest.php @@ -0,0 +1,111 @@ +createUser(['ROLE_DOCTOR']); + $doctor = new Doctor($user, 'دکتر تماس'); + $this->em->persist($doctor); + + $address = DoctorAddress::forDoctor($doctor) + ->setName('مطب مرکزی') + ->setAddress('یزد، خیابان کاشانی') + ->setTelephone($phone); + $this->em->persist($address); + $this->em->flush(); + + // درخواستِ کرنل همین EM را می‌بیند و کالکشنِ آدرس‌های این نمونه از لحظهٔ + // ساخت خالی مانده است؛ بدون clear، پاسخ آدرسی نشان نمی‌دهد. + $this->em->clear(); + + return [ + $this->em->getRepository(Doctor::class)->find($doctor->getId()), + $this->em->getRepository(DoctorAddress::class)->find($address->getId()), + $this->em->getRepository(\App\Auth\Entity\User::class)->find($user->getId()), + ]; + } + + public function testPublicDoctorResponseKeepsTheAddressButDropsThePhone(): void + { + [$doctor, , ] = $this->doctorWithAddress(); + + $this->client->request('GET', '/api/v1/doctor/' . $doctor->getUuid()); + $this->assertSame(200, $this->responseCode()); + + $payload = json_decode($this->client->getResponse()->getContent(), true); + $address = $payload['data']['data']['address'][0]; + + $this->assertSame('یزد، خیابان کاشانی', $address['address'], 'آدرس باید عمومی بماند'); + $this->assertNull($address['telephone'], 'شمارهٔ مطب نباید عمومی باشد'); + } + + public function testTheDoctorStillSeesTheirOwnPhone(): void + { + [$doctor, , $owner] = $this->doctorWithAddress(); + + $body = $this->authJson('GET', '/api/v1/doctor/' . $doctor->getUuid(), $owner); + + $this->assertSame('03536666666', $body['data']['data']['address'][0]['telephone']); + } + + public function testThePatientSeesTheVenuePhoneOnTheirOwnAppointment(): void + { + [$doctor, $address, ] = $this->doctorWithAddress(); + $patient = $this->createUser(['ROLE_USER']); + + $appointment = $this->newAppointment($doctor, $patient, time() + 3600, time() + 5400); + $appointment->setAddressId((int) $address->getId()); + $this->em->persist($appointment); + $this->em->flush(); + + $body = $this->authJson('GET', '/api/v1/appointments/user', $patient); + $row = $body['data']['data'][0]; + + $this->assertSame($appointment->getUuid(), $row['uuid']); + $this->assertSame('یزد، خیابان کاشانی', $row['address']['address']); + $this->assertSame('03536666666', $row['address']['telephone']); + } + + /** + * پیش از این نشانی نوبت «اولین آدرس پزشک» بود، پس بیمارِ شعبهٔ دوم نشانی و شمارهٔ + * شعبهٔ اول را می‌دید — نه فقط ناقص، که نشانیِ اشتباه. + */ + public function testTheAppointmentShowsItsOwnVenueNotTheDoctorsFirstOne(): void + { + [$doctor, , ] = $this->doctorWithAddress('03511111111'); + + $second = DoctorAddress::forDoctor($doctor) + ->setName('مطب دوم') + ->setAddress('یزد، صفائیه') + ->setTelephone('03522222222'); + $this->em->persist($second); + $this->em->flush(); + + $patient = $this->createUser(['ROLE_USER']); + $appointment = $this->newAppointment($doctor, $patient, time() + 3600, time() + 5400); + $appointment->setAddressId((int) $second->getId()); + $this->em->persist($appointment); + $this->em->flush(); + + $row = $this->authJson('GET', '/api/v1/appointments/user', $patient)['data']['data'][0]; + + $this->assertSame('یزد، صفائیه', $row['address']['address']); + $this->assertSame('03522222222', $row['address']['telephone']); + } +}