Add GET /api/v1/specialties/doctor-counts?city_id= returning every active specialty with number_of_doctors (distinct doctors via doctor_specialties, scoped by doctor_cities when city_id is given). Make /api/v1/specialties GET public. Powers the /specialties page count. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.4 KiB
تعداد پزشکان هر تخصص (به تفکیک شهر)
پروژه
clinicpro (Backend — منبع حقیقت).
Cross-repo: صفحهی عمومی
/specialtiesدرnobat724_frontاین داده را مصرف میکند تا زیر هر تخصص «N پزشک» نشان دهد (پرامپت همتا:nobat724_front/.claude/prompt/specialties-doctor-count-wire.md). این پرامپت اول اجرا شود.
زمینه
صفحهی /specialties در سایت عمومی، زیر هر کارت تخصص میخواهد تعداد پزشکان آن تخصص را نشان دهد ({number_of_doctors} پزشک). اما هیچ endpointی این تعداد را نمیدهد؛ GET /api/v1/specialties فقط {id, uuid, name, slug, status, weight, parent_id} برمیگرداند. سایت چند-شهری است و شمارش باید فقط پزشکانِ شهرِ دامنهی جاری باشد.
دادهی موجود: join table doctor_specialties (پزشک↔تخصص) و doctor_cities (پزشک↔شهر). Doctor::getSpecialties() و getCities() روابط ManyToManyاند. endpoint GET /api/v1/doctors از قبل پارامترهای specialty_id و city_id را میپذیرد (الگوی فیلتر شهر).
مشکل / هدف
یک endpoint عمومی که برای هر تخصصِ فعال، تعداد پزشکانِ آن تخصص را در یک شهر مشخص (city_id) برگرداند — در یک فراخوانی (نه N درخواست).
فایلهای مرتبط
| فایل | نقش |
|---|---|
src/Specialty/Controller/SpecialtyController.php |
افزودن endpoint doctorCounts |
src/Specialty/Repository/SpecialtyRepository.php |
متد شمارش پزشک per specialty با فیلتر شهر |
src/Doctor/Entity/Doctor.php |
روابط specialties (doctor_specialties) و cities (doctor_cities) — مرجع |
config/packages/security.yaml |
endpoint جدید زیر public_endpoints (مثل specialties) |
docs/api/specialty.md |
مستندسازی |
وضعیت فعلی (کد واقعی)
SpecialtyController::list (مرجع سبک):
#[Route('/api/v1/specialties', methods: ['GET'])]
public function list(Request $request): JsonResponse
{
$parentId = $request->query->get('parent_id');
$items = array_map(fn(Specialty $s) => $s->toArray(),
$this->repo->findActive($parentId !== null ? (int)$parentId : null));
return $this->success(['data' => $items]);
}
Specialty::toArray: {id, uuid, name, slug, status, weight, parent_id} — بدون تعداد پزشک.
Doctor:
#[ORM\ManyToMany(targetEntity: Specialty::class)]
#[ORM\JoinTable(name: 'doctor_specialties', ...)]
private Collection $specialties;
#[ORM\ManyToMany(targetEntity: City::class)] // doctor_cities
private Collection $cities;
وظایف
۱. متد شمارش در SpecialtyRepository
متدی که با یک کوئری، نگاشت specialty_id => count(distinct doctor) را برای پزشکانِ یک شهر برگرداند:
/** @return array<int,int> specialtyId => doctorCount (within a city if given) */
public function doctorCountsByCity(?int $cityId): array
{
$qb = $this->getEntityManager()->createQueryBuilder()
->select('s.id AS specialty_id', 'COUNT(DISTINCT d.id) AS cnt')
->from(\App\Doctor\Entity\Doctor::class, 'd')
->join('d.specialties', 's')
->groupBy('s.id');
if ($cityId !== null) {
$qb->join('d.cities', 'c')->andWhere('c.id = :city')->setParameter('city', $cityId);
}
// در صورت وجود فیلد وضعیت پزشک (فعال/تأییدشده) آن را هم اعمال کن تا با لیست عمومی doctors همخوان باشد
$rows = $qb->getQuery()->getArrayResult();
$map = [];
foreach ($rows as $r) { $map[(int)$r['specialty_id']] = (int)$r['cnt']; }
return $map;
}
اگر
Doctorفیلد وضعیت/visibility دارد (مثل آنچهGET /doctorsبرای لیست عمومی فیلتر میکند)، همان شرط را اینجا هم بگذار تا تعداد با نتیجهی واقعیِ/doctors?specialty_id=&city_id=یکی باشد. الگوی فیلتر را ازDoctorController::listبردار.
۲. endpoint doctorCounts در SpecialtyController
#[Route('/api/v1/specialties/doctor-counts', methods: ['GET'])]
public function doctorCounts(Request $request): JsonResponse
{
$cityId = $request->query->get('city_id');
$counts = $this->repo->doctorCountsByCity($cityId !== null ? (int) $cityId : null);
$items = array_map(function (Specialty $s) use ($counts) {
$arr = $s->toArray();
$arr['number_of_doctors'] = $counts[$s->getId()] ?? 0;
return $arr;
}, $this->repo->findActive(null));
return $this->success(['data' => $items]);
}
- خروجی: همان شکل
specialties+ کلیدnumber_of_doctors. - تخصصهای بدون پزشک →
0.
۳. عمومیکردن مسیر در security.yaml
specialties از قبل عمومی است؛ مطمئن شو /api/v1/specialties/doctor-counts هم زیر public_endpoints میافتد (الگوی api/v1/specialties معمولاً با prefix پوشش میدهد — تأیید کن مسیر جدید عمومی است، وگرنه اضافه کن).
۴. مستندسازی docs/api/specialty.md
endpoint جدید: method/path، query city_id (اختیاری)، و response با number_of_doctors.
نکات مهم
- شمارش
DISTINCT d.idتا اگر پزشک چند شهر/رابطه دارد دوبار شمرده نشود. city_idهمان id شهر درcategories(bundle=city) است که فرانت ازgetStateInfo().matchedCity.idمیفرستد.- اگر
city_idنیامد، شمارش سراسری برگردد (fallback ایمن). - فیلتر وضعیت پزشک باید با
GET /doctorsهمخوان باشد تا «N پزشک» با لیست واقعی بخواند. - یک کوئری grouped (نه N+1).
- پاسخها از
BaseController؛ migration لازم نیست. - تست:
GET /api/v1/specialties/doctor-counts?city_id=<id>→ آرایهای از تخصصها که هر کدامnumber_of_doctorsدارند؛ مجموع/نمونه را با شمارش مستقیمdoctor_specialties⋈doctor_citiesراستیآزمایی کن.