- 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.
285 lines
8.5 KiB
Markdown
285 lines
8.5 KiB
Markdown
# تسک ۱۰: ماژول نوبتدهی
|
|
|
|
## توضیح
|
|
سیستم رزرو نوبت شامل نمایش اسلاتهای خالی، رزرو نوبت، لغو نوبت،
|
|
روزهای غیرقابل رزرو و لیست نوبتهای کاربر.
|
|
|
|
## Endpoint ها
|
|
|
|
| متد | مسیر | توضیح | نیاز به Auth |
|
|
|-----|------|-------|-------------|
|
|
| GET | `/api/v1/appointment-slots` | اسلاتهای خالی دکتر در تاریخ | خیر |
|
|
| POST | `/api/v1/appointment` | رزرو نوبت | بله |
|
|
| GET | `/api/v1/appointment/not-available/{doctorId}` | روزهای غیرقابل رزرو | خیر |
|
|
| GET | `/api/v1/appointment/my-appointments/{userId}` | نوبتهای من | بله |
|
|
| PATCH | `/api/v1/appointment/{uuid}/cancel` | لغو نوبت توسط کاربر | بله (Owner) |
|
|
| PATCH | `/api/v1/appointment/{uuid}/status` | تغییر وضعیت نوبت | بله (Doctor/Secretary/Admin) |
|
|
|
|
## پیشنیازها
|
|
- تسک ۰۱، ۰۲، ۰۵ (Doctor)، ۰۹ (تنظیمات)، ۱۵ (Payment)
|
|
|
|
## زمان تخمینی
|
|
۱۲ تا ۱۵ ساعت
|
|
|
|
---
|
|
|
|
## Status Machine نوبت
|
|
|
|
```
|
|
[ایجاد نوبت]
|
|
↓
|
|
waiting_for_payment ──→ (پرداخت موفق) ──→ reserved
|
|
↓ ↓
|
|
(لغو) ┌────────────┤
|
|
↓ │ │
|
|
cancelled_by_patient checked_in (لغو دکتر)
|
|
↓ ↓
|
|
waiting cancelled_by_doctor
|
|
↓
|
|
in_progress
|
|
↓
|
|
┌─────────────┴─────────────┐
|
|
↓ ↓
|
|
visited no_show
|
|
↓
|
|
completed
|
|
```
|
|
|
|
**وضعیتها:**
|
|
| وضعیت | توضیح | چه کسی تغییر میدهد |
|
|
|--------|-------|---------------------|
|
|
| `waiting_for_payment` | منتظر پرداخت | سیستم — بعد از رزرو |
|
|
| `reserved` | رزرو شده — پرداخت موفق | سیستم — بعد از تأیید پرداخت |
|
|
| `checked_in` | بیمار به مطب رسیده | منشی/دکتر |
|
|
| `waiting` | در صف انتظار مطب | منشی/دکتر |
|
|
| `in_progress` | ویزیت در حال انجام | منشی/دکتر |
|
|
| `visited` | ویزیت انجام شد | منشی/دکتر |
|
|
| `no_show` | بیمار نیامد | منشی/دکتر |
|
|
| `completed` | کامل شد | سیستم |
|
|
| `cancelled_by_patient` | لغو توسط بیمار | بیمار (Owner) |
|
|
| `cancelled_by_doctor` | لغو توسط دکتر | دکتر/Admin |
|
|
| `postponed` | به تعویق افتاده | دکتر/Admin |
|
|
|
|
---
|
|
|
|
## فلوی کامل رزرو + پرداخت
|
|
|
|
```
|
|
POST /api/v1/appointment
|
|
↓
|
|
1. بررسی اسلات: آیا time در آن date خالی است؟
|
|
2. بررسی holiday/date_override
|
|
3. ایجاد appointment با status=waiting_for_payment
|
|
4. بازگشت uuid نوبت به کلاینت
|
|
↓
|
|
POST /api/v1/payment (در task-15)
|
|
{ appointment_uuid: "...", payment_method: "mellat" }
|
|
↓
|
|
5. ایجاد payment با status=pending
|
|
6. دریافت payment_url از درگاه
|
|
7. redirect کاربر به درگاه
|
|
↓
|
|
[Callback از درگاه بانک]
|
|
↓
|
|
8. تأیید پرداخت → payments.status = 'received'
|
|
9. appointments.status = 'reserved'
|
|
10. واریز کمیسیون به کیف پول نماینده (اگر از دامنه نماینده)
|
|
```
|
|
|
|
**⚠ نکته:** اگر در ۳۰ دقیقه پرداخت نشود → `waiting_for_payment` به `cancelled_by_system` تغییر کند (job)
|
|
|
|
---
|
|
|
|
## GET /api/v1/appointment-slots
|
|
|
|
```
|
|
Query params:
|
|
doctor_uuid (الزامی)
|
|
date (الزامی) — فرمت: YYYY-MM-DD
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"date": "2024-03-20",
|
|
"doctor": { "uuid": "...", "name": "دکتر احمدی" },
|
|
"slots": [
|
|
{ "time": "09:00", "available": true, "duration": 30 },
|
|
{ "time": "09:30", "available": false, "duration": 30 },
|
|
{ "time": "10:00", "available": true, "duration": 30 }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**منطق محاسبه اسلاتهای خالی:**
|
|
```
|
|
1. بارگذاری weekly_schedule دکتر برای روز هفته مربوطه
|
|
2. بررسی date_override برای تاریخ مشخص
|
|
3. بررسی holiday (اگر تاریخ در بازه تعطیلی است → همه اسلاتها unavailable)
|
|
4. خواندن نوبتهای موجود با status ≠ cancelled → آن اسلاتها unavailable
|
|
5. بازگشت لیست اسلاتها با وضعیت available/unavailable
|
|
```
|
|
|
|
---
|
|
|
|
## POST /api/v1/appointment
|
|
|
|
```json
|
|
// Request
|
|
{
|
|
"doctor_uuid": "61be915b-...",
|
|
"date": "2024-03-20",
|
|
"time": "09:00",
|
|
"address_id": 39,
|
|
"insurance_type_id": null,
|
|
"notes": "درد معده دارم"
|
|
}
|
|
|
|
// Response 201
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "...",
|
|
"doctor": { "uuid": "...", "name": "دکتر احمدی" },
|
|
"date": "2024-03-20",
|
|
"time": "09:00",
|
|
"status": "waiting_for_payment",
|
|
"created_at": 1748000000
|
|
}
|
|
}
|
|
|
|
// Response 409 — اسلات گرفته شده
|
|
{
|
|
"success": false,
|
|
"errors": [{ "code": "ERR_APPOINTMENT_001", "message": "اسلات انتخابشده در دسترس نیست" }]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## PATCH /api/v1/appointment/{uuid}/cancel — لغو نوبت
|
|
|
|
```json
|
|
// Request
|
|
{ "reason": "به دلیل بیماری نمیتوانم بیایم" }
|
|
|
|
// Response 200
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "...",
|
|
"status": "cancelled_by_patient",
|
|
"refund_status": "pending"
|
|
}
|
|
}
|
|
|
|
// Response 400 — نوبت قابل لغو نیست
|
|
{
|
|
"success": false,
|
|
"errors": [{ "code": "ERR_APPOINTMENT_002", "message": "نوبت در وضعیت فعلی قابل لغو نیست" }]
|
|
}
|
|
```
|
|
|
|
**قوانین لغو:**
|
|
- فقط نوبتهای با status `waiting_for_payment` یا `reserved` قابل لغو هستند
|
|
- اگر پرداخت شده (`reserved`) → `payments.status = 'refund'` و refund شروع میشود
|
|
- لغو بعد از `checked_in` فقط توسط Admin/Doctor مجاز است
|
|
|
|
---
|
|
|
|
## PATCH /api/v1/appointment/{uuid}/status
|
|
|
|
```json
|
|
// Request (Doctor/Secretary/Admin)
|
|
{ "status": "checked_in" }
|
|
|
|
// Response 200
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "...",
|
|
"status": "checked_in",
|
|
"updated_at": 1748000000
|
|
}
|
|
}
|
|
```
|
|
|
|
**Transition های مجاز:**
|
|
```
|
|
reserved → checked_in (Doctor/Secretary)
|
|
checked_in → waiting (Doctor/Secretary)
|
|
waiting → in_progress (Doctor/Secretary)
|
|
in_progress → visited (Doctor/Secretary)
|
|
in_progress → no_show (Doctor/Secretary)
|
|
visited → completed (System/Doctor)
|
|
reserved → cancelled_by_doctor (Doctor/Admin)
|
|
reserved → postponed (Doctor/Admin)
|
|
```
|
|
|
|
---
|
|
|
|
## GET /api/v1/appointment/not-available/{doctorId}
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"not_available_dates": [
|
|
"2024-03-20",
|
|
"2024-03-21",
|
|
"2024-04-01"
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**منطق:**
|
|
- روزهایی که holiday هستند
|
|
- روزهایی که date_override با `active=false` تعریف شده
|
|
- روزهایی که همه اسلاتها پر هستند
|
|
|
|
---
|
|
|
|
## GET /api/v1/appointment/my-appointments/{userId}
|
|
|
|
```
|
|
Query params:
|
|
status (اختیاری) — فیلتر بر اساس وضعیت
|
|
page (اختیاری، پیشفرض 1)
|
|
limit (اختیاری، پیشفرض 10)
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"doctor": {
|
|
"uuid": "...",
|
|
"name": "دکتر احمدی",
|
|
"specialty": "قلب و عروق",
|
|
"img": [{ "url": "..." }]
|
|
},
|
|
"date": "2024-03-20",
|
|
"time": "09:00",
|
|
"status": "reserved",
|
|
"payment_status": "received",
|
|
"created_at": 1748000000
|
|
}
|
|
],
|
|
"meta": { "totalRecords": 12, "totalPages": 2, "currentPage": 1 }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## نکات مهم
|
|
|
|
- **Optimistic Locking:** هنگام رزرو اسلات، از Transaction + Lock استفاده شود تا race condition نباشد
|
|
- **Expiry Job:** نوبتهای `waiting_for_payment` بعد از ۳۰ دقیقه باید auto-cancel شوند (Symfony Scheduler)
|
|
- **N+1 Prevention:** در لیست نوبتها، دکتر و وضعیت پرداخت با eager loading بارگذاری شوند
|
|
- **Timestamps:** همه تاریخ/زمانها Unix timestamp (INT) ذخیره میشوند
|