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,69 @@
|
||||
# معماری — تسک ۰۶: ماژول کلینیک
|
||||
|
||||
## ساختار فایلها
|
||||
```
|
||||
src/Module/Clinic/
|
||||
├── Controller/
|
||||
│ ├── ClinicController.php ← CRUD + لیست
|
||||
│ └── ClinicImageController.php ← آپلود تصویر و لوگو
|
||||
├── Service/
|
||||
│ └── ClinicService.php
|
||||
├── Repository/
|
||||
│ └── ClinicRepository.php
|
||||
├── Entity/
|
||||
│ ├── Clinic.php
|
||||
│ └── ClinicDoctor.php ← رابطه کلینیک-دکتر
|
||||
├── DTO/
|
||||
│ ├── Request/
|
||||
│ │ ├── CreateClinicRequest.php
|
||||
│ │ └── UpdateClinicRequest.php
|
||||
│ └── Response/
|
||||
│ ├── ClinicResponse.php
|
||||
│ └── ClinicDoctorListResponse.php
|
||||
└── Voter/
|
||||
└── ClinicVoter.php
|
||||
```
|
||||
|
||||
## Entity: Clinic
|
||||
```php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'clinics')]
|
||||
class Clinic
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
private Uuid $uuid;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
private User $owner;
|
||||
|
||||
#[ORM\Column(length: 200)]
|
||||
private string $name;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $description;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $phone;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $address;
|
||||
|
||||
#[ORM\Column(length: 100, nullable: true)]
|
||||
private ?string $city;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $imagePath; // تصویر اصلی
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $logoPath; // لوگو
|
||||
|
||||
#[ORM\ManyToMany(targetEntity: Doctor::class)]
|
||||
#[ORM\JoinTable(name: 'clinic_doctors')]
|
||||
private Collection $doctors;
|
||||
|
||||
// TimestampableTrait
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
# پایگاه داده — تسک ۰۶: ماژول کلینیک
|
||||
|
||||
## جدول: clinics
|
||||
_(entity_type=clinic_pro, bundle=clinic — از config تأیید شده)_
|
||||
|
||||
| ستون | نوع | نام Drupal | توضیح |
|
||||
|------|-----|-----------|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | id | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | uuid | |
|
||||
| user_id | INT FK → users.id NOT NULL | uid | مالک کلینیک |
|
||||
| name | VARCHAR(255) NULL | field_name | نام کلینیک |
|
||||
| info | LONGTEXT NULL | field_info | توضیحات (نه description!) |
|
||||
| address | LONGTEXT NULL | field_address | آدرس |
|
||||
| telephone | VARCHAR(50) NULL | field_telephone | تلفن (نه phone!) |
|
||||
| is_24_7 | TINYINT(1) DEFAULT 0 | field_24_7 | باز ۲۴/۷ |
|
||||
| working_days | VARCHAR(255) NULL | field_working_days | روزهای کاری (متن آزاد، مثال: 'شنبه تا سه شنبه ساعت ۱۲:۲۰') |
|
||||
| latitude | FLOAT NULL | field_latitude | |
|
||||
| longitude | FLOAT NULL | field_longitude | |
|
||||
| logo_id | INT FK → files.id NULL | field_clinic_logo | لوگو کلینیک (image, cardinality=1) |
|
||||
| city_id | INT FK → categories.id NULL | field_city | شهر (entity ref → category/city) |
|
||||
| state_id | INT FK → categories.id NULL | field_state | استان (entity ref → category/state) |
|
||||
| representation_id | INT FK → representations.id NULL | field_agent | نماینده کلینیک (entity ref → user — در Drupal به user اشاره دارد، در Symfony به representation) |
|
||||
| created_at | INT NOT NULL | created | Unix timestamp |
|
||||
| updated_at | INT NOT NULL | changed | Unix timestamp |
|
||||
|
||||
## جدول: clinic_doctors (ManyToMany)
|
||||
| ستون | نوع | نام Drupal | توضیح |
|
||||
|------|-----|-----------|-------|
|
||||
| clinic_id | INT FK → clinics.id CASCADE | | |
|
||||
| doctor_id | INT FK → doctors.id CASCADE | field_doctors | cardinality نامحدود |
|
||||
|
||||
## جدول: clinic_specialties (ManyToMany)
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| clinic_id | INT FK → clinics.id CASCADE | |
|
||||
| category_id | INT FK → categories.id | field_clinic_specialty |
|
||||
|
||||
## جدول: clinic_services (ManyToMany)
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| clinic_id | INT FK → clinics.id CASCADE | |
|
||||
| category_id | INT FK → categories.id | field_doctor_services |
|
||||
|
||||
## جدول: clinic_insurances (ManyToMany)
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| clinic_id | INT FK → clinics.id CASCADE | |
|
||||
| category_id | INT FK → categories.id | field_insurance (insurance_type یا supplementary_insurance bundle) |
|
||||
|
||||
## جدول: clinic_images (تصاویر اضافی)
|
||||
| ستون | نوع | نام Drupal | توضیح |
|
||||
|------|-----|-----------|-------|
|
||||
| id | INT AUTO_INCREMENT PK | | |
|
||||
| clinic_id | INT FK → clinics.id CASCADE | | |
|
||||
| file_id | INT FK → files.id | field_image_clinic | حداکثر ۵ تصویر (cardinality=5) |
|
||||
| sort_order | INT DEFAULT 0 | delta | ترتیب |
|
||||
|
||||
## ایندکسها
|
||||
```sql
|
||||
CREATE INDEX idx_clinics_owner ON clinics(user_id);
|
||||
CREATE INDEX idx_clinics_city ON clinics(city_id);
|
||||
CREATE INDEX idx_clinics_state ON clinics(state_id);
|
||||
CREATE INDEX idx_clinics_representation ON clinics(representation_id);
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
- `field_agent` در Drupal به user اشاره دارد (cardinality=-1 نامحدود) — در Symfony سادهتر شده و فقط یک representation دارد
|
||||
- `field_working_days` متن آزاد است، مثل 'شنبه تا سهشنبه ۸ تا ۱۲'، نه فرمت ساختاریافته
|
||||
- `field_telephone` نه `phone` — اسم فیلد از Drupal config گرفته شده
|
||||
@@ -0,0 +1,24 @@
|
||||
# نکات پیادهسازی — تسک ۰۶: ماژول کلینیک
|
||||
|
||||
## لیست دکترهای کلینیک
|
||||
endpoint `GET /api/v1/clinic/doctor-list/{uuid}` لیست دکترهایی که
|
||||
به این کلینیک تعلق دارند را برمیگرداند. اطلاعات دکتر شامل:
|
||||
- نام، تخصص، تصویر، میانگین امتیاز
|
||||
|
||||
## آپلود دو نوع تصویر
|
||||
کلینیک دو تصویر دارد:
|
||||
- `image`: تصویر اصلی/배너 کلینیک (حداکثر ۵MB)
|
||||
- `logo`: لوگوی کلینیک (حداکثر ۲MB، ترجیحاً مربعی)
|
||||
|
||||
هر دو در مسیر `public/uploads/clinics/` ذخیره میشوند.
|
||||
|
||||
## مجوزها
|
||||
```
|
||||
POST /api/v1/clinic → ROLE_ADMIN
|
||||
GET /api/v1/clinic/{uuid} → عمومی
|
||||
PATCH /api/v1/clinic/{uuid} → owner یا ROLE_ADMIN
|
||||
GET /api/v1/clinics → عمومی
|
||||
GET clinic/doctor-list → عمومی
|
||||
POST clinic/image → owner یا ROLE_ADMIN
|
||||
POST clinic/logo → owner یا ROLE_ADMIN
|
||||
```
|
||||
@@ -0,0 +1,165 @@
|
||||
# تسک ۰۶: ماژول کلینیک
|
||||
|
||||
## توضیح
|
||||
پیادهسازی مدیریت کلینیکها، لیست کلینیکها، لیست دکترهای هر کلینیک
|
||||
و آپلود تصویر و لوگوی کلینیک.
|
||||
|
||||
## Endpoint ها (واقعی از Drupal)
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| POST | `/api/v1/clinic` | ایجاد کلینیک | بله |
|
||||
| GET | `/api/v1/clinic/{uuid}` | دریافت اطلاعات کامل کلینیک | خیر |
|
||||
| PATCH | `/api/v1/clinic/{uuid}` | ویرایش کلینیک | بله (Owner/Admin) |
|
||||
| GET | `/api/v1/clinics` | لیست کلینیکها با فیلتر | خیر |
|
||||
| GET | `/api/v1/clinic/doctor-list/{clinic_uuid}` | لیست دکترهای کلینیک | خیر |
|
||||
| POST | `/file/upload/clinic_pro/clinic/field_image_clinic` | آپلود تصویر گالری کلینیک | بله |
|
||||
| POST | `/file/upload/clinic_pro/clinic/field_clinic_logo` | آپلود لوگوی کلینیک | بله |
|
||||
|
||||
> **⚠ مسیر آپلود واقعی:** `/file/upload/clinic_pro/clinic/field_image_clinic` (نه `/api/v1/clinic/image`)
|
||||
|
||||
## پیشنیازها
|
||||
- تسک ۰۱، ۰۲، ۰۵ (Doctor)، ۰۸ (Categories)
|
||||
|
||||
## زمان تخمینی
|
||||
۶ تا ۸ ساعت
|
||||
|
||||
---
|
||||
|
||||
## فیلترهای GET /api/v1/clinics
|
||||
|
||||
| پارامتر | نوع | الزامی | مثال |
|
||||
|---------|-----|--------|------|
|
||||
| `state` | string | خیر | `13` |
|
||||
| `city` | string | خیر | `32` |
|
||||
| `specialty` | string | خیر | `511` |
|
||||
| `page` | string | بله | `1` |
|
||||
| `limit` | string | بله | `10` |
|
||||
| `sort` | string | خیر | `DESC` |
|
||||
|
||||
---
|
||||
|
||||
## نمونه واقعی Response — GET /api/v1/clinic/{uuid}
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "23",
|
||||
"uuid": "e4550163-5a88-4f67-b07e-cd6063738598",
|
||||
"title": "clinic 1",
|
||||
"images_clinic": [
|
||||
{
|
||||
"url": "https://domain.com/sites/default/files/2025-11/image.png",
|
||||
"fid": "91",
|
||||
"filename": "image.png",
|
||||
"filemime": "image/png",
|
||||
"filesize": 34314
|
||||
}
|
||||
],
|
||||
"clinic_logo": [
|
||||
{
|
||||
"url": "https://domain.com/sites/default/files/clinics/logo/2025-11/logo.png",
|
||||
"fid": "96",
|
||||
"filename": "logo.png",
|
||||
"filemime": "image/png",
|
||||
"filesize": 39281
|
||||
}
|
||||
],
|
||||
"phone_number": "۰۶۱-۳۳۹۱۶۵۸۹",
|
||||
"caption": "توضیحات درباره کلینیک...",
|
||||
"list_bime": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"id": "1559",
|
||||
"name": "بیمه آتیه سازان حافظ",
|
||||
"logo": [{ "url": "...", "fid": "5", "filename": "hafez-insurance.png", "filemime": "image/png", "filesize": 16100 }]
|
||||
}
|
||||
],
|
||||
"specialties": [
|
||||
{ "uuid": "...", "id": "610", "name": "روماتولوژی", "parent": "602" }
|
||||
],
|
||||
"services": [
|
||||
{ "uuid": "...", "id": "1602", "name": "ویزیت تخصصی" }
|
||||
],
|
||||
"clinic_specialty": [
|
||||
{ "uuid": "...", "id": "610", "name": "روماتولوژی", "parent": "602" }
|
||||
],
|
||||
"doctors": 3,
|
||||
"doctor_list": null,
|
||||
"city": [{ "uuid": "...", "id": "130", "name": "بندرعباس", "parent": "29" }],
|
||||
"state": [{ "uuid": "...", "id": "29", "name": "هرمزگان" }],
|
||||
"location": "بندرعباس: رسالت شمالی- میدان صادقیه",
|
||||
"map": { "latitude": "27.200632975404", "longitude": "56.356043815613" },
|
||||
"24_7": true,
|
||||
"field_working_days": "شنبه تا سه شنبه ساعت ۱۲:۲۰"
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠ نکات فیلدهای واقعی Response:**
|
||||
> - **`phone_number`** (نه `telephone`!) — شماره تماس
|
||||
> - **`caption`** (نه `info`!) — توضیحات کلینیک
|
||||
> - **`images_clinic`** (نه `images`!) — تصاویر گالری با url/fid/filename/filemime/filesize
|
||||
> - **`clinic_logo`** (نه `logo`!) — لوگو با url/fid/filename/filemime/filesize
|
||||
> - **`list_bime`** (نه `insurances`!) — بیمههای کلینیک، هر آیتم دارای `logo` نیز هست
|
||||
> - **`field_working_days`** — روزهای کاری (string آزاد)
|
||||
> - **`24_7`** — boolean
|
||||
> - **`doctors`** — تعداد دکترها (integer)
|
||||
> - **`services`** و **`clinic_specialty`** هر دو در response هستند
|
||||
|
||||
---
|
||||
|
||||
## فیلدهای PATCH /api/v1/clinic/{uuid}
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "نام کلینیک",
|
||||
"address": "آدرس",
|
||||
"telephone": "شماره تلفن",
|
||||
"latitude": "27.2",
|
||||
"longitude": "56.3",
|
||||
"insurance": [300, 301],
|
||||
"info": "توضیحات",
|
||||
"doctor_services": [575, 576],
|
||||
"working_days": "شنبه تا سهشنبه",
|
||||
"24_7": 1,
|
||||
"image_clinic": [91, 92],
|
||||
"clinic_logo": [96]
|
||||
}
|
||||
```
|
||||
|
||||
## فیلدهای POST /api/v1/clinic (ایجاد)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "نام کلینیک",
|
||||
"state": [1],
|
||||
"city": [32],
|
||||
"address": "آدرس",
|
||||
"telephone": "شماره تلفن",
|
||||
"latitude": "50.21",
|
||||
"longitude": "57.212",
|
||||
"insurance": [300, 301],
|
||||
"info": "توضیحات",
|
||||
"doctor_services": [575, 576],
|
||||
"working_days": "شنبه تا سهشنبه",
|
||||
"24_7": 1,
|
||||
"image_clinic": [19],
|
||||
"clinic_logo": [20]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## آپلود تصویر/لوگوی کلینیک
|
||||
|
||||
```
|
||||
POST /file/upload/clinic_pro/clinic/field_image_clinic
|
||||
POST /file/upload/clinic_pro/clinic/field_clinic_logo
|
||||
|
||||
Headers:
|
||||
Content-Type: application/octet-stream
|
||||
Content-Disposition: file; filename="clinic.png"
|
||||
X-CSRF-Token: {token}
|
||||
Authorization: Bearer {token}
|
||||
Body: binary file content
|
||||
Response → { fid, ... } که در image_clinic یا clinic_logo استفاده میشود
|
||||
```
|
||||
Reference in New Issue
Block a user