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

215 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# دسترسی پزشک به آدرس‌های کلینیک‌های عضو + اجباری کردن فیلدهای آدرس
## زمینه
وقتی یک پزشک عضو یک کلینیک می‌شود، آدرس‌های آن کلینیک باید در پروفایل پزشک (بخش «آدرس‌های مطب») نمایش داده شود. این آدرس‌ها با `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
// مثال:
<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`:
```tsx
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)