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>
This commit is contained in:
hamed
2026-06-16 22:27:27 +03:30
co-authored by Claude Opus 4.8
parent 28011160a2
commit 404dd03247
5 changed files with 205 additions and 1 deletions
@@ -30,6 +30,31 @@ class SpecialtyController extends BaseController
return $this->success(['data' => $items]);
}
#[OA\Get(
path: '/api/v1/specialties/doctor-counts',
summary: 'Active specialties with the number of doctors (optionally scoped to a city)',
parameters: [
new OA\Parameter(name: 'city_id', in: 'query', required: false, schema: new OA\Schema(type: 'integer')),
],
responses: [
new OA\Response(response: 200, description: 'Specialties with number_of_doctors'),
]
)]
#[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]);
}
// ── Admin CRUD ────────────────────────────────────────────────────────────
#[Route('/api/v1/admin/specialty', methods: ['POST'])]
@@ -2,6 +2,7 @@
namespace App\Specialty\Repository;
use App\Doctor\Entity\Doctor;
use App\Specialty\Entity\Specialty;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -28,6 +29,34 @@ class SpecialtyRepository extends ServiceEntityRepository
return $qb->getQuery()->getResult();
}
/**
* Count distinct doctors per specialty, optionally limited to a city.
* Mirrors the join filters used by the public doctor list (cities/specialties).
*
* @return array<int,int> specialtyId => doctorCount
*/
public function doctorCountsByCity(?int $cityId): array
{
$qb = $this->getEntityManager()->createQueryBuilder()
->select('s.id AS specialty_id', 'COUNT(DISTINCT d.id) AS cnt')
->from(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);
}
$map = [];
foreach ($qb->getQuery()->getArrayResult() as $row) {
$map[(int) $row['specialty_id']] = (int) $row['cnt'];
}
return $map;
}
public function findBySlug(string $slug): ?Specialty
{
return $this->findOneBy(['slug' => $slug]);