10 KiB
دسترسی پزشک به آدرسهای کلینیکهای عضو + اجباری کردن فیلدهای آدرس
زمینه
وقتی یک پزشک عضو یک کلینیک میشود، آدرسهای آن کلینیک باید در پروفایل پزشک (بخش «آدرسهای مطب») نمایش داده شود. این آدرسها با 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)
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)
#[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)
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)
...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
// 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() را با پارامتر اختیاری گسترش بده:
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):
ClinicRepositoryرا به constructor اضافه کن (اگر از قبل وجود ندارد، بررسی کن)- آدرسهای کلینیکهای عضو پزشک را دریافت کن
- آنها را به لیست آدرسهای پزشک اضافه کن
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 را به این شکل تغییر بده:
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، لیبلهای مربوطه را به این شکل تغییر بده:
// مثال:
<label>تلفن <span className="text-red-500">*</span></label>
<label>آدرس کامل <span className="text-red-500">*</span></label>
<label>استان <span className="text-red-500">*</span></label>
<label>شهر <span className="text-red-500">*</span></label>
و اضافه کردن نمایش خطا در زیر فیلدها با formState: { errors } از useForm:
const { register, handleSubmit, watch, setValue, reset, formState: { errors } } = useForm<AddrForm>({
resolver: zodResolver(addrSchema),
});
// ...
{errors.telephone && <p className="text-xs text-red-500 mt-1">{errors.telephone.message}</p>}
{errors.address && <p className="text-xs text-red-500 mt-1">{errors.address.message}</p>}
برای 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را با تغییرات endpointlistAddressesبهروزرسانی کن (اضافه شدن clinic addresses به response)