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,103 @@
|
||||
# معماری — تسک ۰۳: ماژول پروفایل کاربر
|
||||
|
||||
## ساختار فایلها
|
||||
```
|
||||
src/Module/UserProfile/
|
||||
├── Controller/
|
||||
│ └── UserProfileController.php
|
||||
├── Service/
|
||||
│ └── UserProfileService.php
|
||||
├── Repository/
|
||||
│ └── UserProfileRepository.php
|
||||
├── Entity/
|
||||
│ └── UserProfile.php
|
||||
├── DTO/
|
||||
│ ├── Request/
|
||||
│ │ ├── CreateUserProfileRequest.php
|
||||
│ │ └── UpdateUserProfileRequest.php
|
||||
│ └── Response/
|
||||
│ └── UserProfileResponse.php
|
||||
└── Voter/
|
||||
└── UserProfileVoter.php
|
||||
```
|
||||
|
||||
## Entity: UserProfile
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'user_profiles')]
|
||||
class UserProfile
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
private Uuid $uuid;
|
||||
|
||||
#[ORM\OneToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
private User $user;
|
||||
|
||||
#[ORM\Column(length: 200, nullable: true)]
|
||||
private ?string $name;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $description;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $birthday;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $basicInsurance;
|
||||
|
||||
#[ORM\Column(length: 30, nullable: true)]
|
||||
private ?string $bloodType;
|
||||
|
||||
#[ORM\Column(length: 50, nullable: true)]
|
||||
private ?string $education;
|
||||
|
||||
#[ORM\Column(length: 100, nullable: true)]
|
||||
private ?string $fathersName;
|
||||
|
||||
#[ORM\Column(length: 10, nullable: true)]
|
||||
private ?string $gender;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $homePhone;
|
||||
|
||||
#[ORM\Column(length: 100, nullable: true)]
|
||||
private ?string $job;
|
||||
|
||||
#[ORM\Column(length: 30, nullable: true)]
|
||||
private ?string $maritalStatus;
|
||||
|
||||
#[ORM\Column(length: 50, nullable: true)]
|
||||
private ?string $supplementaryInsurance;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $workPhone;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $address;
|
||||
|
||||
// اطلاعات پزشکی پیچیده به صورت JSON
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $diseases;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $allergies;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $medications;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $surgeries;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $familyHistory;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $relatives;
|
||||
|
||||
// TimestampableTrait
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,109 @@
|
||||
# پایگاه داده — تسک ۰۳: ماژول پروفایل کاربر
|
||||
|
||||
## جدول: profiles
|
||||
_(entity_type=profile — از DB backup و config تأیید شده)_
|
||||
|
||||
| ستون | نوع | نام Drupal | توضیح |
|
||||
|------|-----|-----------|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | id | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | uuid | |
|
||||
| user_id | INT FK → users.id UNIQUE NOT NULL | uid | کاربر مرتبط |
|
||||
| label | VARCHAR(255) NULL | label | نام نمایشی پروفایل |
|
||||
| family | VARCHAR(25) NULL | field_family | نام خانوادگی (max 25) |
|
||||
| fathers_name | VARCHAR(255) NULL | field_fathers_name | نام پدر |
|
||||
| national_code | VARCHAR(10) NULL | field_national_code | کد ملی (max 10) |
|
||||
| national_code_approved | TINYINT(1) DEFAULT 0 | field_national_code_approved | کد ملی تأیید شده |
|
||||
| gender | VARCHAR(10) NULL | field_gender | `male` یا `female` |
|
||||
| date_of_birth | INT NULL | field_date_of_birth | تاریخ تولد (Unix timestamp) |
|
||||
| blood_type | VARCHAR(20) NULL | field_blood_type | گروه خونی |
|
||||
| marital_status | VARCHAR(30) NULL | field_marital_status | وضعیت تأهل |
|
||||
| education | VARCHAR(100) NULL | field_education | تحصیلات |
|
||||
| job | VARCHAR(100) NULL | field_job | شغل |
|
||||
| address | LONGTEXT NULL | field_address | آدرس |
|
||||
| home_phone | VARCHAR(30) NULL | field_home_phone | تلفن منزل |
|
||||
| work_phone | VARCHAR(30) NULL | field_work_phone | تلفن کار |
|
||||
| insurance_id | VARCHAR(50) NULL | field_insurance_id | شماره بیمه |
|
||||
| basic_insurance_id | INT FK → categories.id NULL | field_basic_insurance | entity ref → category (بیمه پایه) |
|
||||
| supplementary_insurance_id | INT FK → categories.id NULL | field_supplementary_insurance | entity ref → category (بیمه تکمیلی) |
|
||||
| other | LONGTEXT NULL | field_other | سایر اطلاعات |
|
||||
| sharing_with_user | TINYINT(1) DEFAULT 0 | field_sharing_with_user | اشتراک با کاربر دیگر |
|
||||
| description | LONGTEXT NULL | description | توضیحات (base field) |
|
||||
| created_at | INT NOT NULL | created | Unix timestamp |
|
||||
| updated_at | INT NOT NULL | changed | Unix timestamp |
|
||||
|
||||
## مقادیر field_gender (profile — از config)
|
||||
```
|
||||
male → آقا
|
||||
female → خانم
|
||||
```
|
||||
|
||||
## مقادیر field_blood_type (از config)
|
||||
```
|
||||
a_positive → A+ a_negative → A-
|
||||
b_positive → B+ b_negative → B-
|
||||
o_positive → O+ o_negative → O-
|
||||
ab_positive → AB+ ab_negative → AB-
|
||||
```
|
||||
|
||||
## مقادیر field_education (از Manual)
|
||||
```
|
||||
diploma → دیپلم
|
||||
postgraduate_diploma → فوق دیپلم
|
||||
bachelor_s_degree → لیسانس
|
||||
master_s_degree → فوق لیسانس
|
||||
doctorate → دکترا
|
||||
```
|
||||
|
||||
## ساختار field_other (JSON — از Manual و نمونه Request واقعی)
|
||||
|
||||
```json
|
||||
{
|
||||
"disease": [
|
||||
{ "id": 1, "name": "فشار خون", "status": "false" },
|
||||
{ "id": 2, "name": "دیابت", "status": "false" }
|
||||
],
|
||||
"allergies": [
|
||||
{ "substance": "پنیسیلین", "reaction": "کهیر", "severity": "شدید" }
|
||||
],
|
||||
"medications": [
|
||||
{ "name": "لورازپام", "dose": "1mg", "frequency": "شبها قبل خواب" }
|
||||
],
|
||||
"surgeries": [
|
||||
{ "type": "آپاندکتومی", "year": 2015, "hospital": "بیمارستان امام خمینی" }
|
||||
],
|
||||
"family_history": [
|
||||
{ "relation": "پدر", "disease": "فشار خون" }
|
||||
],
|
||||
"relatives": [
|
||||
{
|
||||
"name": "علی",
|
||||
"relation": "پسر عمو",
|
||||
"contact": { "phone": "+989121234567", "email": "...", "address": "..." }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## ایندکسها
|
||||
```sql
|
||||
CREATE UNIQUE INDEX idx_profiles_user ON profiles(user_id);
|
||||
CREATE INDEX idx_profiles_national_code ON profiles(national_code);
|
||||
```
|
||||
|
||||
## رابطه
|
||||
- `profiles.user_id` → `users.id` (OneToOne, CASCADE DELETE)
|
||||
- `profiles.basic_insurance_id` → `categories.id`
|
||||
- `profiles.supplementary_insurance_id` → `categories.id`
|
||||
|
||||
## نمونه داده واقعی از DB backup
|
||||
```
|
||||
id=1, uid=10, label='hamed', status=1
|
||||
id=3, uid=32, label='hamed', status=1
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
- `field_family` VARCHAR(25) است — نام خانوادگی کوتاه ذخیره میشود
|
||||
- `field_national_code` VARCHAR(10) است — کد ملی ۱۰ رقمی
|
||||
- `date_of_birth` از نوع timestamp (INT) است، نه DATE
|
||||
- `basic_insurance` و `supplementary_insurance` entity reference به جدول categories هستند
|
||||
- `field_sharing_with_user` احتمالاً برای اشتراک پروفایل با دکتر/منشی است
|
||||
@@ -0,0 +1,67 @@
|
||||
# نکات پیادهسازی — تسک ۰۳: ماژول پروفایل کاربر
|
||||
|
||||
## جریان بعد از ذخیره پروفایل
|
||||
طبق مستندات Drupal، بعد از ذخیره پروفایل باید:
|
||||
1. `POST /oauth/token` با refresh_token اجرا شود (دریافت access_token جدید)
|
||||
2. `GET /oauth/userinfo` اجرا شود
|
||||
|
||||
در Symfony این جریان در سمت **کلاینت** انجام میشود، نه سرور.
|
||||
→ در پاسخ POST /api/v1/user-profile، token های بهروز شده نیز برگردان:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"profile": { "uuid": "...", "name": "..." },
|
||||
"access_token": "eyJ...",
|
||||
"refresh_token": "eyJ..."
|
||||
},
|
||||
"message": "پروفایل با موفقیت ذخیره شد"
|
||||
}
|
||||
```
|
||||
|
||||
## اعتبارسنجی blood_type
|
||||
مقادیر مجاز:
|
||||
```php
|
||||
#[Assert\Choice(choices: [
|
||||
'a_positive', 'a_negative',
|
||||
'b_positive', 'b_negative',
|
||||
'ab_positive', 'ab_negative',
|
||||
'o_positive', 'o_negative'
|
||||
])]
|
||||
```
|
||||
|
||||
## اعتبارسنجی gender
|
||||
```php
|
||||
#[Assert\Choice(choices: ['male', 'female', 'other'])]
|
||||
```
|
||||
|
||||
## اعتبارسنجی education
|
||||
```php
|
||||
#[Assert\Choice(choices: [
|
||||
'primary', 'secondary', 'diploma',
|
||||
'associate', 'bachelor', 'master',
|
||||
'postgraduate_diploma', 'doctorate'
|
||||
])]
|
||||
```
|
||||
|
||||
## Partial Update (PATCH)
|
||||
endpoint PATCH باید فقط فیلدهایی که ارسال شده را آپدیت کند.
|
||||
→ از `$request->request->has('field')` یا DTO با nullable fields استفاده کن.
|
||||
→ مثال: اگر فقط `diseases` در body باشد، بقیه فیلدها تغییر نکنند.
|
||||
|
||||
## مجوزها
|
||||
```
|
||||
POST /api/v1/user-profile → کاربر احراز هویتشده (برای خودش)
|
||||
GET /api/v1/user-profile/{uuid} → owner یا ROLE_ADMIN یا ROLE_DOCTOR (دکتر مرتبط)
|
||||
PATCH /api/v1/user-profile/{uuid} → owner یا ROLE_ADMIN
|
||||
DELETE /api/v1/user-profile/{uuid} → فقط ROLE_ADMIN
|
||||
```
|
||||
|
||||
## نکته UUID در URL
|
||||
در Drupal از UUID واقعی در URL استفاده میشد.
|
||||
در Symfony نیز همان رویکرد حفظ میشود.
|
||||
ParamConverter میتواند UUID را به Entity تبدیل کند:
|
||||
```php
|
||||
#[Route('/api/v1/user-profile/{uuid}', methods: ['GET'])]
|
||||
public function get(UserProfile $userProfile): Response
|
||||
// Doctrine ParamConverter به طور خودکار uuid را به UserProfile تبدیل میکند
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
# تسک ۰۳: ماژول پروفایل کاربر
|
||||
|
||||
## توضیح
|
||||
پیادهسازی CRUD پروفایل پزشکی کاربر شامل اطلاعات شخصی،
|
||||
سابقه بیماری، آلرژیها، داروها، عملهای جراحی، سابقه خانوادگی و بستگان.
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| POST | `/api/v1/user-profile` | ایجاد پروفایل | بله |
|
||||
| GET | `/api/v1/user-profile/{uuid}` | دریافت پروفایل | بله |
|
||||
| PATCH | `/api/v1/user-profile/{uuid}` | ویرایش پروفایل | بله (Owner/Admin) |
|
||||
| DELETE | `/api/v1/user-profile/{uuid}` | حذف پروفایل | بله (Admin) |
|
||||
|
||||
## پیشنیازها
|
||||
- تسک ۰۱ و ۰۲ کامل شده باشند
|
||||
|
||||
## خروجیهای مورد انتظار
|
||||
- [ ] Entity پروفایل با تمام فیلدها
|
||||
- [ ] پشتیبانی از JSON column برای دادههای پزشکی پیچیده
|
||||
- [ ] بعد از ذخیره پروفایل: refresh token و get userinfo اجرا میشود
|
||||
- [ ] فقط owner یا admin میتواند پروفایل را ببیند/ویرایش کند
|
||||
|
||||
## زمان تخمینی
|
||||
۸ تا ۱۰ ساعت
|
||||
|
||||
## نمونه Request
|
||||
|
||||
### POST /api/v1/user-profile
|
||||
```json
|
||||
{
|
||||
"name": "علی رضایی",
|
||||
"description": [{ "value": "متن توضیحات", "format": "basic_html" }],
|
||||
"birthday": "1370-05-15",
|
||||
"basic_insurance": [251],
|
||||
"blood_type": "ab_negative",
|
||||
"education": "postgraduate_diploma",
|
||||
"fathers_name": "محمد",
|
||||
"gender": "male",
|
||||
"home_phone": "07433332178",
|
||||
"job": "مهندس",
|
||||
"marital_status": "married",
|
||||
"supplementary_insurance": "308",
|
||||
"work_phone": "07433332178",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"other": [{
|
||||
"disease": [{ "id": 1, "name": "فشار خون", "status": "true" }],
|
||||
"allergies": [{ "substance": "پنیسیلین", "reaction": "کهیر", "severity": "شدید" }],
|
||||
"medications": [{ "name": "آتنولول", "dose": "50mg", "frequency": "صبحها" }],
|
||||
"surgeries": [{ "type": "آپاندکتومی", "year": 2015, "hospital": "بیمارستان امام خمینی" }],
|
||||
"family_history": [{ "relation": "پدر", "disease": "فشار خون" }],
|
||||
"relatives": [{
|
||||
"name": "سارا",
|
||||
"relation": "خواهر",
|
||||
"contact": { "phone": "09121234567", "email": "sara@example.com", "address": "تهران" }
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user