251 lines
11 KiB
Markdown
251 lines
11 KiB
Markdown
# رفع فیلدهای free_turn و hours_of_work در API لیست پزشکان
|
||
|
||
## زمینه
|
||
|
||
endpoint `GET /api/v1/doctors` و `GET /api/v1/doctor/{uuid}` در پاسخ خود دو فیلد `free_turn` و `hours_of_work` دارند که همیشه مقدار ثابت «نوبت آزادی موجود نیست» و «برنامه کاری تنظیم نشده» برمیگردانند — حتی برای پزشکانی که برنامه هفتگی (`weekly_schedules`) ست کردهاند. همچنین `activeDoctorAppointment` (فیلد `active`) باید با وجود یا نبود برنامه هفتگی هماهنگ باشد.
|
||
|
||
## مشکل / هدف
|
||
|
||
- `free_turn`: باید نزدیکترین روز کاری پزشک را نشان دهد (مثلاً «شنبه ۹:۰۰–۱۳:۰۰»)؛ اگر برنامهای نداشت «نوبت آزادی موجود نیست»
|
||
- `hours_of_work`: باید ساعتهای کاری روزهای فعال را خلاصه کند (مثلاً «شنبه تا چهارشنبه ۹–۱۳ و ۱۴–۱۸»)؛ اگر برنامه نداشت «برنامه کاری تنظیم نشده»
|
||
- `active` (`activeDoctorAppointment`): اگر پزشک برنامه هفتگی نداشته باشد یا همه روزها `sessions: []` باشند، باید `false` برگردد — نوبتدهی غیرفعال
|
||
|
||
## فایلهای مرتبط
|
||
|
||
| فایل | نقش |
|
||
|------|-----|
|
||
| `src/Doctor/Entity/Doctor.php` | متدهای `toListArray()` و `toDetailArray()` — مقادیر hardcoded اینجاست |
|
||
| `src/Doctor/Repository/DoctorRepository.php` | `findWithFilters()` — JOIN با weekly_schedules ندارد |
|
||
| `src/Doctor/Controller/DoctorController.php` | `list()` (خط ۲۳۳) و `show()` — از toListArray/toDetailArray استفاده میکنند |
|
||
| `src/Appointment/Entity/WeeklySchedule.php` | Entity برنامه هفتگی — فیلد `setting` آرایه ۷ عنصری (شنبه تا جمعه) |
|
||
| `src/Appointment/Repository/WeeklyScheduleRepository.php` | `findByDoctor(Doctor $doctor): ?WeeklySchedule` موجود است |
|
||
|
||
## وضعیت فعلی
|
||
|
||
### Doctor.php — toListArray() خط ۱۷۸:
|
||
```php
|
||
public function toListArray(): array
|
||
{
|
||
return [
|
||
// ...
|
||
'free_turn' => 'نوبت آزادی موجود نیست', // ❌ hardcoded
|
||
'hours_of_work' => 'برنامه کاری تنظیم نشده', // ❌ hardcoded
|
||
'active' => $this->activeDoctorAppointment,
|
||
];
|
||
}
|
||
```
|
||
|
||
### Doctor.php — toDetailArray() خط ۱۹۸:
|
||
```php
|
||
'free_turn' => 'نوبت آزادی موجود نیست', // ❌ hardcoded
|
||
'hours_of_work' => 'برنامه کاری تنظیم نشده', // ❌ hardcoded
|
||
```
|
||
|
||
### ساختار setting در weekly_schedules:
|
||
آرایه ۷ عنصری (ایندکس ۰=شنبه، ۱=یکشنبه، ..., ۶=جمعه):
|
||
```json
|
||
[
|
||
{"sessions": [{"active": true, "start_time": "09:00", "end_time": "13:00", ...}, ...]},
|
||
{"sessions": [{"active": true, "start_time": "14:00", "end_time": "18:00", ...}]},
|
||
...
|
||
{"sessions": []} // جمعه — تعطیل
|
||
]
|
||
```
|
||
|
||
### DoctorRepository.findWithFilters() — JOIN ندارد:
|
||
```php
|
||
$qb = $this->createQueryBuilder('d')
|
||
->leftJoin('d.specialties', 's')
|
||
->leftJoin('d.provinces', 'pr')
|
||
->leftJoin('d.cities', 'ci')
|
||
// ❌ هیچ JOIN با weekly_schedules ندارد
|
||
```
|
||
|
||
## وظایف
|
||
|
||
### ۱. اضافه کردن متد کمکی به Doctor entity
|
||
|
||
در `src/Doctor/Entity/Doctor.php` یک متد `computeScheduleFields(?WeeklySchedule $schedule): array` اضافه کن:
|
||
|
||
```php
|
||
use App\Appointment\Entity\WeeklySchedule;
|
||
|
||
private const DAY_NAMES = ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
|
||
|
||
public function computeScheduleFields(?WeeklySchedule $schedule): array
|
||
{
|
||
if ($schedule === null) {
|
||
return [
|
||
'free_turn' => 'نوبت آزادی موجود نیست',
|
||
'hours_of_work' => 'برنامه کاری تنظیم نشده',
|
||
'has_schedule' => false,
|
||
];
|
||
}
|
||
|
||
$setting = $schedule->getSetting(); // آرایه ۷ عنصری
|
||
|
||
// محاسبه hours_of_work — ساعتهای روزهای فعال
|
||
$workDays = [];
|
||
foreach ($setting as $dayIdx => $day) {
|
||
$activeSessions = array_filter(
|
||
$day['sessions'] ?? [],
|
||
fn($s) => ($s['active'] ?? false) && !empty($s['start_time'])
|
||
);
|
||
if (!empty($activeSessions)) {
|
||
$times = array_map(fn($s) => $s['start_time'] . '–' . $s['end_time'], $activeSessions);
|
||
$workDays[] = self::DAY_NAMES[$dayIdx] . ': ' . implode(' و ', $times);
|
||
}
|
||
}
|
||
|
||
if (empty($workDays)) {
|
||
return [
|
||
'free_turn' => 'نوبت آزادی موجود نیست',
|
||
'hours_of_work' => 'برنامه کاری تنظیم نشده',
|
||
'has_schedule' => false,
|
||
];
|
||
}
|
||
|
||
// محاسبه free_turn — نزدیکترین روز کاری فعال
|
||
// روز هفته فعلی را بگیر (PHP: 0=یکشنبه...6=شنبه → تبدیل به ایندکس ایرانی)
|
||
$phpDay = (int) date('w'); // 0=Sun, 6=Sat
|
||
$iranDay = $phpDay === 0 ? 1 : ($phpDay === 6 ? 0 : $phpDay + 1); // 0=Sat, 1=Sun, ...
|
||
|
||
$freeTurn = null;
|
||
for ($i = 0; $i < 7; $i++) {
|
||
$idx = ($iranDay + $i) % 7;
|
||
$activeSessions = array_filter(
|
||
$setting[$idx]['sessions'] ?? [],
|
||
fn($s) => ($s['active'] ?? false) && !empty($s['start_time'])
|
||
);
|
||
if (!empty($activeSessions)) {
|
||
$first = array_values($activeSessions)[0];
|
||
$freeTurn = self::DAY_NAMES[$idx] . ' ' . $first['start_time'] . '–' . $first['end_time'];
|
||
break;
|
||
}
|
||
}
|
||
|
||
return [
|
||
'free_turn' => $freeTurn ?? 'نوبت آزادی موجود نیست',
|
||
'hours_of_work' => implode(' | ', $workDays),
|
||
'has_schedule' => true,
|
||
];
|
||
}
|
||
```
|
||
|
||
### ۲. آپدیت toListArray() و toDetailArray()
|
||
|
||
`toListArray()` و `toDetailArray()` باید یک `?WeeklySchedule` دریافت کنند:
|
||
|
||
```php
|
||
public function toListArray(?WeeklySchedule $schedule = null): array
|
||
{
|
||
$scheduleFields = $this->computeScheduleFields($schedule);
|
||
return [
|
||
// ... بقیه فیلدها
|
||
'free_turn' => $scheduleFields['free_turn'],
|
||
'hours_of_work' => $scheduleFields['hours_of_work'],
|
||
'active' => $this->activeDoctorAppointment && $scheduleFields['has_schedule'],
|
||
];
|
||
}
|
||
|
||
public function toDetailArray(?WeeklySchedule $schedule = null): array
|
||
{
|
||
$scheduleFields = $this->computeScheduleFields($schedule);
|
||
return [
|
||
// ... بقیه فیلدها
|
||
'free_turn' => $scheduleFields['free_turn'],
|
||
'hours_of_work' => $scheduleFields['hours_of_work'],
|
||
'active' => $this->activeDoctorAppointment && $scheduleFields['has_schedule'],
|
||
// ... address, state, city
|
||
];
|
||
}
|
||
```
|
||
|
||
### ۳. آپدیت DoctorRepository.findWithFilters()
|
||
|
||
LEFT JOIN با `weekly_schedules` اضافه کن تا schedule را یکجا load کند:
|
||
|
||
```php
|
||
$qb = $this->createQueryBuilder('d')
|
||
->leftJoin('d.specialties', 's')
|
||
->leftJoin('d.provinces', 'pr')
|
||
->leftJoin('d.cities', 'ci')
|
||
->addSelect('d') // ensure d is selected for eager loading
|
||
->distinct();
|
||
```
|
||
|
||
**نکته مهم:** از eager loading یا جداگانه query استفاده کن. چون Doctor entity OneToOne با WeeklySchedule ندارد (رابطه از طرف WeeklySchedule است)، بهترین راه این است که در Controller بعد از گرفتن لیست doctors، scheduleها را batch load کنی.
|
||
|
||
### ۴. آپدیت DoctorController — متد list()
|
||
|
||
در `src/Doctor/Controller/DoctorController.php` متد `list()` خط ۲۳۳:
|
||
|
||
```php
|
||
public function __construct(
|
||
// اضافه کن:
|
||
private readonly WeeklyScheduleRepository $scheduleRepo,
|
||
// ...
|
||
)
|
||
|
||
public function list(Request $request): JsonResponse
|
||
{
|
||
$filters = $request->query->all();
|
||
$result = $this->doctorRepo->findWithFilters($filters);
|
||
|
||
// batch load schedules برای همه doctors
|
||
$scheduleMap = [];
|
||
foreach ($this->scheduleRepo->findByDoctors($result['items']) as $schedule) {
|
||
$scheduleMap[$schedule->getDoctor()->getId()] = $schedule;
|
||
}
|
||
|
||
return $this->paginated(
|
||
array_map(
|
||
fn(Doctor $d) => $d->toListArray($scheduleMap[$d->getId()] ?? null),
|
||
$result['items']
|
||
),
|
||
$result['total'],
|
||
$result['page'],
|
||
$result['limit']
|
||
);
|
||
}
|
||
```
|
||
|
||
### ۵. اضافه کردن findByDoctors() به WeeklyScheduleRepository
|
||
|
||
```php
|
||
/**
|
||
* @param Doctor[] $doctors
|
||
* @return WeeklySchedule[]
|
||
*/
|
||
public function findByDoctors(array $doctors): array
|
||
{
|
||
if (empty($doctors)) return [];
|
||
|
||
return $this->createQueryBuilder('ws')
|
||
->where('ws.doctor IN (:doctors)')
|
||
->setParameter('doctors', $doctors)
|
||
->getQuery()
|
||
->getResult();
|
||
}
|
||
```
|
||
|
||
### ۶. آپدیت متدهای show() در DoctorController
|
||
|
||
هر جایی که `$doctor->toDetailArray()` صدا زده میشود (خطوط ۱۱۷، ۱۶۳، ۱۸۴، ۳۱۲)، schedule را پاس بده:
|
||
|
||
```php
|
||
$schedule = $this->scheduleRepo->findByDoctor($doctor);
|
||
return $this->success(['data' => $doctor->toDetailArray($schedule)]);
|
||
```
|
||
|
||
## نکات مهم
|
||
|
||
- **Doctor entity رابطه مستقیم با WeeklySchedule ندارد** — رابطه OneToOne از طرف WeeklySchedule به Doctor است؛ پس `$doctor->getSchedule()` وجود ندارد و باید از Repository بخوانی
|
||
- **Circular dependency**: `Doctor.php` نباید مستقیم WeeklyScheduleRepository inject کند — بهتر است در Controller schedule را بگیری و به متد پاس بدهی (دقیقاً همان الگوی پیشنهادی بالا)
|
||
- **active flag**: فقط وقتی هم `activeDoctorAppointment=true` هم `has_schedule=true` باشد نوبتدهی فعال است
|
||
- **batch load**: برای endpoint لیست، حتماً از `findByDoctors()` batch استفاده کن — N+1 query نساز
|
||
- **ایندکس روزها**: `setting[0]=شنبه`, `setting[1]=یکشنبه`, ..., `setting[6]=جمعه` — PHP `date('w')` را باید به این ایندکس تبدیل کنی
|
||
- **sessions خالی**: اگر `sessions: []` باشد آن روز تعطیل است — فقط `active: true` و `start_time` غیرخالی معتبر است
|
||
- **مستندات**: بعد از تغییر، `docs/api/doctor.md` را آپدیت کن (فیلدهای `free_turn`، `hours_of_work`، `active` را توضیح بده)
|
||
- هیچ migration لازم نیست — Entity تغییر ساختاری ندارد
|