- 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.
123 lines
4.5 KiB
Markdown
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 یا دکتر مرتبط
|
|
```
|