6.5 KiB
فیلتر کلینیک بر اساس شهر/استانِ آدرس کلینیک
پروژه
clinicpro (Backend). تغییر فقط منطق داخلی است؛ قرارداد API (GET /api/v1/clinics?city=&state=) و مصرف nobat724_front تغییر نمیکند — نیازی به پرامپت همتای فرانت نیست.
زمینه
معماری مکانِ کلینیک عوض شده: قبلاً شهر/استان روی خودِ Clinic (ستونهای city_id/province_id) ذخیره میشد، اما حالا این ستونها عملاً پر نمیشوند و شهر/استان واقعی روی آدرس کلینیک است — یعنی رکورد DoctorAddress با clinic_id = clinic.id، که city_id/province_id خودش را دارد.
شواهد DB:
clinics: تنها رکوردهاcity_id/province_idندارند (یا قدیمیاند). فیلتر رویc.cityId/c.provinceIdهیچ نتیجهای نمیدهد.doctor_addressesباtype='clinic'وclinic_idپر: مثلاًclinic_id=226 → city_id=132 (یزد), province_id=100،clinic_id=225 → city_id=101 (تبریز), province_id=1.
در نتیجه GET /api/v1/clinics?city=132&state=100 (یزد) صفر کلینیک برمیگرداند، هرچند کلینیک ۲۲۶ در یزد است.
مشکل / هدف
ClinicRepository::findWithFilters باید فیلتر city/state را از طریق آدرس کلینیک (DoctorAddress با clinic_id = clinic.id) اعمال کند، نه ستونهای منسوخِ c.cityId/c.provinceId.
فایلهای مرتبط
| فایل | نقش |
|---|---|
src/Clinic/Repository/ClinicRepository.php |
findWithFilters — فیلتر مکان |
src/Doctor/Entity/DoctorAddress.php |
حامل مکان: clinicId (scalar)، city (ManyToOne City)، province (ManyToOne Province) |
src/Clinic/Entity/Clinic.php |
cityId/provinceId منسوخ (دیگر مبنای فیلتر نیست) |
وضعیت فعلی (کد واقعی)
ClinicRepository::findWithFilters (مکان روی خود Clinic — نادرست):
$qb = $this->createQueryBuilder('c')
->leftJoin('c.specialties', 's')
->distinct();
if (!empty($filters['state'])) {
$qb->andWhere('c.provinceId = :state')->setParameter('state', (int) $filters['state']);
}
if (!empty($filters['city'])) {
$qb->andWhere('c.cityId = :city')->setParameter('city', (int) $filters['city']);
}
if (!empty($filters['specialty'])) {
$qb->andWhere('s.id = :specialty')->setParameter('specialty', (int) $filters['specialty']);
}
$qb->orderBy('c.id', $sort);
DoctorAddress (مرجع — مکان واقعی):
#[ORM\Column(name: 'clinic_id', type: 'integer', nullable: true)]
private ?int $clinicId = null; // scalar (نه رابطهی Doctrine به Clinic)
#[ORM\ManyToOne(targetEntity: City::class)]
#[ORM\JoinColumn(name: 'city_id', ...)]
private ?City $city = null;
#[ORM\ManyToOne(targetEntity: Province::class)]
#[ORM\JoinColumn(name: 'province_id', ...)]
private ?Province $province = null;
چون رابطهی Doctrine بین
ClinicوDoctorAddressوجود ندارد (فقط scalarclinicId)، باید با یک join شرطدار (WITH addr.clinicId = c.id) join کرد.
وظایف
۱. join به آدرس کلینیک و فیلتر روی city/province آدرس
c.provinceId/c.cityId را با join به DoctorAddress (روی clinic_id) و فیلتر addr.city/addr.province جایگزین کن. join فقط وقتی لازم است که city یا state در فیلترها باشد:
$qb = $this->createQueryBuilder('c')
->leftJoin('c.specialties', 's')
->distinct();
if (!empty($filters['city']) || !empty($filters['state'])) {
// DoctorAddress رابطهی Doctrine به Clinic ندارد؛ با شرط clinicId join میکنیم
$qb->join(
\App\Doctor\Entity\DoctorAddress::class,
'addr',
\Doctrine\ORM\Query\Expr\Join::WITH,
'addr.clinicId = c.id'
);
if (!empty($filters['city'])) {
$qb->andWhere('IDENTITY(addr.city) = :city')
->setParameter('city', (int) $filters['city']);
}
if (!empty($filters['state'])) {
$qb->andWhere('IDENTITY(addr.province) = :state')
->setParameter('state', (int) $filters['state']);
}
}
if (!empty($filters['specialty'])) {
$qb->andWhere('s.id = :specialty')->setParameter('specialty', (int) $filters['specialty']);
}
$qb->orderBy('c.id', $sort);
IDENTITY(addr.city)کلید خارجی (city_id) را بدون join اضافی به جدولcategoriesمیگیرد — سبک و دقیق.distinct()حفظ شود (یک کلینیک ممکن است چند آدرس داشته باشد).Paginatorموجود روی همین$qbکار میکند؛ بهخاطر join شرطدار،distinctباید فعال بماند تا شمارش total درست باشد.
۲. مستندسازی docs/api/clinic.md
در بخش GET /api/v1/clinics، یک جمله اضافه کن که فیلتر city/state بر اساس شهر/استانِ آدرس کلینیک اعمال میشود (نه فیلد مستقیم روی کلینیک).
نکات مهم
- این رفع، باگِ
Class Clinic has no field stateId(که قبلاً بهprovinceIdتغییر یافت) را هم بهطور ریشهای حل میکند: مکان دیگر از خود Clinic خوانده نمیشود. DoctorAddress.clinicIdیک ستون scalar است (نه association)، پس join باید باJoin::WITH 'addr.clinicId = c.id'باشد، نهc.addresses.- ستونهای
c.cityId/c.provinceIdرا دست نزن (ممکن است جای دیگری استفاده شوند)؛ فقط مبنای فیلتر را عوض کن. - پاسخها از
BaseController(paginated)؛ migration لازم نیست. - تست رفتاری (از داخل container و هاست):
GET /api/v1/clinics?city=132&state=100(یزد) → باید کلینیک ۲۲۶ را برگرداند (قبلاً صفر بود).GET /api/v1/clinics?city=101&state=1(تبریز) → کلینیک ۲۲۵.GET /api/v1/clinicsبدون فیلتر → همهی کلینیکها (join اعمال نشود).?specialty=<id>همچنان کار کند.- حتماً در محیط prod هم تست کن (
cache:clear --env=prodسپس درخواست ازhttps://clinic-pro.ddev.site) چون باگ قبلی فقط در prod ظاهر شد.