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

123 lines
4.5 KiB
Markdown

# نکات پیاده‌سازی — تسک ۱۰: ماژول نوبت‌دهی
## وضعیت‌های واقعی نوبت (از 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 یا دکتر مرتبط
```