Files
clinicpro/.claude/prompt/specialty-doctor-counts.md
T
hamedandClaude Opus 4.8 404dd03247 feat(specialty): public doctor-counts endpoint (per city)
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>
2026-06-16 22:27:27 +03:30

6.4 KiB
Raw Blame History

تعداد پزشکان هر تخصص (به تفکیک شهر)

پروژه

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 راستی‌آزمایی کن.