# نکات پیاده‌سازی — تسک ۱۰: ماژول نوبت‌دهی ## وضعیت‌های واقعی نوبت (از Drupal) ``` waiting_for_payment → وضعیت پیش‌فرض هنگام ثبت نوبت confirmed → بعد از پرداخت موفق auto_cancel_unpaid → لغو خودکار به دلیل عدم پرداخت cancelled_by_patient → لغو توسط بیمار cancelled_by_doctor → لغو توسط دکتر ``` ⚠️ در طراحی اولیه `pending/cancelled/completed` بود — این‌ها **اشتباه** بودند. ## تشخیص نماینده از HTTP Host (Multi-tenant) ```php // در AppointmentService.php Drupal: // نماینده از domain_name=host پیدا می‌شود private function getRepresentation(string $host): ?int { return $this->representationRepo->findByDomainName($host)?->getId(); } // در Symfony: از $request->getHost() استفاده کن $host = $request->getSchemeAndHttpHost() . '/'; // e.g. http://yasuj-nobat.localhost:3000/ $representation = $this->representationRepo->findByDomainName($host); ``` ## فیلدهای واقعی نوبت (از کد Drupal) ``` field_doctor_id → entity reference به doctor field_start_time → Unix timestamp (Asia/Tehran) field_end_time → Unix timestamp (Asia/Tehran) field_address → entity reference به doctor_address field_slot → JSON: {start_time_timestamp, end_time_timestamp, location_id, start, end, duration} field_representation → entity reference به representation field_status → string (waiting_for_payment, confirmed, ...) field_visited_at → Unix timestamp (بعد از ویزیت) field_info → یادداشت ``` ## اعتبارسنجی slot (از کد Drupal) ```php // بررسی start_time معتبر بودن (در آینده، نه گذشته) $checkStartTime = $this->isTimestampValid($startTime, 10); // 10 دقیقه حداقل $checkEndTime = $this->isTimestampValid($endTime, 10); // بررسی تداخل (conflict check) $unacceptableStatus = ['auto_cancel_unpaid', 'cancelled_by_patient', 'cancelled_by_doctor']; // اگر نوبتی برای همین doctor + slot وجود داشت که status آن در لیست بالا نبود → خطا ``` ## جلوگیری از Race Condition از database transaction + pessimistic write lock استفاده کن: ```php $this->entityManager->beginTransaction(); try { $existing = $this->repo->findConflictingAppointment( $doctorId, $startTime, $endTime, lockMode: LockMode::PESSIMISTIC_WRITE ); if ($existing) throw new SlotAlreadyTakenException(); $appointment = new Appointment(...); $this->entityManager->persist($appointment); $this->entityManager->flush(); $this->entityManager->commit(); } catch (\Exception $e) { $this->entityManager->rollback(); throw $e; } ``` ## Response کامل نوبت (از finalizedData Drupal) ```json { "id": 1, "uuid": "...", "status": "waiting_for_payment", "start_time": 1716000000, "end_time": 1716001800, "visited_at": null, "info": null, "slot": { "start_time_timestamp": 1716000000, "end_time_timestamp": 1716001800, "location_id": 42, "start": "09:00", "end": "09:30", "duration": 30 }, "doctor": { "id": 5, "uuid": "...", "name": "دکتر محمدی", "specialty": {"id": 3, "uuid": "...", "name": "متخصص قلب"} }, "address": { "id": 42, "uuid": "...", "name": "مطب شیراز", "address": "...", "phone": "071...", "map": {"latitude": 29.6, "longitude": 52.5} }, "patient": { "id": 10, "uuid": "...", "name": "علی رضایی", "mobile": "09120000000", "profile": {"id": 8, "uuid": "..."} } } ``` ## روزهای غیر قابل رزرو endpoint `GET /appointment/not-available/{doctorId}` تاریخ‌هایی را برمی‌گرداند که در آن‌ها نوبت خالی نیست: - روزهایی در Holiday جای گرفته‌اند - روزهایی که DateOverride با active=false دارند - روزهایی که WeeklySchedule آن‌ها active=false است - روزهایی که همه slot‌هایشان رزرو فعال دارند ## مجوزها ``` POST /appointment → احراز هویت‌شده GET /appointment-slots/{doctorId} → عمومی GET /appointment/not-available/{id} → عمومی GET /appointment/my-appointments/{id} → owner یا ROLE_ADMIN PATCH /appointment/{uuid}/status → ROLE_ADMIN یا دکتر مرتبط ```