Merge branch 'dev' into main

# Conflicts:
#	docs/api/doctor.md
This commit is contained in:
hamed
2026-07-19 16:15:30 +03:30
1026 changed files with 190049 additions and 15130 deletions
@@ -138,6 +138,88 @@ class DoctorRepository extends ServiceEntityRepository
*
* @return int[]
*/
/**
* شهر/استان دسته‌ای پزشکان برای پاسخ لیست — دو کوئری ثابت، نه یکی به‌ازای هر پزشک.
*
* همان قاعده‌ای که فیلتر city_id/state_id در findWithFilters اعمال می‌کند اینجا هم
* برقرار است: اول آدرس شخصی خود پزشک، و اگر نداشت آدرس کلینیکی که عضو آن است
* (آدرس کلینیک ردیفی از DoctorAddress با doctor IS NULL و clinicId پرشده است).
* بدون این fallback، پزشکی که فقط از طریق کلینیک مکان دارد در فیلتر city_id
* می‌آمد ولی در پاسخ شهرش خالی بود.
*
* @param Doctor[] $doctors
* @return array<int, array{city: ?array, province: ?array}>
*/
public function findLocationsByDoctors(array $doctors): array
{
$ids = array_values(array_filter(array_map(fn(Doctor $d) => $d->getId(), $doctors)));
if (!$ids) {
return [];
}
$locationFields = [
'c.id AS cityId', 'c.uuid AS cityUuid', 'c.name AS cityName',
'p.id AS provinceId', 'p.uuid AS provinceUuid', 'p.name AS provinceName',
];
$ownRows = $this->getEntityManager()->createQueryBuilder()
->select('IDENTITY(da.doctor) AS doctorId', ...$locationFields)
->from(DoctorAddress::class, 'da')
->join('da.city', 'c')
->leftJoin('da.province', 'p')
->where('da.doctor IN (:ids)')
->setParameter('ids', $ids)
->getQuery()
->getArrayResult();
$map = $this->indexLocationRows($ownRows, []);
$missing = array_values(array_diff($ids, array_keys($map)));
if ($missing) {
$clinicRows = $this->getEntityManager()->createQueryBuilder()
->select('cd.id AS doctorId', ...$locationFields)
->from(Clinic::class, 'cl')
->join('cl.doctors', 'cd')
->join(DoctorAddress::class, 'ca', Join::WITH, 'ca.clinicId = cl.id AND ca.doctor IS NULL')
->join('ca.city', 'c')
->leftJoin('ca.province', 'p')
->where('cd.id IN (:ids)')
->setParameter('ids', $missing)
->getQuery()
->getArrayResult();
$map = $this->indexLocationRows($clinicRows, $map);
}
return $map;
}
/** اولین مکانِ هر پزشک برنده است — پزشک چند-مطبی یک شهر اصلی می‌گیرد. */
private function indexLocationRows(array $rows, array $map): array
{
foreach ($rows as $row) {
$doctorId = (int) $row['doctorId'];
if (isset($map[$doctorId])) {
continue;
}
$map[$doctorId] = [
'city' => [
'uuid' => $row['cityUuid'],
'id' => (string) $row['cityId'],
'name' => $row['cityName'],
'parent' => $row['provinceId'] !== null ? (string) $row['provinceId'] : null,
],
'province' => $row['provinceId'] !== null ? [
'uuid' => $row['provinceUuid'],
'id' => (string) $row['provinceId'],
'name' => $row['provinceName'],
] : null,
];
}
return $map;
}
private function doctorIdsViaClinicLocation(string $field, int $locationId): array
{
$column = $field === 'city' ? 'ca.city' : 'ca.province';