- 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.
8.5 KiB
8.5 KiB
تسک ۱۰: ماژول نوبتدهی
توضیح
سیستم رزرو نوبت شامل نمایش اسلاتهای خالی، رزرو نوبت، لغو نوبت، روزهای غیرقابل رزرو و لیست نوبتهای کاربر.
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
{
"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
// 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 — لغو نوبت
// 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
// 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}
{
"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)
{
"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) ذخیره میشوند