# دسترسی پزشک به آدرس‌های کلینیک‌های عضو + اجباری کردن فیلدهای آدرس ## زمینه وقتی یک پزشک عضو یک کلینیک می‌شود، آدرس‌های آن کلینیک باید در پروفایل پزشک (بخش «آدرس‌های مطب») نمایش داده شود. این آدرس‌ها با `type: 'clinic'` در entity `DoctorAddress` ذخیره می‌شوند و `clinic_id` دارند، اما در پاسخ API فعلی `clinic_name` برگردانده نمی‌شود در حالی که frontend آن را از `a.clinic_name` می‌خواند. همچنین پزشک باید بتواند آدرس‌های کلینیک‌هایی که عضو آن‌هاست را ببیند — در حال حاضر `GET /api/v1/clinic-pro/doctor-addresses/{doctorId}` فقط آدرس‌های مستقیم Doctor entity را برمی‌گرداند و آدرس‌های کلینیک‌های وابسته را شامل نمی‌شود. علاوه بر این، در `AddressModal` (افزودن آدرس جدید در پروفایل پزشک)، فیلدهای `telephone`، `address`، `province_id` و `city_id` اختیاری هستند و باید اجباری شوند. ## مشکل / هدف سه هدف مجزا: **۱.** آدرس‌های کلینیک‌های عضو پزشک باید در لیست آدرس‌های پزشک نمایش داده شوند **۲.** `DoctorAddress::toArray()` باید `clinic_name` را نیز برگرداند تا frontend بتواند آن را نشان دهد **۳.** در `AddressModal`، فیلدهای تلفن، آدرس کامل، شهر و استان باید اجباری (required) باشند ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `src/Doctor/Entity/DoctorAddress.php` | entity آدرس — متد `toArray()` باید `clinic_name` اضافه کند | | `src/Doctor/Controller/DoctorController.php` | `listAddresses()` — خط 671 — باید آدرس‌های کلینیک‌ها را هم شامل شود | | `src/Clinic/Repository/ClinicRepository.php` | دارای `findByDoctor(Doctor $doctor): array` | | `src/Clinic/Controller/ClinicController.php` | `listAddresses()` — خط 544 — PUBLIC، آدرس‌های کلینیک را برمی‌گرداند | | `assets/admin/pages/DoctorDetailPage.tsx` | `addrSchema` در خط 772 — `AddressModal` — فرم افزودن آدرس | | `docs/api/doctor.md` | مستندات endpoint پزشک | ## وضعیت فعلی ### Backend — DoctorAddress::toArray() (فایل: `src/Doctor/Entity/DoctorAddress.php`, خط 112) ```php public function toArray(): array { return [ 'id' => (string) $this->id, 'uuid' => $this->uuid, 'type' => $this->type, 'clinic_id' => $this->clinicId, // ← فقط ID، بدون نام 'name' => $this->name, // ... ]; } ``` ### Backend — listAddresses در DoctorController (خط 671) ```php #[Route('/api/v1/clinic-pro/doctor-addresses/{doctorId}', methods: ['GET'])] public function listAddresses(int $doctorId): JsonResponse { $doctor = $this->doctorRepo->find($doctorId); // ... $addresses = array_map(fn(DoctorAddress $a) => $a->toArray(), $doctor->getAddresses()->toArray()); // ← فقط آدرس‌های مستقیم Doctor entity — آدرس‌های کلینیک‌های عضو شامل نمی‌شود return $this->success(['data' => $addresses]); } ``` ### Frontend — addrSchema در DoctorDetailPage.tsx (خط 772) ```tsx const addrSchema = z.object({ name: z.string().optional(), address: z.string().optional(), // ← باید required باشد telephone: z.string().max(20).optional(), // ← باید required باشد province_id: z.number().nullable().optional(), // ← باید required باشد city_id: z.number().nullable().optional(), // ← باید required باشد latitude: z.string().optional(), longitude: z.string().optional(), }); ``` ### Frontend — SessionEditor (خط 1082-1085) ```tsx ...addresses.filter(a => a.type === 'clinic').map(a => ({ label: `🏨 ${a.clinic_name ? `${a.clinic_name}${a.name ? ` — ${a.name}` : ''}` : (a.name ?? a.address ?? `کلینیک ${a.id}`)}`, })) // ← a.clinic_name را استفاده می‌کند اما toArray() این فیلد را برنمی‌گرداند ``` ## وضعیت ClinicRepository ```php // src/Clinic/Repository/ClinicRepository.php — خط 25 public function findByDoctor(Doctor $doctor): array { return $this->createQueryBuilder('c') ->innerJoin('c.doctors', 'd') ->where('d.id = :doctorId') ->setParameter('doctorId', $doctor->getId()) ->getQuery() ->getResult(); } ``` متد `findByDoctor()` وجود دارد — از آن استفاده کن. ## وظایف ### ۱. Backend — اضافه کردن `clinic_name` به `DoctorAddress::toArray()` در `src/Doctor/Entity/DoctorAddress.php`، `DoctorAddress` entity دارای فیلد `clinicId` (integer) است اما ارتباط مستقیم به entity `Clinic` ندارد. بنابراین `clinic_name` باید از طریق injection خارجی به toArray اضافه شود. **روش پیشنهادی:** متد `toArray()` را با پارامتر اختیاری گسترش بده: ```php public function toArray(?string $clinicName = null): array { return [ 'id' => (string) $this->id, 'uuid' => $this->uuid, 'type' => $this->type, 'clinic_id' => $this->clinicId, 'clinic_name' => $clinicName, // ← جدید 'name' => $this->name, // ... بقیه فیلدها بدون تغییر ]; } ``` ### ۲. Backend — آدرس‌های کلینیک‌های عضو را در `listAddresses` شامل کن در `src/Doctor/Controller/DoctorController.php`، متد `listAddresses` (خط 671): 1. `ClinicRepository` را به constructor اضافه کن (اگر از قبل وجود ندارد، بررسی کن) 2. آدرس‌های کلینیک‌های عضو پزشک را دریافت کن 3. آن‌ها را به لیست آدرس‌های پزشک اضافه کن ```php public function listAddresses(int $doctorId): JsonResponse { $doctor = $this->doctorRepo->find($doctorId); if ($doctor === null) { return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404); } // آدرس‌های شخصی پزشک $personalAddresses = array_map( fn(DoctorAddress $a) => $a->toArray(), $doctor->getAddresses()->toArray() ); // آدرس‌های کلینیک‌هایی که پزشک عضو آن‌هاست $clinicAddresses = []; $clinics = $this->clinicRepo->findByDoctor($doctor); foreach ($clinics as $clinic) { $clinicAddrs = $this->addressRepo->findBy(['clinicId' => $clinic->getId()]); foreach ($clinicAddrs as $addr) { $clinicAddresses[] = $addr->toArray($clinic->getName()); } } return $this->success(['data' => array_merge($personalAddresses, $clinicAddresses)]); } ``` **نکته:** `$this->addressRepo` باید از نوع `DoctorAddressRepository` باشد (همان که در ClinicController استفاده می‌شود). بررسی کن که DoctorController این repo را دارد یا نه — اگر ندارد به constructor اضافه کن. **نکته:** `$this->clinicRepo` از نوع `ClinicRepository` — بررسی کن DoctorController این dependency را دارد. ### ۳. Frontend — اجباری کردن فیلدها در `addrSchema` در `assets/admin/pages/DoctorDetailPage.tsx`، خط 772، schema را به این شکل تغییر بده: ```tsx const addrSchema = z.object({ name: z.string().optional(), address: z.string().min(1, 'آدرس کامل اجباری است'), telephone: z.string().min(1, 'تلفن اجباری است').max(20), province_id: z.number({ required_error: 'استان اجباری است' }), city_id: z.number({ required_error: 'شهر اجباری است' }), latitude: z.string().optional(), longitude: z.string().optional(), }); ``` همچنین لیبل فیلدهای اجباری در form باید علامت ستاره (`*`) داشته باشند — فیلدهای `address`، `telephone`، `استان`، `شهر`. در JSX، لیبل‌های مربوطه را به این شکل تغییر بده: ```tsx // مثال: ``` و اضافه کردن نمایش خطا در زیر فیلدها با `formState: { errors }` از `useForm`: ```tsx const { register, handleSubmit, watch, setValue, reset, formState: { errors } } = useForm({ resolver: zodResolver(addrSchema), }); // ... {errors.telephone &&

