Files
clinicpro/docs/tasks/task-10-appointment/implementation_notes.md
T
hamed de1a78a235 feat: Implement SMS sending functionality with KavehNegar and Rangineh providers
- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
2026-06-09 22:00:34 +03:30

4.5 KiB

نکات پیاده‌سازی — تسک ۱۰: ماژول نوبت‌دهی

وضعیت‌های واقعی نوبت (از Drupal)

waiting_for_payment   → وضعیت پیش‌فرض هنگام ثبت نوبت
confirmed             → بعد از پرداخت موفق
auto_cancel_unpaid    → لغو خودکار به دلیل عدم پرداخت
cancelled_by_patient  → لغو توسط بیمار
cancelled_by_doctor   → لغو توسط دکتر

⚠️ در طراحی اولیه pending/cancelled/completed بود — این‌ها اشتباه بودند.

تشخیص نماینده از HTTP Host (Multi-tenant)

// در 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)

// بررسی 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 استفاده کن:

$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)

{
  "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 یا دکتر مرتبط