Files
clinicpro/.claude/prompt/doctor-clinic-address-access.md

10 KiB
Raw Permalink Blame History

دسترسی پزشک به آدرس‌های کلینیک‌های عضو + اجباری کردن فیلدهای آدرس

زمینه

وقتی یک پزشک عضو یک کلینیک می‌شود، آدرس‌های آن کلینیک باید در پروفایل پزشک (بخش «آدرس‌های مطب») نمایش داده شود. این آدرس‌ها با 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):

  1. ClinicRepository را به constructor اضافه کن (اگر از قبل وجود ندارد، بررسی کن)
  2. آدرس‌های کلینیک‌های عضو پزشک را دریافت کن
  3. آن‌ها را به لیست آدرس‌های پزشک اضافه کن
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 را با تغییرات endpoint listAddresses به‌روزرسانی کن (اضافه شدن clinic addresses به response)