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.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# معماری — تسک ۰۹: ماژول تنظیمات نوبتدهی
|
||||
|
||||
## ساختار فایلها
|
||||
```
|
||||
src/Module/AppointmentSettings/
|
||||
├── Controller/
|
||||
│ ├── WeeklyScheduleController.php
|
||||
│ ├── DateOverrideController.php
|
||||
│ └── HolidayController.php
|
||||
├── Service/
|
||||
│ ├── WeeklyScheduleService.php
|
||||
│ ├── DateOverrideService.php
|
||||
│ └── HolidayService.php
|
||||
├── Repository/
|
||||
│ ├── WeeklyScheduleRepository.php
|
||||
│ ├── DateOverrideRepository.php
|
||||
│ └── HolidayRepository.php
|
||||
├── Entity/
|
||||
│ ├── WeeklySchedule.php
|
||||
│ ├── DateOverride.php
|
||||
│ └── Holiday.php
|
||||
└── DTO/
|
||||
├── Request/
|
||||
│ ├── CreateWeeklyScheduleRequest.php
|
||||
│ ├── CreateDateOverrideRequest.php
|
||||
│ └── CreateHolidayRequest.php
|
||||
└── Response/
|
||||
├── WeeklyScheduleResponse.php
|
||||
└── DateOverrideResponse.php
|
||||
```
|
||||
|
||||
## Entity: WeeklySchedule
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'weekly_schedules')]
|
||||
class WeeklySchedule
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
private Uuid $uuid;
|
||||
|
||||
#[ORM\OneToOne(targetEntity: Doctor::class)]
|
||||
private Doctor $doctor;
|
||||
|
||||
// هر روز هفته یک JSON: {active, slots: [{start, end, duration}]}
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $saturday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $sunday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $monday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $tuesday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $wednesday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $thursday = ['active' => false, 'slots' => []];
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $friday = ['active' => false, 'slots' => []];
|
||||
|
||||
// TimestampableTrait
|
||||
}
|
||||
```
|
||||
|
||||
## Entity: DateOverride
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'date_overrides')]
|
||||
class DateOverride
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
private Uuid $uuid;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Doctor::class)]
|
||||
private Doctor $doctor;
|
||||
|
||||
#[ORM\Column(type: 'date')]
|
||||
private \DateTimeInterface $date;
|
||||
|
||||
#[ORM\Column(type: 'boolean', default: false)]
|
||||
private bool $active;
|
||||
|
||||
#[ORM\Column(length: 200, nullable: true)]
|
||||
private ?string $reason;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $customSlots; // [{start, end, duration}]
|
||||
|
||||
// TimestampableTrait
|
||||
}
|
||||
```
|
||||
|
||||
## Entity: Holiday
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'holidays')]
|
||||
class Holiday
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
private Uuid $uuid;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Doctor::class)]
|
||||
private Doctor $doctor;
|
||||
|
||||
#[ORM\Column(type: 'date')]
|
||||
private \DateTimeInterface $startDate;
|
||||
|
||||
#[ORM\Column(type: 'date')]
|
||||
private \DateTimeInterface $endDate;
|
||||
|
||||
#[ORM\Column(length: 200, nullable: true)]
|
||||
private ?string $reason;
|
||||
|
||||
// TimestampableTrait
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# پایگاه داده — تسک ۰۹: ماژول تنظیمات نوبتدهی
|
||||
|
||||
## مهم: ساختار واقعی field_setting از DB backup
|
||||
|
||||
**تفاوت اساسی با طراحی اولیه:**
|
||||
- یک فیلد JSON به نام `field_setting` کل برنامه هفتگی را ذخیره میکند
|
||||
- ساختار: **آرایه ۷ المان** (ایندکس 0=شنبه تا 6=جمعه)
|
||||
- هر روز دو نوبت **صبح** و **عصر** دارد (نه slotهای آرایهای)
|
||||
|
||||
## جدول: weekly_schedules
|
||||
_(entity_type=appointment_settings, bundle=weekly_schedule)_
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| doctor_id | INT FK → doctors.id UNIQUE | یک رکورد بهازای هر دکتر |
|
||||
| setting | LONGTEXT NOT NULL | JSON برنامه کامل هفتگی |
|
||||
| created_at | INT NOT NULL | Unix timestamp |
|
||||
| updated_at | INT NOT NULL | Unix timestamp |
|
||||
|
||||
## ساختار واقعی JSON فیلد `setting` (از DB backup)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"morning": {
|
||||
"active": 1,
|
||||
"location_id": 48,
|
||||
"start_time": "08:00",
|
||||
"end_time": "12:00",
|
||||
"patient_limit": 10,
|
||||
"duration_per_patient": 15,
|
||||
"has_rest": true,
|
||||
"rest_interval": 60,
|
||||
"time_to_rest": 10
|
||||
},
|
||||
"evening": {
|
||||
"active": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"morning": { "active": 0 },
|
||||
"evening": {
|
||||
"active": 1,
|
||||
"location_id": 49,
|
||||
"start_time": "15:00",
|
||||
"end_time": "18:00",
|
||||
"patient_limit": 8,
|
||||
"duration_per_patient": 20,
|
||||
"has_rest": false
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
ایندکس روزها:
|
||||
| ایندکس | روز |
|
||||
|--------|-----|
|
||||
| 0 | شنبه |
|
||||
| 1 | یکشنبه |
|
||||
| 2 | دوشنبه |
|
||||
| 3 | سهشنبه |
|
||||
| 4 | چهارشنبه |
|
||||
| 5 | پنجشنبه |
|
||||
| 6 | جمعه |
|
||||
|
||||
فیلدهای هر session (morning/evening):
|
||||
| فیلد | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| active | 0/1 | آیا این نوبت فعال است |
|
||||
| location_id | int | ID آدرس مطب (→ doctor_addresses) |
|
||||
| start_time | "HH:MM" | ساعت شروع |
|
||||
| end_time | "HH:MM" | ساعت پایان |
|
||||
| patient_limit | int | حداکثر تعداد بیمار |
|
||||
| duration_per_patient | int (دقیقه) | مدت هر ویزیت |
|
||||
| has_rest | boolean | آیا استراحت دارد |
|
||||
| rest_interval | int (دقیقه) | فاصله استراحت |
|
||||
| time_to_rest | int (دقیقه) | مدت استراحت |
|
||||
|
||||
## جدول: date_overrides
|
||||
_(entity_type=appointment_settings, bundle=date_override)_
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| doctor_id | INT FK → doctors.id | دکتر |
|
||||
| date | INT NOT NULL | تاریخ (Unix timestamp) — field_date |
|
||||
| active | TINYINT(1) DEFAULT 0 | آیا کار میکند — field_active |
|
||||
| setting | LONGTEXT NULL | JSON اسلاتهای سفارشی (همان ساختار field_setting) |
|
||||
| created_at | INT NOT NULL | |
|
||||
| updated_at | INT NOT NULL | |
|
||||
|
||||
## جدول: holidays
|
||||
_(entity_type=appointment_settings, bundle=holidays)_
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| doctor_id | INT FK → doctors.id | دکتر |
|
||||
| start_date | INT NOT NULL | تاریخ شروع (Unix timestamp) |
|
||||
| end_date | INT NOT NULL | تاریخ پایان (Unix timestamp) |
|
||||
| active | TINYINT(1) DEFAULT 1 | field_active |
|
||||
| created_at | INT NOT NULL | |
|
||||
| updated_at | INT NOT NULL | |
|
||||
|
||||
## ایندکسها
|
||||
```sql
|
||||
CREATE UNIQUE INDEX idx_weekly_schedules_doctor ON weekly_schedules(doctor_id);
|
||||
CREATE INDEX idx_date_overrides_doctor_date ON date_overrides(doctor_id, date);
|
||||
CREATE INDEX idx_holidays_doctor_range ON holidays(doctor_id, start_date, end_date);
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
- **GET /appointment-settings/{uuid}** — uuid دکتر است، نه uuid schedule
|
||||
- `setting[0]` ایندکس 0=شنبه تا 6=جمعه (هفته ایرانی)
|
||||
- هر روز دقیقاً ۲ نوبت (morning و evening) دارد
|
||||
- session غیرفعال فقط `{"active": 0}` است، بقیه فیلدها ندارد
|
||||
@@ -0,0 +1,72 @@
|
||||
# نکات پیادهسازی — تسک ۰۹: ماژول تنظیمات نوبتدهی
|
||||
|
||||
## اولویتبندی تنظیمات (از Manual — بخش ۲.۸.۴)
|
||||
هنگام محاسبه اسلاتهای خالی (تسک ۱۰):
|
||||
```
|
||||
1. DateOverride (بالاترین) → اگر Override فعال برای این تاریخ وجود دارد، تعطیلی نادیده گرفته میشود
|
||||
2. Holiday → اگر تاریخ تعطیل است AND override ندارد → روز بسته است
|
||||
3. WeeklySchedule (پایین) → در صورت نبود override و تعطیلی → برنامه هفتگی
|
||||
```
|
||||
|
||||
## UUID در URL endpoint لیست override ها
|
||||
مسیر `GET /api/v1/appointment-settings/date-override/list/{uuid}`
|
||||
→ این `uuid` برابر است با UUID دکتر (نه DateOverride)
|
||||
|
||||
## ساختار واقعی هر session در weekly schedule (از Manual و API request)
|
||||
```json
|
||||
{
|
||||
"active": 1,
|
||||
"number_of_turns": 10, ← تعداد نوبت (نه patient_limit!)
|
||||
"turn_time": 10, ← مدت هر نوبت به دقیقه (نه duration_per_patient!)
|
||||
"location": { "id": 48 }, ← آدرس مطب (object، نه فقط ID)
|
||||
"start_time": "10:00",
|
||||
"end_time": "13:00"
|
||||
}
|
||||
```
|
||||
|
||||
ساختار کامل weekly schedule (7 روز — از "0"=شنبه تا "6"=جمعه):
|
||||
```json
|
||||
{
|
||||
"0": {
|
||||
"morning": { "active": 1, "number_of_turns": 10, "turn_time": 10, "location": {"id": 48}, "start_time": "10:00", "end_time": "13:00" },
|
||||
"evening": { "active": 0 }
|
||||
},
|
||||
"1": { "morning": {"active": 0}, "evening": { "active": 1, ... } },
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**مهم:** در DB backup، فیلدهای `patient_limit` و `duration_per_patient` استفاده شده بود.
|
||||
در API (کلاینت) از `number_of_turns` و `turn_time` استفاده میشود.
|
||||
در Symfony باید هر دو نام را پشتیبانی کنی یا از نامهای API استفاده کنی.
|
||||
|
||||
## مهم: ذخیرهسازی field_setting (از کد واقعی)
|
||||
```php
|
||||
// در Drupal:
|
||||
$normalized['field_setting'] = json_encode($data['setting'][0]);
|
||||
// یعنی اولین المان آرایهای که فرانت میفرستد ذخیره میشود
|
||||
|
||||
// در Symfony هم همین رویکرد:
|
||||
$weeklySchedule->setSetting(json_encode($request->getSetting()[0]));
|
||||
```
|
||||
|
||||
## GET از UUID دکتر (نه UUID schedule)
|
||||
```php
|
||||
// weeklyScheduleService.get($uuid) در Drupal:
|
||||
// 1. ابتدا دکتر با این uuid را پیدا کن → $doctorEntity
|
||||
// 2. سپس schedule با field_doctor_id = $doctorId را پیدا کن
|
||||
|
||||
// در Symfony:
|
||||
$doctor = $this->doctorRepo->findByUuid($uuid);
|
||||
$schedule = $this->scheduleRepo->findByDoctor($doctor);
|
||||
```
|
||||
|
||||
## مجوزها
|
||||
تمام endpoint های این ماژول نیاز به احراز هویت دارند:
|
||||
```
|
||||
POST/PATCH/DELETE → دکتر مرتبط (owner) یا ROLE_ADMIN
|
||||
GET → دکتر مرتبط یا ROLE_ADMIN یا منشی دکتر
|
||||
```
|
||||
|
||||
## user_flow
|
||||
جریان کامل در فایل جداگانه user_flow.md توضیح داده شده است.
|
||||
@@ -0,0 +1,66 @@
|
||||
# تسک ۰۹: ماژول تنظیمات نوبتدهی
|
||||
|
||||
## توضیح
|
||||
پیادهسازی سیستم تنظیمات نوبتدهی دکتر شامل برنامه هفتگی،
|
||||
override روزهای خاص و تعطیلات.
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| POST | `/api/v1/appointment-settings/weekly-schedule` | ایجاد برنامه هفتگی | بله (Doctor) |
|
||||
| PATCH | `/api/v1/appointment-settings/weekly-schedule/{uuid}` | ویرایش برنامه | بله |
|
||||
| GET | `/api/v1/appointment-settings/weekly-schedule/{uuid}` | دریافت برنامه | بله |
|
||||
| DELETE | `/api/v1/booking-setting/{uuid}` | حذف تنظیمات | بله |
|
||||
| GET | `/api/v1/appointment-settings/date-override/list/{uuid}` | لیست override ها | بله |
|
||||
| POST | `/api/v1/appointment-settings/date-override` | ایجاد override | بله (Doctor) |
|
||||
| PATCH | `/api/v1/appointment-settings/date-override/{uuid}` | ویرایش override | بله |
|
||||
| DELETE | `/api/v1/appointment-settings/date-override/{uuid}` | حذف override | بله |
|
||||
| GET | `/api/v1/appointment-settings/date-override/{uuid}` | دریافت override | بله |
|
||||
| POST | `/api/v1/appointment-settings/holidays` | ثبت تعطیلات | بله (Doctor) |
|
||||
| PATCH | `/api/v1/appointment-settings/holidays/{uuid}` | ویرایش تعطیلات | بله |
|
||||
|
||||
## پیشنیازها
|
||||
- تسک ۰۱، ۰۲، ۰۵ (Doctor)
|
||||
|
||||
## زمان تخمینی
|
||||
۱۰ تا ۱۲ ساعت
|
||||
|
||||
## نمونه Request
|
||||
|
||||
### POST /api/v1/appointment-settings/weekly-schedule
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "61be915b-...",
|
||||
"schedule": {
|
||||
"saturday": { "active": true, "slots": [{"start": "09:00", "end": "13:00", "duration": 30}] },
|
||||
"sunday": { "active": true, "slots": [{"start": "09:00", "end": "13:00", "duration": 30}] },
|
||||
"monday": { "active": false, "slots": [] },
|
||||
"tuesday": { "active": true, "slots": [{"start": "14:00", "end": "18:00", "duration": 20}] },
|
||||
"wednesday": { "active": false, "slots": [] },
|
||||
"thursday": { "active": true, "slots": [{"start": "09:00", "end": "12:00", "duration": 30}] },
|
||||
"friday": { "active": false, "slots": [] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/v1/appointment-settings/date-override
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "...",
|
||||
"date": "2024-03-20",
|
||||
"active": false,
|
||||
"reason": "مرخصی",
|
||||
"custom_slots": []
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/v1/appointment-settings/holidays
|
||||
```json
|
||||
{
|
||||
"doctor_uuid": "...",
|
||||
"start_date": "2024-03-20",
|
||||
"end_date": "2024-03-27",
|
||||
"reason": "نوروز"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
# جریان کاربری — تسک ۰۹: تنظیمات نوبتدهی
|
||||
|
||||
## جریان تنظیم اولیه نوبتدهی توسط دکتر
|
||||
|
||||
```
|
||||
دکتر وارد پنل میشود
|
||||
│
|
||||
▼
|
||||
POST /api/v1/appointment-settings/weekly-schedule
|
||||
{ doctor_uuid, schedule: { saturday: {...}, sunday: {...}, ... } }
|
||||
│
|
||||
└─► ذخیره برنامه هفتگی پایه
|
||||
```
|
||||
|
||||
## جریان ثبت مرخصی یا تعطیلات
|
||||
|
||||
```
|
||||
دکتر تعطیلات را ثبت میکند
|
||||
│
|
||||
▼
|
||||
POST /api/v1/appointment-settings/holidays
|
||||
{ doctor_uuid, start_date, end_date, reason }
|
||||
│
|
||||
└─► در بازه تعطیلات، هیچ نوبتی نمیتوان گرفت
|
||||
```
|
||||
|
||||
## جریان override یک روز خاص
|
||||
|
||||
```
|
||||
دکتر میخواهد یک روز خاص را سفارشی کند
|
||||
│
|
||||
├─► غیرفعال کردن یک روز:
|
||||
│ POST /date-override { date: "2024-03-15", active: false }
|
||||
│
|
||||
└─► اسلات سفارشی برای یک روز:
|
||||
POST /date-override {
|
||||
date: "2024-03-15",
|
||||
active: true,
|
||||
custom_slots: [{ start: "10:00", end: "12:00", duration: 20 }]
|
||||
}
|
||||
```
|
||||
|
||||
## الگوریتم محاسبه اسلاتهای خالی (تسک ۱۰ از این استفاده میکند)
|
||||
|
||||
```
|
||||
برای تاریخ درخواستشده:
|
||||
│
|
||||
▼
|
||||
آیا در بازه Holiday است؟
|
||||
بله → نوبت موجود نیست
|
||||
خیر │
|
||||
▼
|
||||
آیا DateOverride برای این تاریخ وجود دارد؟
|
||||
بله → active=false: نوبت موجود نیست
|
||||
active=true: از custom_slots استفاده کن
|
||||
خیر │
|
||||
▼
|
||||
از WeeklySchedule روز هفته مربوطه استفاده کن
|
||||
active=false: نوبت موجود نیست
|
||||
active=true: اسلاتهای slots را محاسبه کن
|
||||
│
|
||||
▼
|
||||
اسلاتهای رزروشده را حذف کن (از جدول appointments)
|
||||
│
|
||||
▼
|
||||
لیست اسلاتهای خالی را برگردان
|
||||
```
|
||||
Reference in New Issue
Block a user