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:
hamed
2026-06-09 22:00:34 +03:30
commit de1a78a235
222 changed files with 36388 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
# معماری — تسک ۰۵: ماژول دکتر
## ساختار فایل‌ها
```
src/Module/Doctor/
├── Controller/
│ ├── DoctorController.php ← CRUD دکتر + لیست + جستجو
│ ├── DoctorImageController.php ← آپلود تصویر
│ └── DoctorAddressController.php ← CRUD آدرس مطب
├── Service/
│ ├── DoctorService.php
│ └── DoctorAddressService.php
├── Repository/
│ ├── DoctorRepository.php
│ └── DoctorAddressRepository.php
├── Entity/
│ ├── Doctor.php
│ └── DoctorAddress.php
├── DTO/
│ ├── Request/
│ │ ├── CreateDoctorRequest.php
│ │ ├── UpdateDoctorRequest.php
│ │ ├── DoctorFilterRequest.php
│ │ ├── CreateDoctorAddressRequest.php
│ │ └── UpdateDoctorAddressRequest.php
│ └── Response/
│ ├── DoctorResponse.php ← view کامل با آمار
│ ├── DoctorListItemResponse.php ← لیست/جستجو
│ └── DoctorAddressResponse.php
└── Voter/
└── DoctorVoter.php
```
## Entity: Doctor (بر اساس فیلدهای واقعی Drupal)
```php
#[ORM\Entity]
#[ORM\Table(name: 'doctors')]
class Doctor
{
#[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)]
private User $user; // uid در Drupal
#[ORM\Column(length: 100)]
private string $name; // field_name
#[ORM\Column(length: 10, nullable: true)]
private ?string $gender; // field_gender
#[ORM\Column(length: 50)]
private string $medicalSystemCode; // field_doctor_id (شماره نظام پزشکی)
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $activityTime; // field_activity_time (timestamp سال شروع)
#[ORM\Column(length: 100, nullable: true)]
private ?string $degree; // field_degree
#[ORM\Column(type: 'text', nullable: true)]
private ?string $info; // field_info
#[ORM\Column(length: 255, nullable: true)]
private ?string $imagePath; // field_image (media)
#[ORM\Column(type: 'decimal', precision: 3, scale: 1, options: ['default' => 3.5])]
private float $doctorRate = 3.5; // field_doctor_rate
#[ORM\Column(type: 'decimal', precision: 5, scale: 1, options: ['default' => 60])]
private float $doctorRatePercentage = 60; // field_doctor_rate_percentage
#[ORM\Column(type: 'boolean', options: ['default' => true])]
private bool $activeDoctorAppointment = true; // field_active_doctor_appointmen
#[ORM\ManyToOne(targetEntity: Representation::class)]
#[ORM\JoinColumn(nullable: true)]
private ?Representation $representation; // field_representation (multi-tenant)
// ManyToMany relations (field_specialty, field_doctor_services, field_state, field_city)
#[ORM\ManyToMany(targetEntity: Category::class)]
#[ORM\JoinTable(name: 'doctor_specialties')]
private Collection $specialties; // field_specialty (چند مقداری)
#[ORM\ManyToMany(targetEntity: Category::class)]
#[ORM\JoinTable(name: 'doctor_services')]
private Collection $services; // field_doctor_services (چند مقداری)
#[ORM\ManyToMany(targetEntity: Category::class)]
#[ORM\JoinTable(name: 'doctor_states')]
private Collection $states; // field_state
#[ORM\ManyToMany(targetEntity: Category::class)]
#[ORM\JoinTable(name: 'doctor_cities')]
private Collection $cities; // field_city
#[ORM\OneToMany(targetEntity: DoctorAddress::class, mappedBy: 'doctor')]
private Collection $addresses;
// TimestampableTrait
}
```
## Entity: DoctorAddress (بر اساس فیلدهای واقعی Drupal)
```php
#[ORM\Entity]
#[ORM\Table(name: 'doctor_addresses')]
class DoctorAddress
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private int $id;
#[ORM\Column(type: UuidType::NAME, unique: true)]
private Uuid $uuid;
#[ORM\ManyToOne(targetEntity: Doctor::class, inversedBy: 'addresses')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Doctor $doctor; // field_doctor
#[ORM\Column(length: 100, nullable: true)]
private ?string $name; // field_name (نام مطب/شعبه)
#[ORM\Column(type: 'text', nullable: true)]
private ?string $address; // field_address
#[ORM\Column(length: 20, nullable: true)]
private ?string $telephone; // field_telephone (نه phone!)
#[ORM\Column(type: 'decimal', precision: 10, scale: 8, nullable: true)]
private ?float $latitude; // field_latitude
#[ORM\Column(type: 'decimal', precision: 11, scale: 8, nullable: true)]
private ?float $longitude; // field_longitude
// TimestampableTrait
}
```
## Response کامل دکتر (finalizeData)
```json
{
"id": 5, "uuid": "...",
"name": "دکتر محمدی",
"gender": "male",
"experience": 12,
"activity_time": 1388534400,
"medical_system_code": "12345",
"detail": "متخصص قلب و عروق...",
"degree": "دکترای تخصصی",
"specialties": [{"id": 3, "uuid": "...", "name": "قلب و عروق"}],
"img": ["https://..."],
"expertise": [{"id": 10, "uuid": "...", "name": "اکوکاردیوگرافی"}],
"satisfaction": 87.5,
"point": 4.4,
"free_turn": "اولین نوبت آزاد: دوشنبه ۱۲ اردیبهشت ساعت ۱۰:۳۰",
"hours_of_work": ["شنبه", "یکشنبه", "دوشنبه"],
"address": [...],
"average_rate": {"average_stars": 4.3, "total_rates": 87},
"state": [{"id": 2, "uuid": "...", "name": "فارس"}],
"city": [{"id": 5, "uuid": "...", "name": "شیراز"}]
}
```
## منطق create دکتر (از DoctorService.php)
۱. اگر `doctor_mobile_number` داده شد:
- کاربر با این موبایل را پیدا کن
- اگر نقش `doctor` ندارد → 403
- اگر قبلاً profile دکتر دارد → 403
- اگر کاربر وجود ندارد → کاربر جدید با نقش doctor بساز
۲. اگر `doctor_mobile_number` نداده شد → کاربر جاری استفاده شود
۳. اعتبارسنجی همه فیلدهای اجباری
۴. ایجاد entity
+160
View File
@@ -0,0 +1,160 @@
# پایگاه داده — تسک ۰۵: ماژول دکتر
## جدول: doctors
_(entity_type=clinic_pro, bundle=doctor — از 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 | کاربر صاحب پروفایل |
| name | VARCHAR(255) NOT NULL | field_name | نام دکتر |
| gender | VARCHAR(10) NULL | field_gender | `woman` یا `man` |
| medical_system_code | VARCHAR(25) NULL | field_doctor_id | شماره نظام پزشکی (max 25) |
| mobile_number | VARCHAR(15) NULL | field_doctor_mobile_number | شماره موبایل دکتر (max 15) |
| activity_time | INT NULL | field_activity_time | timestamp سال شروع فعالیت |
| degree | VARCHAR(30) NULL | field_degree | مدرک (مقادیر زیر) |
| info | LONGTEXT NULL | field_info | بیوگرافی |
| image_path | VARCHAR(255) NULL | field_image | تصویر (media) |
| doctor_rate | FLOAT NULL | field_doctor_rate | امتیاز ستاره‌ای (default: 3.5) |
| doctor_rate_percentage | FLOAT NULL | field_doctor_rate_percentage | درصد رضایت (default: 60) |
| active_doctor_appointment | TINYINT(1) DEFAULT 1 | field_active_doctor_appointmen | نوبت‌دهی فعال (بدون حرف 't') |
| representation_id | INT FK → representations.id NULL | field_representation | entity ref → clinic_pro |
| created_at | INT NOT NULL | created | Unix timestamp |
| updated_at | INT NOT NULL | changed | Unix timestamp |
## مقادیر field_degree (از config)
```
expert → کارشناس
general → پزشک عمومی
specialist → پزشک متخصص
subspecialistplus → پزشک فوق تخصص
```
## مقادیر field_gender (از config)
```
woman → زن
man → مرد
```
## جدول: doctor_specialties (ManyToMany pivot)
| ستون | نوع | توضیح |
|------|-----|-------|
| doctor_id | INT FK → doctors.id | |
| category_id | INT FK → categories.id | |
| PRIMARY KEY (doctor_id, category_id) | | field_specialty → cardinality=2 (حداکثر ۲ تخصص) |
## جدول: doctor_services (ManyToMany pivot)
| ستون | نوع | توضیح |
|------|-----|-------|
| doctor_id | INT FK → doctors.id | |
| category_id | INT FK → categories.id | |
| PRIMARY KEY (doctor_id, category_id) | | field_doctor_services → cardinality=8 (حداکثر ۸ سرویس) |
## جدول: doctor_states (ManyToMany pivot)
| ستون | نوع | توضیح |
|------|-----|-------|
| doctor_id | INT FK → doctors.id | |
| category_id | INT FK → categories.id | |
| PRIMARY KEY (doctor_id, category_id) | | field_state → cardinality=1 |
## جدول: doctor_cities (ManyToMany pivot)
| ستون | نوع | توضیح |
|------|-----|-------|
| doctor_id | INT FK → doctors.id | |
| category_id | INT FK → categories.id | |
| PRIMARY KEY (doctor_id, category_id) | | field_city → cardinality=1 |
## جدول: doctor_addresses
_(entity_type=clinic_pro, bundle=doctors_addresses — از DB backup)_
| ستون | نوع | نام Drupal | توضیح |
|------|-----|-----------|-------|
| id | INT UNSIGNED AUTO_INCREMENT PK | id | |
| uuid | CHAR(36) UNIQUE NOT NULL | uuid | |
| doctor_id | INT FK → doctors.id CASCADE | field_doctor | دکتر |
| name | VARCHAR(255) NULL | field_name | نام مطب/آدرس |
| address | LONGTEXT NULL | field_address | آدرس کامل |
| telephone | VARCHAR(50) NULL | field_telephone | تلفن مطب |
| latitude | FLOAT NULL | field_latitude | عرض جغرافیایی |
| longitude | FLOAT NULL | field_longitude | طول جغرافیایی |
| created_at | INT NOT NULL | created | Unix timestamp |
| updated_at | INT NOT NULL | changed | Unix timestamp |
## جدول: 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 | توضیحات |
| address | LONGTEXT NULL | field_address | آدرس |
| telephone | VARCHAR(50) NULL | field_telephone | تلفن |
| 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 | |
| city_id | INT FK → categories.id NULL | field_city | |
| state_id | INT FK → categories.id NULL | field_state | |
| representation_id | INT FK → representations.id NULL | field_agent | نماینده کلینیک |
| created_at | INT NOT NULL | created | |
| updated_at | INT NOT NULL | changed | |
جداول pivot مرتبط با clinic:
- `clinic_doctors` (clinic_id, doctor_id) → field_doctors (cardinality نامحدود)
- `clinic_specialties` (clinic_id, category_id) → field_clinic_specialty
- `clinic_services` (clinic_id, category_id) → field_doctor_services
- `clinic_insurances` (clinic_id, category_id) → field_insurance
## جدول: doctor_secretaries
_(entity_type=clinic_pro, bundle=doctor_secretary — از 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 | |
| doctor_id | INT FK → doctors.id NOT NULL | field_doctor | entity ref → clinic_pro/doctor |
| secretary_id | INT FK → users.id NOT NULL | field_secretary | entity ref → user (منشی) |
| telephone | VARCHAR(50) NULL | field_telephone | تلفن |
| permission | LONGTEXT NULL | field_permission | مجوزها (JSON) |
| active | TINYINT(1) DEFAULT 1 | field_active | فعال/غیرفعال |
| created_at | INT NOT NULL | created | |
| updated_at | INT NOT NULL | changed | |
## ایندکس‌ها
```sql
CREATE UNIQUE INDEX idx_doctors_user ON doctors(user_id);
CREATE INDEX idx_doctors_representation ON doctors(representation_id);
CREATE INDEX idx_doctors_active ON doctors(active_doctor_appointment);
CREATE INDEX idx_doctors_rate ON doctors(doctor_rate DESC);
CREATE INDEX idx_doctor_addresses_doctor ON doctor_addresses(doctor_id);
CREATE INDEX idx_clinics_user ON clinics(user_id);
CREATE UNIQUE INDEX idx_doctor_secretaries_doctor_secretary ON doctor_secretaries(doctor_id, secretary_id);
```
## نمونه داده واقعی از DB (clinic_pro table)
```
id=18, bundle='doctor'
id=22, bundle='doctor'
id=23, bundle='clinic'
id=24, bundle='clinic'
id=27, bundle='doctor_secretary'
id=29, bundle='doctor'
id=38, bundle='doctors_addresses'
id=41, bundle='representation'
```
## روابط کامل clinic_pro entity
- `doctors``users` (ManyToOne, UNIQUE → OneToOne)
- `doctors``representations` (ManyToOne)
- `doctors``categories` (ManyToMany: specialties, services, states, cities)
- `doctor_addresses``doctors` (ManyToOne, CASCADE)
- `clinics``users` (ManyToOne)
- `clinics``doctors` (ManyToMany)
- `clinics``categories` (ManyToMany)
- `doctor_secretaries``doctors` (ManyToOne)
- `doctor_secretaries``users` as secretary (ManyToOne)
@@ -0,0 +1,64 @@
# نکات پیاده‌سازی — تسک ۰۵: ماژول دکتر
## فیلتر لیست دکترها
لیست دکترها باید فیلترهای زیر را پشتیبانی کند:
```php
// DoctorRepository.php
public function findFiltered(DoctorFilterRequest $filter): array
{
$qb = $this->createQueryBuilder('d')
->join('d.user', 'u');
if ($filter->specialty) {
$qb->andWhere('d.specialty = :specialty')
->setParameter('specialty', $filter->specialty);
}
if ($filter->city) {
$qb->join('d.addresses', 'a')
->andWhere('a.city = :city')
->setParameter('city', $filter->city);
}
if ($filter->name) {
$qb->andWhere('u.firstName LIKE :name OR u.lastName LIKE :name')
->setParameter('name', '%'.$filter->name.'%');
}
if ($filter->insurance) {
$qb->andWhere('JSON_CONTAINS(d.insurances, :ins) = 1')
->setParameter('ins', json_encode([$filter->insurance]));
}
return $qb->getQuery()->getResult();
}
```
## آپدیت میانگین امتیاز
وقتی یک rating جدید ثبت می‌شود (تسک ۱۲)، average_rating را آپدیت کن:
```php
// در RatingService (تسک ۱۲)
$this->em->createQuery(
'UPDATE Doctor d SET d.averageRating = (
SELECT AVG(r.score) FROM Rating r WHERE r.doctor = d
), d.reviewCount = (
SELECT COUNT(r.id) FROM Rating r WHERE r.doctor = d
) WHERE d.id = :id'
)->setParameter('id', $doctor->getId())->execute();
```
## آدرس مطب
- یک دکتر می‌تواند چندین آدرس مطب داشته باشد
- `{doctorId}` در endpoint لیست آدرس‌ها، UUID دکتر است (نه ID)
- دکتر می‌تواند آدرس‌های خودش را ویرایش/حذف کند
## مجوزها
```
POST /api/v1/doctor → ROLE_ADMIN
PATCH /api/v1/doctor/{uuid} → owner (دکتر خودش) یا ROLE_ADMIN
GET /api/v1/doctor/{uuid} → عمومی
GET /api/v1/doctors → عمومی
POST doctor-address → دکتر احراز هویت‌شده (برای خودش)
PATCH doctor-address/{id} → owner یا ROLE_ADMIN
DELETE doctor-address/{id} → owner یا ROLE_ADMIN
GET doctor-address/{id} → عمومی
GET doctor-addresses/{doctorId} → عمومی
```
+178
View File
@@ -0,0 +1,178 @@
# تسک ۰۵: ماژول دکتر
## توضیح
پیاده‌سازی مدیریت پروفایل دکترها، لیست دکترها با فیلتر،
آپلود تصویر پروفایل و مدیریت آدرس‌های مطب.
## Endpoint ها (واقعی از Drupal)
| متد | مسیر | توضیح | نیاز به Auth |
|-----|------|-------|-------------|
| POST | `/api/v1/doctor` | ایجاد پروفایل دکتر | بله |
| PATCH | `/api/v1/doctor/{uuid}` | ویرایش پروفایل دکتر | بله (Owner/Admin) |
| DELETE | `/api/v1/doctor/{uuid}` | حذف دکتر | بله (Admin) |
| GET | `/api/v1/doctor/{uuid}` | دریافت پروفایل کامل دکتر | خیر |
| GET | `/api/v1/doctors` | لیست دکترها با فیلتر | خیر |
| POST | `/file/upload/clinic_pro/doctor/field_image` | آپلود تصویر پروفایل دکتر | بله |
| GET | `/api/v1/clinic/doctor-list/{clinic_uuid}` | لیست دکترهای یک کلینیک | خیر |
| POST | `/api/v1/clinic-pro/doctor-address` | ایجاد آدرس مطب | بله |
| PATCH | `/api/v1/clinic-pro/doctor-address/{id}` | ویرایش آدرس | بله |
| DELETE | `/api/v1/clinic-pro/doctor-address/{id}` | حذف آدرس | بله |
| GET | `/api/v1/clinic-pro/doctor-address/{id}` | دریافت یک آدرس | بله |
| GET | `/api/v1/clinic-pro/doctor-addresses/{doctorId}` | لیست آدرس‌های دکتر | خیر |
## پیش‌نیازها
- تسک ۰۱، ۰۲، ۰۸ (Categories برای تخصص و سرویس‌ها)
## زمان تخمینی
۸ تا ۱۰ ساعت
---
## نمونه واقعی Response — GET /api/v1/doctor/{uuid}
```json
{
"id": "29",
"uuid": "61be915b-595a-42e5-bca5-f80d22f4f14a",
"name": "single doctor",
"gender": "woman",
"experience": 21,
"activity_time": "1107808200",
"medical_system_code": "121212121212",
"detail": "test",
"degree": "specialist",
"specialties": [
{
"uuid": "d60a269d-f7d7-4589-8eab-451e6f740d40",
"id": "603",
"name": "داخلی عمومی",
"parent": "602"
}
],
"img": [
{
"url": "https://domain.com/sites/default/files/doctors/2025-11/image.png",
"fid": "98",
"filename": "image.png",
"filemime": "image/png",
"filesize": 173665
}
],
"expertise": [
{ "uuid": "...", "id": "1601", "name": "معاینه و تشخیص پزشک متخصص" },
{ "uuid": "...", "id": "1602", "name": "ویزیت تخصصی" }
],
"satisfaction": "60",
"point": "3.5",
"free_turn": "اولین نوبت آزاد: سه‌شنبه 19 خرداد ساعت 15:00",
"hours_of_work": "از شنبه تا چهارشنبه از ساعت 08:00 تا 18:00",
"address": [
{
"id": "39",
"uuid": "7b759d2a-af8a-4730-8eb0-e77dcd3a724e",
"name": "مطب اصلی",
"map": { "latitude": "53.121212", "longitude": "57.121212" },
"address": "آدرس کامل مطب",
"telephone": "09120671756"
}
],
"average_rate": { "total_rates": null },
"state": [{ "uuid": "...", "id": "23", "name": "کهگیلویه و بویراحمد" }],
"city": [{ "uuid": "...", "id": "123", "name": "یاسوج", "parent": "23" }]
}
```
> **توجه فیلدها:**
> - `img` (نه `image` یا `images`!) — آرایه با url/fid/filename/filemime/filesize
> - `specialties` → تخصص اصلی + والد
> - `expertise` → همان doctor_services
> - `satisfaction` و `point` به صورت **string** برگردانده می‌شوند
> - `free_turn` → متن محاسبه‌شده از برنامه هفتگی (مثلاً "نوبت آزادی موجود نیست")
> - `hours_of_work` → متن محاسبه‌شده از برنامه هفتگی
> - `average_rate.total_rates` → می‌تواند null باشد
> - `experience` → محاسبه‌شده از `activity_time` (Unix timestamp شروع فعالیت)
---
## فیلترهای GET /api/v1/doctors
| پارامتر | نوع | الزامی | مثال |
|---------|-----|--------|------|
| `state` | string | بله | `31` |
| `city` | string | بله | `62` |
| `specialty` | string | خیر | `503` |
| `gender` | string | خیر | `man` یا `woman` |
| `degree` | string | خیر | `general`, `specialist`, `expert`, `subspecialistplus` |
| `name` | string | خیر | `علی` |
| `active` | array | خیر | `0` یا `1` |
| `page` | string | خیر | `1` |
| `limit` | string | خیر | `10` |
| `sort` | string | خیر | `ASC` یا `DESC` |
## نمونه Response لیست دکترها
```json
{
"data": [
{
"id": "29",
"uuid": "...",
"name": "single doctor",
"gender": "woman",
"degree": "specialist",
"img": [{ "url": "...", "fid": "98", "filename": "image.png", "filemime": "image/png", "filesize": 173665 }],
"specialties": [{ "uuid": "...", "id": "603", "name": "داخلی عمومی", "parent": "602" }],
"satisfaction": "60",
"point": "3.5",
"free_turn": "اولین نوبت آزاد: سه‌شنبه 19 خرداد ساعت 15:00",
"hours_of_work": "از شنبه تا چهارشنبه از ساعت 08:00 تا 18:00",
"active": true
}
],
"page": {
"totalRecords": 7,
"totalPages": 1,
"currentPage": 1
}
}
```
---
## PATCH /api/v1/doctor/{uuid} — فیلدهای قابل ویرایش
```json
{
"title": "نام دکتر",
"doctor_services": ["اکوکاردیوگرافی", "ویزیت تخصصی"]
}
```
---
## آپلود تصویر دکتر — POST /file/upload/clinic_pro/doctor/field_image
```
Headers:
Content-Type: application/octet-stream
Content-Disposition: file; filename="doctor.png"
X-CSRF-Token: {token}
Authorization: Bearer {token}
Body: binary file content
Response → { fid, uuid, ... } که در PATCH doctor استفاده می‌شود
```
---
## نمونه واقعی Response — GET /api/v1/clinic-pro/doctor-address/{id}
```json
{
"id": "39",
"uuid": "7b759d2a-af8a-4730-8eb0-e77dcd3a724e",
"name": "مطب اصلی",
"map": {
"latitude": "53.121212",
"longitude": "57.121212"
},
"address": "آذربايجان غربي، مياندوآب، خيابان ۱۵ خرداد، برج ماندگار",
"telephone": "09120671756"
}
```