# افزودن شهر به لیست پزشکان + رفع سقف خاموش limit ## پروژه `clinicpro` (backend) **cross-repo:** خروجی این endpoint را `nobat724_front/app/sitemap.js` مصرف می‌کند. پرامپت همتا (بعد از این اجرا شود): `nobat724_front/.claude/prompt/sitemap-simplify-with-city.md` ## زمینه در ممیزی SEO سایت عمومی دو محدودیت این endpoint باعث دو باگ در sitemap شد: ۱. **پاسخ لیست پزشکان فیلد شهر ندارد.** سایت عمومی چند-دامنه‌ای است (۳۵ دامنهٔ شهری) و باید بداند هر پزشک به کدام دامنه تعلق دارد. چون شهر در پاسخ نیست، sitemap دامنهٔ اصلی مجبور است **۳۵ بار جداگانه** لیست را با `city_id` بگیرد و از کل کم کند تا بفهمد کدام پزشک شهر اختصاصی ندارد. تولید sitemap ریشه ~۱۳ ثانیه طول می‌کشد. ۲. **پارامتر `limit` بی‌صدا به ۵۰ سقف می‌خورد.** کلاینت `limit=500` می‌فرستد، پاسخ ۵۰ رکورد است و هیچ نشانه‌ای از سقف‌خوردن در پاسخ نیست. این باعث شد sitemap ماه‌ها روی ۵۰ پزشک بریده بماند (حلقهٔ صفحه‌بندی وقتی `items.length < limit` بود متوقف می‌شد). سمت فرانت با تکیه بر `meta.totalPages` رفع شد، ولی رفتار خاموشِ API همچنان تله است. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `clinicpro/src/Doctor/Entity/Doctor.php` | `toListArray()` — شکل پاسخ لیست | | `clinicpro/src/Doctor/Repository/DoctorRepository.php` | سقف `limit` در خطوط ۴۵ و ۱۴۲ | | `clinicpro/src/Doctor/Controller/DoctorController.php` | route `/api/v1/doctors` خط ۲۵۲ | | `clinicpro/docs/api/doctor.md` | مستندات — الزاماً به‌روز شود | ## وضعیت فعلی `src/Doctor/Entity/Doctor.php:540` — بدون شهر: ```php public function toListArray(array $schedules = []): array { $sf = $this->computeScheduleFields($schedules); return [ 'id' => (string) $this->id, 'uuid' => $this->uuid, 'name' => $this->name, 'gender' => $this->gender, 'degree' => $this->degree, 'img' => $this->images ?? [], 'specialties' => array_map(fn(Specialty $s) => [...], $this->specialties->toArray()), 'satisfaction' => $this->hasPublicRating() ? (string) $this->doctorRatePercentage : null, 'point' => $this->hasPublicRating() ? (string) $this->doctorRate : null, 'free_turn' => $sf['free_turn'], 'hours_of_work' => $sf['hours_of_work'], 'active' => $this->activeDoctorAppointment && $sf['has_schedule'], 'owner_status' => $this->ownerStatus, ]; } ``` `src/Doctor/Repository/DoctorRepository.php:45` و `:142` — سقف خاموش: ```php $limit = min(50, max(1, (int) ($filters['limit'] ?? 10))); ``` > نکته: در پاسخ **جزئیات** پزشک (`toDetailArray`) شهر داخل `address[].city` هست، ولی `city` و `state` سطح‌بالا آرایهٔ خالی برمی‌گردند. سایت عمومی برای همین از `address[].city.id` استخراج می‌کند (`nobat724_front/lib/domainHelpers.js` → `extractEntityCityId`). این پرامپت آن رفتار را تغییر نمی‌دهد. ## وظایف ### ۱. افزودن شهر به `toListArray()` شهرِ پزشک از آدرس‌هایش می‌آید. شهر **اولین آدرس** (یا آدرس اصلی، اگر مفهوم آدرس اصلی وجود دارد) به‌عنوان شهر پزشک برگردد — چون سایت عمومی هم برای canonical دقیقاً همین قاعده («یک شهر اصلی برای پزشک چند-شهری») را اعمال می‌کند. ```php 'city' => $primaryAddress?->getCity() ? [ 'id' => (string) $primaryAddress->getCity()->getId(), 'name' => $primaryAddress->getCity()->getName(), ] : null, 'state' => $primaryAddress?->getProvince() ? [ 'id' => (string) $primaryAddress->getProvince()->getId(), 'name' => $primaryAddress->getProvince()->getName(), ] : null, ``` - شکل `{ id, name }` باشد تا با `city` در پاسخ لیست کلینیک‌ها یکسان باشد (آنجا آرایه‌ای از همین شکل است). - `id` رشته باشد — هم‌راستا با بقیهٔ فیلدهای این متد. - پزشک بدون آدرس → `null` (نه آرایهٔ خالی، تا با «شهر ندارد» تفکیک‌پذیر بماند). - **N+1 نساز:** آدرس/شهر/استان در همان کوئری `findWithFilters` با `JOIN`/`addSelect` بارگذاری شود، نه lazy per-doctor. ### ۲. شفاف‌کردن سقف `limit` سقف ۵۰ حفظ شود (محافظت از دیتابیس)، ولی دیگر خاموش نباشد: - مقدار مؤثر `limit` در `meta` برگردد (اگر الان برنمی‌گردد) تا کلاینت بفهمد درخواستش کوتاه شده. - در `docs/api/doctor.md` صریح نوشته شود: «`limit` حداکثر ۵۰؛ مقادیر بزرگ‌تر بی‌صدا به ۵۰ کاهش می‌یابند». اگر تصمیم گرفتی سقف را برای مصرف‌کنندهٔ sitemap بالاتر ببری، آن را به‌صورت یک حد جداگانه و مستند انجام بده — نه با حذف `min()`. ### ۳. به‌روزرسانی مستندات `clinicpro/docs/api/doctor.md` برای `GET /api/v1/doctors`: - فیلدهای جدید `city` و `state` با مثال واقعی JSON - رفتار و سقف `limit` ## نکات مهم - `toListArray()` را مصرف‌کنندگان دیگری هم دارند (پنل ادمین، اپ Tauri). **فیلد اضافه می‌کنیم، فیلد موجود را تغییر نام یا حذف نمی‌کنیم** — تغییر افزایشی و backward-compatible باشد. - بعد از تغییر، پاسخ واقعی را با یک پزشک دارای آدرس تست کن: `curl -s "https://clinic-pro.ir/api/v1/doctors?page=1&limit=5" | jq '.data.data[0] | {name, city, state}'` - پزشک `beaca548-816f-4613-937d-360db01bd7c8` شهرش یاسوج (`city_id: 123`) است — نمونهٔ خوبی برای تأیید. - Entity تغییر نمی‌کند (فقط متد سریال‌سازی) ⇒ migration لازم نیست.