feat(appointment): include doctor specialties and clinic address in toArray

The appointment detail view needs the doctor's specialty and the clinic
address/phone/map, which toArray didn't return. Add doctor.specialties[] and
a top-level `address` (the doctor's first address via DoctorAddress::toArray —
address, telephone, map). Additive only; existing keys unchanged. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-16 12:01:32 +03:30
co-authored by Claude Opus 4.8
parent b60dbdadba
commit 28011160a2
3 changed files with 105 additions and 9 deletions
@@ -0,0 +1,81 @@
# غنی‌سازی پاسخ نوبت با تخصص و آدرس مطب پزشک
## پروژه
`clinicpro` (Backend — منبع حقیقت).
> **Cross-repo:** صفحه‌ی «جزئیات نوبت» در `nobat724_front` (داشبورد → نوبت‌های من → مشاهده جزئیات) این فیلدها را مصرف می‌کند (پرامپت همتا: `nobat724_front/.claude/prompt/appointment-detail-fields-fix.md`). این پرامپت **اول** اجرا شود.
## زمینه
`Appointment::toArray()` فقط `doctor:{uuid,name}`, `slot_start/slot_end`, `status`, `note`, `patient_*` را برمی‌گرداند. اما صفحه‌ی جزئیات نوبت در فرانت می‌خواهد **تخصص پزشک، آدرس مطب، تلفن و مختصات نقشه** را هم نشان دهد. چون این داده‌ها در پاسخ نیستند، آن بخش‌ها خالی می‌مانند.
`Doctor` این روابط را دارد: `getSpecialties()` (ManyToMany به `Specialty``getAddresses()` (OneToMany به `DoctorAddress`). `DoctorAddress::toArray()` شکل `{address, telephone, map:{latitude,longitude}, name, ...}` می‌دهد. نوبت آدرس مشخصی ذخیره نمی‌کند، پس **اولین آدرس پزشک** به‌عنوان آدرس مطب استفاده می‌شود.
## مشکل / هدف
`Appointment::toArray()` گسترش یابد تا `doctor` شامل `specialties` و `address` (اولین آدرس پزشک با `address/telephone/map`) باشد — بدون شکستن مصرف‌کننده‌های فعلی.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `src/Appointment/Entity/Appointment.php` | `toArray()` — افزودن specialties + address زیر `doctor` |
| `src/Doctor/Entity/Doctor.php` | `getSpecialties()`, `getAddresses()` (موجود) |
| `src/Doctor/Entity/DoctorAddress.php` | `toArray()``{address, telephone, map, ...}` (موجود) |
| `src/Specialty/Entity/Specialty.php` | `getUuid()/getName()` (موجود) |
## وضعیت فعلی (کد واقعی)
```php
// Appointment::toArray()
'doctor' => [
'uuid' => $this->doctor->getUuid(),
'name' => $this->doctor->getName(),
],
'slot_start' => $this->slotStart,
'slot_end' => $this->slotEnd,
// ... بدون specialties / address
```
`DoctorAddress::toArray()` (مرجع شکل):
```php
'map' => ['latitude' => (string), 'longitude' => (string)],
'address' => $this->address,
'telephone' => $this->telephone,
'name' => $this->name,
```
## وظایف
### ۱. افزودن specialties و address به `doctor` در `Appointment::toArray()`
```php
$doctor = $this->doctor;
$firstAddress = $doctor->getAddresses()->first() ?: null;
// ...
'doctor' => [
'uuid' => $doctor->getUuid(),
'name' => $doctor->getName(),
'specialties' => array_map(
fn($s) => ['uuid' => $s->getUuid(), 'name' => $s->getName()],
$doctor->getSpecialties()->toArray()
),
],
'address' => $firstAddress ? $firstAddress->toArray() : null,
```
- `address` را در سطح بالای آرایه‌ی نوبت بگذار (نه داخل doctor) تا فرانت `appointmentData.address.{address,telephone,map}` را مستقیم بخواند.
- `getAddresses()->first()` ممکن است `false` برگرداند (Doctrine Collection) → با `?: null` ایمن کن.
- بقیه‌ی کلیدهای موجود (`slot_start`, `status`, ...) بدون تغییر بمانند.
### ۲. مستندسازی `docs/api/appointment.md`
شکل جدید `doctor.specialties` و `address` را در response نمونه‌ی `GET /api/v1/appointments/user``GET /api/v1/appointment/{uuid}`) اضافه کن.
## نکات مهم
- **بدون شکستن مصرف‌کننده‌ها:** فقط کلید اضافه می‌شود؛ کلیدهای فعلی دست‌نخورده. (پنل ادمین/جاهای دیگری که `toArray` نوبت را می‌خوانند نباید بشکنند.)
- تخصص ممکن است خالی باشد (آرایه‌ی خالی) و آدرس ممکن است `null` باشد (پزشک بدون آدرس) — فرانت باید این‌ها را تحمل کند (در پرامپت فرانت لحاظ شده).
- نوبت آدرس اختصاصی ذخیره نمی‌کند؛ «اولین آدرس پزشک» یک تقریب منطقی است. اگر بعداً نوبت به آدرس مشخص گره خورد، اینجا باید آن را ترجیح داد.
- `slot_start`/`slot_end` Unix‌اند (فرانت تاریخ و ساعت را از `slot_start` می‌سازد).
- پاسخ‌ها از `BaseController`؛ migration لازم نیست.
- تست: `GET /api/v1/appointments/user` برای کاربری با نوبت → آیتم باید `doctor.specialties[]` و `address.{address,telephone,map}` داشته باشد.
+15 -7
View File
@@ -216,24 +216,32 @@ Get appointment detail.
"uuid": "appt-uuid-...",
"doctor": {
"uuid": "...",
"title": "دکتر علی احمدی",
"image": "https://..."
"name": "دکتر علی احمدی",
"specialties": [
{ "uuid": "...", "name": "اورولوژی عمومی" }
]
},
"user": {
"address": {
"uuid": "...",
"real_name": "کاربر",
"mobile_number": "09..."
"name": "مطب دکتر علی احمدی",
"address": "یزد، خیابان ...",
"telephone": "035...",
"map": { "latitude": "31.8", "longitude": "54.3" },
"city": { "id": "132", "name": "یزد" },
"province": { "id": "100", "name": "یزد" }
},
"user": { "uuid": "...", "mobile": "09..." },
"slot_start": 1718438400,
"slot_end": 1718439600,
"status": "confirmed",
"note": "...",
"price": 500000,
"payment_uuid": "pay-uuid-...",
"patient_name": "...",
"patient_mobile": "...",
"created_at": 1717000000
}
}
```
> `doctor.specialties` آرایه (ممکن است خالی)؛ `address` اولین آدرس پزشک است (ممکن است `null` اگر پزشک آدرسی ندارد). `address.map.latitude/longitude` رشته یا `null`. تاریخ‌ها Unix.
### Errors
| Code | HTTP | Description |
+9 -2
View File
@@ -153,12 +153,19 @@ class Appointment
public function toArray(): array
{
$firstAddress = $this->doctor->getAddresses()->first() ?: null;
return [
'uuid' => $this->uuid,
'doctor' => [
'uuid' => $this->doctor->getUuid(),
'name' => $this->doctor->getName(),
'uuid' => $this->doctor->getUuid(),
'name' => $this->doctor->getName(),
'specialties' => array_map(
fn($s) => ['uuid' => $s->getUuid(), 'name' => $s->getName()],
$this->doctor->getSpecialties()->toArray()
),
],
'address' => $firstAddress?->toArray(),
'user' => [
'uuid' => $this->user->getUuid(),
'mobile' => $this->user->getMobileNumber(),