{errors.telephone.message}

} {errors.address &&

{errors.address.message}

} ``` برای `province_id` و `city_id` که از `SearchableSelect` استفاده می‌کنند، error در زیر select نمایش بده. ## نکات مهم - **Migration لازم نیست** — هیچ schema تغییر نمی‌کند، فقط response format و validation - **`DoctorAddress::toArray(?string $clinicName = null)`** — پارامتر optional است، همه فراخوانی‌های فعلی بدون تغییر کار می‌کنند - **DoctorController constructor** — بررسی کن آیا `ClinicRepository` و `DoctorAddressRepository` از قبل inject شده‌اند - **ClinicController** دارای `$this->addressRepo` از نوع `DoctorAddressRepository` — در DoctorController باید نام variable یکسان باشد (بررسی کن) - **frontend** در خط 1084: `a.clinic_name` — بعد از اضافه شدن این فیلد به response، این بخش کار می‌کند - `GET /api/v1/clinic-pro/doctor-addresses/{doctorId}` PUBLIC است — نیازی به auth guard جدید نیست - بعد از تغییر controller: `ddev exec php bin/console cache:clear` - بعد از تغییر frontend: `ddev exec yarn dev` و TypeScript check - مستندات: `docs/api/doctor.md` را با تغییرات endpoint `listAddresses` به‌روزرسانی کن (اضافه شدن clinic addresses به response)