808 lines
45 KiB
Markdown
808 lines
45 KiB
Markdown
# ClinicPro — مستندات کامل پروژه
|
||
|
||
> **آخرین بهروزرسانی:** ۱۴۰۵/۰۳/۲۱ | **Symfony:** 7.x | **PHP:** ≥ 8.2 | **React:** 19
|
||
|
||
---
|
||
|
||
## فهرست مطالب
|
||
|
||
1. [معرفی پروژه](#۱-معرفی-پروژه)
|
||
2. [تکنولوژی Stack](#۲-تکنولوژی-stack)
|
||
3. [راهاندازی محیط توسعه](#۳-راهاندازی-محیط-توسعه)
|
||
4. [آدرسهای مهم](#۴-آدرسهای-مهم)
|
||
5. [متغیرهای محیطی](#۵-متغیرهای-محیطی)
|
||
6. [دستورات Console](#۶-دستورات-console)
|
||
7. [امنیت و احراز هویت](#۷-امنیت-و-احراز-هویت)
|
||
8. [پایگاه داده — جداول و موجودیتها](#۸-پایگاه-داده--جداول-و-موجودیتها)
|
||
9. [API Endpoints](#۹-api-endpoints)
|
||
10. [پنل ادمین React](#۱۰-پنل-ادمین-react)
|
||
11. [Migrations](#۱۱-migrations)
|
||
12. [سرویسهای خارجی](#۱۲-سرویسهای-خارجی)
|
||
13. [ساختار دایرکتوری](#۱۳-ساختار-دایرکتوری)
|
||
|
||
---
|
||
|
||
## ۱. معرفی پروژه
|
||
|
||
**ClinicPro** سیستم جامع مدیریت کلینیک و نوبتدهی پزشکی است — مهاجرتیافته از Drupal به **Symfony 7**. شامل یک REST API کامل و یک پنل ادمین React SPA داخل Symfony.
|
||
|
||
### ویژگیهای اصلی
|
||
|
||
- احراز هویت OTP + رمز عبور + JWT
|
||
- مدیریت پزشکان با پروفایل کامل، تخصص، آدرسهای مطب
|
||
- مدیریت کلینیکها با گالری، نقشه، دعوت پزشک
|
||
- سیستم نوبتدهی با اسلات هفتگی، روزهای استثناء، تعطیلات
|
||
- درگاه پرداخت (ملت + سپ) با کیف پول و تسویهحساب
|
||
- مدیریت نمایندگان (agents) با کمیسیون
|
||
- منشی با دسترسیهای دانهای (fine-grained permissions)
|
||
- سیستم دعوتنامه پزشک به کلینیک با توکن ۷۲ ساعته
|
||
- ارسال پیامک async از طریق KaveNegar / Rangineh
|
||
- امتیاز و نظرات پزشکان
|
||
- بلاگ با مدیریت کامل
|
||
- داشبورد آمار real-time
|
||
- Swagger UI محافظتشده با HTTP Basic Auth
|
||
|
||
---
|
||
|
||
## ۲. تکنولوژی Stack
|
||
|
||
### Backend
|
||
|
||
| لایه | تکنولوژی |
|
||
| ------------- | -------------------------------- |
|
||
| Framework | Symfony 7.x |
|
||
| PHP | ≥ 8.2 |
|
||
| ORM | Doctrine ORM |
|
||
| Auth | LexikJWT + OTP |
|
||
| Queue | Symfony Messenger |
|
||
| Cache | Redis |
|
||
| Database | MariaDB 11.8 |
|
||
| API Docs | NelmioApiDocBundle + swagger-php |
|
||
| Rate Limiting | Symfony Rate Limiter |
|
||
|
||
### Frontend (Admin SPA)
|
||
|
||
| لایه | تکنولوژی |
|
||
| ------------- | --------------------- |
|
||
| Framework | React 19 + TypeScript |
|
||
| Routing | React Router DOM v7 |
|
||
| Server State | TanStack Query v5 |
|
||
| Client State | Zustand v5 (persist) |
|
||
| Forms | React Hook Form + Zod |
|
||
| Charts | Recharts |
|
||
| Maps | React Leaflet 5 |
|
||
| Icons | Heroicons v2 |
|
||
| Notifications | Sonner |
|
||
| Date | jalaali-js |
|
||
| Build | Webpack Encore 6 |
|
||
|
||
---
|
||
|
||
## ۳. راهاندازی محیط توسعه
|
||
|
||
### پیشنیازها
|
||
|
||
- [ddev](https://ddev.readthedocs.io/) نصبشده
|
||
|
||
### مراحل
|
||
|
||
```bash
|
||
# ۱. clone و وارد شدن به پروژه
|
||
git clone <repo-url> clinic-pro-symfony
|
||
cd clinic-pro-symfony
|
||
|
||
# ۲. راهاندازی ddev
|
||
ddev start
|
||
|
||
# ۳. نصب وابستگیهای PHP
|
||
ddev exec composer install
|
||
|
||
# ۴. تولید کلیدهای JWT
|
||
ddev exec php bin/console lexik:jwt:generate-keypair
|
||
|
||
# ۵. اجرای migration های پایگاه داده
|
||
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
|
||
|
||
# ۶. seed دستهبندیها (استان، شهر، تخصص، خدمات پزشک) از data/seed/*.json
|
||
ddev exec php bin/console app:seed-categories
|
||
|
||
# ۷. نصب وابستگیهای Node و بیلد فرانتاند
|
||
ddev exec yarn install
|
||
ddev exec yarn dev
|
||
|
||
# ۸. ایجاد اولین ادمین (اختیاری)
|
||
ddev exec php bin/console app:create-admin
|
||
```
|
||
|
||
### دستورات روزانه
|
||
|
||
```bash
|
||
# بیلد فرانتاند (یکبار)
|
||
ddev exec yarn dev
|
||
|
||
# حالت watch (هنگام توسعه)
|
||
ddev exec yarn watch
|
||
|
||
# پاک کردن cache بعد از تغییر backend
|
||
ddev exec php bin/console cache:clear
|
||
|
||
# اجرای queue worker
|
||
ddev exec php bin/console messenger:consume async
|
||
```
|
||
|
||
---
|
||
|
||
## ۴. آدرسهای مهم
|
||
|
||
| سرویس | آدرس |
|
||
| ---------------- | ------------------------------------ |
|
||
| **وبسایت** | https://clinic-pro.ddev.site |
|
||
| **پنل ادمین** | https://clinic-pro.ddev.site/admin |
|
||
| **Swagger UI** | https://clinic-pro.ddev.site/api/doc |
|
||
| **Health Check** | https://clinic-pro.ddev.site/health |
|
||
|
||
### ورود به Swagger UI
|
||
|
||
> آدرس: `https://clinic-pro.ddev.site/api/doc`
|
||
|
||
| فیلد | مقدار |
|
||
| -------------- | ----------- |
|
||
| **نام کاربری** | `admin` |
|
||
| **رمز عبور** | `clinic123` |
|
||
|
||
پس از ورود، برای احراز هویت API درون Swagger ابتدا از `/api/v1/user/login` توکن بگیرید، سپس دکمه **Authorize** را بزنید و `Bearer <token>` وارد کنید.
|
||
|
||
---
|
||
|
||
## ۵. متغیرهای محیطی
|
||
|
||
فایل `.env` (مقادیر پیشفرض توسعه):
|
||
|
||
```env
|
||
# ── برنامه ─────────────────────────────────────────────────────
|
||
APP_ENV=dev
|
||
APP_SECRET=clinic_pro_secret_change_in_prod
|
||
DEFAULT_URI=https://clinic-pro.ddev.site
|
||
APP_BASE_URL=https://clinic-pro.ddev.site
|
||
|
||
# ── پایگاه داده ──────────────────────────────────────────────────
|
||
DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=8.0&charset=utf8mb4"
|
||
|
||
# ── JWT ─────────────────────────────────────────────────────────
|
||
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||
JWT_PASSPHRASE=<رشته تصادفی>
|
||
|
||
# ── CORS ────────────────────────────────────────────────────────
|
||
CORS_ALLOW_ORIGIN='^https?://(clinic-pro\.ddev\.site|localhost)(:[0-9]+)?$'
|
||
ALLOWED_FRONTEND_HOSTS=clinic-pro.ddev.site,localhost
|
||
|
||
# ── صف پیام و Cache ──────────────────────────────────────────────
|
||
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages
|
||
REDIS_URL=redis://redis:6379
|
||
|
||
# ── توکنها ──────────────────────────────────────────────────────
|
||
REFRESH_TOKEN_TTL=2592000 # ۳۰ روز (ثانیه)
|
||
OTP_TTL=1200 # ۲۰ دقیقه (ثانیه)
|
||
|
||
# ── پیامک ────────────────────────────────────────────────────────
|
||
SMS_PROVIDER=kavenegar
|
||
KAVENEGAR_API_KEY=<کلید>
|
||
KAVENEGAR_SENDER=<شماره فرستنده>
|
||
RANGINEH_API_KEY=<کلید>
|
||
RANGINEH_SENDER=<شماره فرستنده>
|
||
|
||
# ── فایل آپلود ───────────────────────────────────────────────────
|
||
MAX_FILE_SIZE_BYTES=5242880 # 5MB
|
||
UPLOAD_DIR=var/uploads
|
||
|
||
# ── درگاه پرداخت ─────────────────────────────────────────────────
|
||
MELLAT_TERMINAL_ID=<شناسه>
|
||
MELLAT_USERNAME=<کاربری>
|
||
MELLAT_PASSWORD=<رمز>
|
||
SEP_TERMINAL_ID=<شناسه>
|
||
|
||
# ── Swagger UI ───────────────────────────────────────────────────
|
||
API_DOC_USERNAME=admin
|
||
API_DOC_PASSWORD=<bcrypt hash> # plain text: admin@admin
|
||
```
|
||
|
||
---
|
||
|
||
## ۶. دستورات Console
|
||
|
||
```bash
|
||
# ── Cache ────────────────────────────────────────────────────────
|
||
ddev exec php bin/console cache:clear
|
||
|
||
# ── Database ─────────────────────────────────────────────────────
|
||
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
|
||
ddev exec php bin/console doctrine:migrations:diff --no-interaction # بعد از تغییر entity
|
||
ddev exec php bin/console doctrine:migrations:status
|
||
|
||
# ── Debug ────────────────────────────────────────────────────────
|
||
ddev exec php bin/console debug:router | grep api
|
||
ddev exec php bin/console debug:container | grep -i service
|
||
|
||
# ── Queue ────────────────────────────────────────────────────────
|
||
ddev exec php bin/console messenger:consume async
|
||
|
||
# ── Test ─────────────────────────────────────────────────────────
|
||
ddev exec php bin/phpunit
|
||
ddev exec php bin/phpunit tests/SomeTest.php
|
||
|
||
# ── Static Analysis ──────────────────────────────────────────────
|
||
ddev exec php vendor/bin/phpstan analyse
|
||
|
||
# ── Frontend ─────────────────────────────────────────────────────
|
||
ddev exec yarn dev # بیلد یکبار
|
||
ddev exec yarn watch # حالت watch
|
||
ddev exec yarn build # پروداکشن
|
||
ddev exec npx tsc --noEmit # فقط type check
|
||
```
|
||
|
||
---
|
||
|
||
## ۷. امنیت و احراز هویت
|
||
|
||
### جریان احراز هویت
|
||
|
||
```
|
||
۱. POST /api/v1/user/send-code → ارسال OTP به موبایل
|
||
۲. POST /api/v1/user/verify-code → تأیید OTP → دریافت uuid
|
||
۳. POST /oauth/token → ارسال uuid → دریافت JWT + refresh token
|
||
۴. Authorization: Bearer <jwt> → همراه هر درخواست محافظتشده
|
||
```
|
||
|
||
### جریان Login با رمز عبور
|
||
|
||
```
|
||
POST /api/v1/user/login
|
||
{ "mobile_number": "09...", "password": "..." }
|
||
→ { "access_token": "...", "refresh_token": "..." }
|
||
```
|
||
|
||
### نقشهای کاربری
|
||
|
||
| نقش | توضیح | دسترسی |
|
||
| ---------------- | ------------------ | ------------------------ |
|
||
| `ROLE_USER` | پایه — همه کاربران | نوبتگیری، پروفایل |
|
||
| `ROLE_ADMIN` | مدیر کل | پنل ادمین کامل |
|
||
| `ROLE_CLINIC` | صاحب کلینیک | مدیریت کلینیک خود |
|
||
| `ROLE_DOCTOR` | پزشک | مدیریت نوبتها، پروفایل |
|
||
| `ROLE_SECRETARY` | منشی | بر اساس permissions دکتر |
|
||
|
||
### دسترسیهای منشی (JSON)
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"resources": {
|
||
"appointments": {
|
||
"view": true,
|
||
"create": true,
|
||
"cancel": false,
|
||
"update_status": true
|
||
},
|
||
"addresses": {
|
||
"view": true,
|
||
"create": false,
|
||
"update": false,
|
||
"delete": false
|
||
},
|
||
"clinic_info": { "view": true, "update": false },
|
||
"insurances": {
|
||
"view": true,
|
||
"create": false,
|
||
"update": false,
|
||
"delete": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### JWT
|
||
|
||
- مدت اعتبار access token: **۱ ساعت**
|
||
- Refresh token: **۳۰ روز** (ذخیره در Redis)
|
||
- پیلود: `{ username: mobile_number, roles: [...], iat, exp }`
|
||
- تجدید: `POST /oauth/token/refresh`
|
||
|
||
### Firewall های security.yaml
|
||
|
||
| Firewall | Pattern | توضیح |
|
||
| ------------------ | ------------------------------ | ----------------------------------- |
|
||
| `api_doc` | `^/api/doc` | HTTP Basic Auth (admin / clinic123) |
|
||
| `public_endpoints` | OTP، OAuth، endpoint های عمومی | بدون احراز هویت |
|
||
| `payment_callback` | `/api/v1/payment/callback/` | بدون احراز هویت |
|
||
| `api` | `^/(api\|oauth\|file/)` | JWT stateless |
|
||
|
||
---
|
||
|
||
## ۸. پایگاه داده — جداول و موجودیتها
|
||
|
||
### جداول اصلی
|
||
|
||
| جدول | موجودیت | توضیح |
|
||
| --------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
||
| `users` | User | uuid، mobile_number (unique)، roles (JSON)، status |
|
||
| `doctors` | Doctor | user_id (OneToOne)، degree، doctor_rate، active_doctor_appointment |
|
||
| `doctor_addresses` | DoctorAddress | doctor_id، name، latitude، longitude |
|
||
| `clinics` | Clinic | user_id (owner)، name، telephone، is_24_7، latitude، longitude، images_clinic (JSON) |
|
||
| `clinic_doctor_invitations` | ClinicDoctorInvitation | token (unique 96char hex)، status، expires_at (+72h)، mobile، uuid |
|
||
| `appointments` | Appointment | doctor_id، user_id، slot_start، slot_end، status، price |
|
||
| `weekly_schedules` | WeeklySchedule | doctor_id (OneToOne)، setting (JSON) |
|
||
| `date_overrides` | DateOverride | doctor_id، date، active، setting (JSON) |
|
||
| `holidays` | Holiday | doctor_id، start_date، end_date |
|
||
| `payments` | Payment | order_id (unique)، amount_rials، status، gateway (mellat\|sep) |
|
||
| `settlements` | Settlement | user_id، amount_rials، status، bank_account (JSON) |
|
||
| `wallet_transactions` | WalletTransaction | user_id، amount_rials، type (credit\|debit)، balance_after |
|
||
| `representations` | Representation | full_name، commission_percent، city_id |
|
||
| `doctor_secretaries` | DoctorSecretary | doctor_id، secretary_id، permissions (JSON)، active |
|
||
| `rates` | Rate | doctor_id، user_id، overall، diagnosis_accuracy، skill، behavior |
|
||
| `comments` | Comment | doctor_id، user_id، body، status (approved\|pending\|rejected) |
|
||
| `likes` | Like | comment_id، user_id |
|
||
| `blogs` | Blog | author_id، title، slug (unique)، body، status |
|
||
| `sms_templates` | SmsTemplate | name، body، provider_code، status |
|
||
| `sms_logs` | SmsLog | mobile، message، provider، success |
|
||
| `profiles` | UserProfile | blood_type، gender، insurance_id |
|
||
| `provinces` | Province | name |
|
||
| `cities` | City | name، province_id (FK) |
|
||
| `specialties` | Specialty | name، parent_id (nullable برای زیر-تخصص) |
|
||
| `doctor_services` | DoctorService | name |
|
||
| `insurances` | Insurance | name، type (basic\|supplementary) |
|
||
| `tags` | Tag | name |
|
||
|
||
### جداول رابطهای (ManyToMany)
|
||
|
||
| جدول | رابطه |
|
||
| -------------------- | ---------------------- |
|
||
| `clinic_doctors` | Clinic ↔ Doctor |
|
||
| `clinic_specialties` | Clinic ↔ Specialty |
|
||
| `clinic_services` | Clinic ↔ DoctorService |
|
||
| `clinic_insurances` | Clinic ↔ Insurance |
|
||
| `doctor_specialties` | Doctor ↔ Specialty |
|
||
| `doctor_expertise` | Doctor ↔ Category |
|
||
| `doctor_provinces` | Doctor ↔ Province |
|
||
|
||
### وابستگیهای کلیدی
|
||
|
||
```
|
||
User ──OneToOne──► Doctor ──ManyToMany──► Specialty
|
||
──OneToOne──► Representation
|
||
──OneToMany──► Appointment
|
||
──OneToMany──► Clinic (owner)
|
||
|
||
Doctor ──OneToMany──► DoctorAddress
|
||
──OneToMany──► Appointment
|
||
──ManyToMany──► Clinic
|
||
|
||
Clinic ──ManyToOne──► User (owner)
|
||
──ManyToMany──► Doctor
|
||
──OneToMany──► ClinicDoctorInvitation
|
||
```
|
||
|
||
### نکته timestamp
|
||
|
||
همه فیلدهای تاریخ (createdAt، updatedAt، slotStart، ...) به صورت **integer Unix timestamp** ذخیره میشوند — نه DateTime object.
|
||
|
||
---
|
||
|
||
## ۹. API Endpoints
|
||
|
||
### احراز هویت
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ---- | -------------------------- | ------------------ | ---------- |
|
||
| POST | `/api/v1/user/send-code` | ارسال OTP | عمومی |
|
||
| POST | `/api/v1/user/verify-code` | تأیید OTP | عمومی |
|
||
| POST | `/api/v1/user/register` | ثبتنام | عمومی |
|
||
| POST | `/api/v1/user/login` | لاگین با رمز عبور | عمومی |
|
||
| POST | `/oauth/token` | دریافت JWT | عمومی |
|
||
| POST | `/oauth/token/refresh` | تجدید توکن | عمومی |
|
||
| GET | `/oauth/userinfo` | اطلاعات کاربر جاری | احراز هویت |
|
||
| POST | `/oauth/logout` | خروج | احراز هویت |
|
||
|
||
### پزشکان
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ------ | ------------------------------------------------------------ | --------------- | ---------- |
|
||
| GET | `/api/v1/doctors` | لیست پزشکان | عمومی |
|
||
| GET | `/api/v1/doctor/{uuid}` | جزئیات پزشک | عمومی |
|
||
| POST | `/api/v1/doctor` | ایجاد پروفایل | احراز هویت |
|
||
| PATCH | `/api/v1/doctor/{uuid}` | ویرایش | احراز هویت |
|
||
| DELETE | `/api/v1/doctor/{uuid}` | حذف | احراز هویت |
|
||
| POST | `/file/upload/clinic_pro/doctor/field_image` | آپلود تصویر | احراز هویت |
|
||
| GET | `/api/v1/clinic-pro/doctor-addresses/{doctorId}` | آدرسهای مطب | عمومی |
|
||
| POST | `/api/v1/clinic-pro/doctor-address` | افزودن آدرس | احراز هویت |
|
||
| PATCH | `/api/v1/clinic-pro/doctor-address/{id}` | ویرایش آدرس | احراز هویت |
|
||
| DELETE | `/api/v1/clinic-pro/doctor-address/{id}` | حذف آدرس | احراز هویت |
|
||
| POST | `/api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid}` | ایجاد از کلینیک | احراز هویت |
|
||
|
||
### کلینیکها
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ----- | --------------------------------------------------- | -------------- | ---------- |
|
||
| GET | `/api/v1/clinics` | لیست کلینیکها | عمومی |
|
||
| GET | `/api/v1/clinic/{uuid}` | جزئیات کلینیک | عمومی |
|
||
| POST | `/api/v1/clinic` | ایجاد کلینیک | احراز هویت |
|
||
| PATCH | `/api/v1/clinic/{uuid}` | ویرایش | احراز هویت |
|
||
| GET | `/api/v1/clinic/doctor-list/{clinicUuid}` | پزشکان کلینیک | عمومی |
|
||
| POST | `/file/upload/clinic_pro/clinic/field_clinic_logo` | آپلود لوگو | احراز هویت |
|
||
| POST | `/file/upload/clinic_pro/clinic/field_image_clinic` | آپلود گالری | احراز هویت |
|
||
|
||
### دعوتنامه پزشک به کلینیک
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ------ | -------------------------------------------------- | ----------------- | ---------- |
|
||
| POST | `/api/v1/admin/clinic/{uuid}/invite-doctor` | ارسال دعوتنامه | ROLE_ADMIN |
|
||
| GET | `/api/v1/admin/clinic/{uuid}/invitations` | لیست دعوتنامهها | ROLE_ADMIN |
|
||
| POST | `/api/v1/admin/clinic/invitation/{invUuid}/resend` | ارسال مجدد پیامک | ROLE_ADMIN |
|
||
| PATCH | `/api/v1/admin/clinic/invitation/{invUuid}/status` | تغییر وضعیت | ROLE_ADMIN |
|
||
| DELETE | `/api/v1/admin/clinic/invitation/{invUuid}` | حذف | ROLE_ADMIN |
|
||
| GET | `/api/v1/clinic-invitation/{token}` | مشاهده دعوتنامه | عمومی |
|
||
| POST | `/api/v1/clinic-invitation/{token}/accept` | پذیرش توسط دکتر | عمومی |
|
||
| POST | `/api/v1/clinic-invitation/{token}/reject` | رد کردن توسط دکتر | عمومی |
|
||
|
||
> توکن ۹۶ کاراکتر hex (`bin2hex(random_bytes(48))`) — مدت اعتبار ۷۲ ساعت. پیامک async از طریق Symfony Messenger ارسال میشود.
|
||
|
||
### نوبتدهی
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ----- | ------------------------------------------ | -------------- | ---------- |
|
||
| GET | `/api/v1/appointment-slots` | اسلاتهای خالی | عمومی |
|
||
| POST | `/api/v1/appointment` | رزرو نوبت | احراز هویت |
|
||
| GET | `/api/v1/appointment/{uuid}` | جزئیات نوبت | احراز هویت |
|
||
| PATCH | `/api/v1/appointment/{uuid}/status` | تغییر وضعیت | احراز هویت |
|
||
| GET | `/api/v1/appointments/doctor/{doctorUuid}` | نوبتهای دکتر | احراز هویت |
|
||
| GET | `/api/v1/appointments/user` | نوبتهای کاربر | احراز هویت |
|
||
|
||
### تنظیمات نوبتدهی
|
||
|
||
| متد | آدرس | توضیح |
|
||
| --------------------- | ---------------------------------------------- | ------------ |
|
||
| POST/PATCH/GET | `/api/v1/appointment-settings/weekly-schedule` | برنامه هفتگی |
|
||
| POST/PATCH/DELETE/GET | `/api/v1/appointment-settings/date-override` | روز استثناء |
|
||
| POST/PATCH/DELETE/GET | `/api/v1/appointment-settings/holidays` | تعطیلات |
|
||
|
||
### پرداخت
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| -------- | ------------------------------------ | ---------------------- | ---------- |
|
||
| POST | `/api/v1/payment/appointment` | شروع پرداخت نوبت | احراز هویت |
|
||
| POST/GET | `/api/v1/payment/callback/{gateway}` | callback (mellat\|sep) | عمومی |
|
||
| GET | `/api/v1/payment/{uuid}` | وضعیت پرداخت | احراز هویت |
|
||
| POST | `/api/v1/subscription-payment` | پرداخت اشتراک | احراز هویت |
|
||
|
||
### کیف پول و تسویهحساب
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ---- | ----------------------------------- | ------------------- | ---------- |
|
||
| GET | `/api/v1/wallet/balance` | موجودی کیف پول | احراز هویت |
|
||
| GET | `/api/v1/wallet/transactions` | تاریخچه تراکنشها | احراز هویت |
|
||
| POST | `/api/v1/settlement` | درخواست تسویه | احراز هویت |
|
||
| GET | `/api/v1/settlement` | لیست تسویههای خودم | احراز هویت |
|
||
| POST | `/api/v1/settlement/{uuid}/approve` | تأیید | ROLE_ADMIN |
|
||
| POST | `/api/v1/settlement/{uuid}/reject` | رد | ROLE_ADMIN |
|
||
|
||
### امتیاز و نظرات
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ------ | ------------------------------- | -------------- | ---------- |
|
||
| POST | `/api/v1/rate` | ثبت امتیاز | احراز هویت |
|
||
| GET | `/api/v1/rate/{doctorUuid}` | میانگین امتیاز | عمومی |
|
||
| POST | `/api/v1/comment` | ثبت نظر | احراز هویت |
|
||
| GET | `/api/v1/comments/{doctorUuid}` | نظرات تأییدشده | عمومی |
|
||
| DELETE | `/api/v1/comment/{uuid}` | حذف نظر | احراز هویت |
|
||
| POST | `/api/v1/like/{commentUuid}` | لایک | احراز هویت |
|
||
|
||
### منشی
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ------ | ---------------------------------- | ------------------ | ----------- |
|
||
| POST | `/api/v1/secretary` | ایجاد منشی | ROLE_DOCTOR |
|
||
| GET | `/api/v1/secretary/{uuid}` | جزئیات | احراز هویت |
|
||
| PATCH | `/api/v1/secretary/{uuid}` | ویرایش permissions | ROLE_DOCTOR |
|
||
| DELETE | `/api/v1/secretary/{uuid}` | حذف | ROLE_DOCTOR |
|
||
| GET | `/api/v1/secretaries/{doctorUuid}` | لیست منشیهای دکتر | احراز هویت |
|
||
|
||
### نمایندگان
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| --------------------- | ------------------------------------------------- | -------------- | ---------- |
|
||
| POST/GET/PATCH/DELETE | `/api/v1/representation/{uuid?}` | مدیریت نماینده | احراز هویت |
|
||
| GET | `/api/v1/representation/{uuid}/dashboard/monthly` | آمار ماهانه | احراز هویت |
|
||
| GET | `/api/v1/representation/{uuid}/dashboard/yearly` | آمار سالانه | احراز هویت |
|
||
|
||
### پیامک
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| --------------------- | ------------------------------------------- | ---------------- | ---------- |
|
||
| POST | `/api/v1/sms/send` | ارسال مستقیم | احراز هویت |
|
||
| POST | `/api/v1/sms/send-template` | ارسال با قالب | احراز هویت |
|
||
| POST/PATCH/GET/DELETE | `/api/v1/sms/template/{uuid?}` | مدیریت قالب | احراز هویت |
|
||
| POST | `/api/v1/sms/template/{uuid}/submit` | ارسال برای تأیید | احراز هویت |
|
||
| POST | `/api/v1/admin/sms/template/{uuid}/approve` | تأیید قالب | ROLE_ADMIN |
|
||
|
||
### تخصص، خدمات، بیمه، تگ، موقعیت
|
||
|
||
```
|
||
GET /api/v1/specialties عمومی
|
||
GET /api/v1/doctor-services عمومی
|
||
GET /api/v1/insurances عمومی
|
||
GET /api/v1/tags عمومی
|
||
GET /api/v1/provinces عمومی
|
||
GET /api/v1/cities?province_id= عمومی
|
||
GET /api/v1/categorys/{bundle} عمومی (legacy — bundle: state|city|specially_doctor|doctor_services|insurance_type|supplementary_insurance|tag)
|
||
|
||
POST/PATCH/DELETE /api/v1/admin/specialty/{id?} ROLE_ADMIN
|
||
POST/PATCH/DELETE /api/v1/admin/doctor-service/{id?} ROLE_ADMIN
|
||
POST/PATCH/DELETE /api/v1/admin/insurance/{id?} ROLE_ADMIN
|
||
POST/PATCH/DELETE /api/v1/admin/tag/{id?} ROLE_ADMIN
|
||
POST/PATCH/DELETE /api/v1/admin/province/{id?} ROLE_ADMIN
|
||
POST/PATCH/DELETE /api/v1/admin/city/{id?} ROLE_ADMIN
|
||
```
|
||
|
||
### بلاگ
|
||
|
||
| متد | آدرس | توضیح | دسترسی |
|
||
| ------ | ------------------------------------------ | ---------------------- | ---------- |
|
||
| GET | `/api/v1/blogs` | لیست بلاگهای منتشرشده | عمومی |
|
||
| GET | `/api/v1/blog/{slug}` | جزئیات بلاگ | عمومی |
|
||
| POST | `/api/v1/blog` | نوشتن بلاگ | احراز هویت |
|
||
| PATCH | `/api/v1/blog/{uuid}` | ویرایش | احراز هویت |
|
||
| DELETE | `/api/v1/blog/{uuid}` | حذف | احراز هویت |
|
||
| POST | `/file/upload/clinic_pro/blog/field_image` | آپلود تصویر | احراز هویت |
|
||
|
||
### پنل ادمین API
|
||
|
||
> همه endpoint های زیر نیاز به `ROLE_ADMIN` دارند.
|
||
|
||
| آدرس | توضیح |
|
||
| ------------------------------------------ | ------------------------------------------ |
|
||
| `GET /api/v1/admin/dashboard/stats` | ۱۲ KPI (کاربران، پزشکان، نوبت، درآمد، ...) |
|
||
| `GET /api/v1/admin/dashboard/charts` | نمودار ۳۰ روزه نوبت و درآمد |
|
||
| `GET /api/v1/admin/dashboard/recent` | آخرین نوبتها، پرداختها، کاربران |
|
||
| `GET /api/v1/admin/users` | لیست کاربران (paginated) |
|
||
| `GET /api/v1/admin/users/{uuid}` | جزئیات کاربر |
|
||
| `PUT /api/v1/admin/users/{uuid}/role` | تغییر نقش |
|
||
| `POST /api/v1/admin/users/{uuid}/status` | فعال/غیرفعال |
|
||
| `DELETE /api/v1/admin/users/{uuid}` | حذف کاربر |
|
||
| `GET /api/v1/admin/doctors` | لیست پزشکان |
|
||
| `GET /api/v1/admin/clinics` | لیست کلینیکها |
|
||
| `PATCH /api/v1/admin/clinic/{uuid}/status` | تغییر وضعیت کلینیک |
|
||
| `DELETE /api/v1/admin/clinic/{uuid}` | حذف کلینیک |
|
||
| `GET /api/v1/admin/appointments` | لیست نوبتها |
|
||
| `GET /api/v1/admin/payments` | لیست پرداختها |
|
||
| `GET /api/v1/admin/settlements` | لیست تسویهها |
|
||
| `GET /api/v1/admin/representations` | لیست نمایندگان |
|
||
| `GET /api/v1/admin/secretaries` | لیست منشیها |
|
||
| `GET /api/v1/admin/rates` | لیست امتیازها |
|
||
| `GET /api/v1/admin/comments` | لیست نظرات |
|
||
| `GET /api/v1/admin/sms/logs` | لاگ پیامکها |
|
||
| `GET /api/v1/admin/sms/templates` | قالبهای پیامک |
|
||
|
||
### فرمت پاسخهای API
|
||
|
||
```json
|
||
// Single resource
|
||
{ "success": true, "data": { ... } }
|
||
|
||
// Paginated list
|
||
{ "success": true, "data": [...], "meta": { "totalRecords": N, "totalPages": N, "currentPage": N } }
|
||
|
||
// Error
|
||
{ "success": false, "data": null, "errors": [{ "code": "ERR_...", "message": "..." }] }
|
||
```
|
||
|
||
**نکته double-nested:** برخی endpoint ها پاسخ را داخل `data` اضافه میکنند:
|
||
`$this->success(['data' => $entity->toArray()])` → فرانت باید با `data?.data?.data` استخراج کند.
|
||
|
||
---
|
||
|
||
## ۱۰. پنل ادمین React
|
||
|
||
### Route های پنل
|
||
|
||
| مسیر | صفحه | توضیح |
|
||
| ------------------------------ | ------------------------ | ------------------------------------ |
|
||
| `/admin/login` | LoginPage | ورود با موبایل + رمز |
|
||
| `/admin/dashboard` | DashboardPage | آمار KPI، نمودار، فعالیتهای اخیر |
|
||
| `/admin/users` | UsersPage | لیست + جستجو + فیلتر نقش |
|
||
| `/admin/users/:uuid` | UserDetailPage | پروفایل + ویرایش نقش |
|
||
| `/admin/doctors` | DoctorsPage | لیست پزشکان |
|
||
| `/admin/doctors/new` | DoctorFormPage | ایجاد پزشک جدید |
|
||
| `/admin/doctors/:uuid` | DoctorDetailPage | پروفایل کامل + تخصص + نوبتها |
|
||
| `/admin/clinics` | ClinicsPage | لیست کلینیکها |
|
||
| `/admin/clinics/:uuid` | ClinicDetailPage | پروفایل + نقشه + گالری + دعوتنامهها |
|
||
| `/admin/appointments` | AppointmentsPage | لیست نوبتها + فیلتر |
|
||
| `/admin/appointments/:uuid` | AppointmentDetailPage | جزئیات نوبت |
|
||
| `/admin/payments` | PaymentsPage | لیست پرداختها |
|
||
| `/admin/settlements` | SettlementsPage | تسویهحساب |
|
||
| `/admin/representations` | RepresentationsPage | نمایندگان |
|
||
| `/admin/representations/:uuid` | RepresentationDetailPage | جزئیات نماینده |
|
||
| `/admin/comments` | CommentsPage | مدیریت نظرات |
|
||
| `/admin/ratings` | RatingsPage | امتیازها |
|
||
| `/admin/sms` | SmsPage | مدیریت پیامک |
|
||
| `/admin/categories` | CategoriesPage | تخصص/خدمات/بیمه/تگ |
|
||
| `/admin/blogs` | BlogsPage | لیست بلاگها |
|
||
| `/admin/blogs/new` | BlogFormPage | نوشتن بلاگ جدید |
|
||
| `/admin/secretaries` | SecretariesPage | مدیریت منشیها |
|
||
|
||
### الگوی دریافت داده
|
||
|
||
```typescript
|
||
// لیست paginated
|
||
const { data } = useQuery({
|
||
queryKey: ["resource", page, filters],
|
||
queryFn: () =>
|
||
api.get<PaginatedResponse<T>>(`/api/v1/admin/...?page=${page}`),
|
||
});
|
||
const items = data?.data ?? []; // آرایه آیتمها
|
||
const total = data?.meta?.totalRecords ?? 0;
|
||
|
||
// Single resource (double-nested — برخی endpoint ها)
|
||
const raw = data?.data;
|
||
const item = (raw as any)?.data ?? raw;
|
||
|
||
// Category API (triple-nested legacy)
|
||
const cats = data?.data?.data ?? [];
|
||
```
|
||
|
||
### ویژگیهای فنی فرانتاند
|
||
|
||
- **Auth Store** (Zustand persist): توکن در `localStorage['clinicpro-auth']` → `state.token`
|
||
- **Modal ها**: از `createPortal(modal, document.body)` استفاده میکنند تا از z-index Leaflet بگریزند
|
||
- **CSS**: کلاسهای template اختصاصی — `.card`، `.btn.primary/.ghost/.soft/.sm`، `.badge.green/.gray/.blue`، `.overlay`، `.seg`، `.skeleton`، `.empty`
|
||
- **Avatar**: OKLCH gradient با `HUES_LIST = [256, 205, 162, 295, 272]`
|
||
|
||
---
|
||
|
||
## ۱۱. Migrations
|
||
|
||
```bash
|
||
# ایجاد migration جدید بعد از تغییر entity
|
||
ddev exec php bin/console doctrine:migrations:diff --no-interaction
|
||
|
||
# اجرا
|
||
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
|
||
|
||
# وضعیت
|
||
ddev exec php bin/console doctrine:migrations:status
|
||
```
|
||
|
||
### لیست migration های موجود
|
||
|
||
| فایل | توضیح |
|
||
| ------------------------- | ---------------------------------------------- |
|
||
| Version20260609130407 | جداول پایه: users, doctors, appointments |
|
||
| Version20260609131304 | پرداخت، کیف پول |
|
||
| Version20260609131553 | امتیاز، نظرات |
|
||
| Version20260609132009 | نمایندگان |
|
||
| Version20260609132708 | منشی |
|
||
| Version20260609133121 | کلینیک |
|
||
| Version20260609133334 | پیامک |
|
||
| Version20260609133546 | بلاگ |
|
||
| Version20260609134112 | تخصص، بیمه، تگ |
|
||
| Version20260609134741 | استان، شهر |
|
||
| Version20260609135126 | پروفایل، آدرس مطب |
|
||
| Version20260609135514 | برنامه هفتگی |
|
||
| Version20260609135704 | روز استثناء، تعطیلات |
|
||
| Version20260609135923 | ManyToMany: clinic_doctors, clinic_specialties |
|
||
| Version20260609140223 | ایندکسهای اضافی |
|
||
| Version20260609140423 | doctor_insurances |
|
||
| Version20260610062539 | فیلدهای اضافی clinic |
|
||
| Version20260610175105 | فیلد is_active برای clinic |
|
||
| **Version20260610183655** | **جدول clinic_doctor_invitations** |
|
||
|
||
---
|
||
|
||
## ۱۲. سرویسهای خارجی
|
||
|
||
### پیامک
|
||
|
||
**KaveNegar** (پیشفرض)
|
||
|
||
- API: `https://api.kavenegar.com/v1/{KEY}/sms/send.json`
|
||
- پشتیبانی از: send (متن آزاد) + sendTemplate (قالب)
|
||
|
||
**Rangineh**
|
||
|
||
- API: `https://rest.payamresan.com/api/v1/send`
|
||
- پشتیبانی از: send + sendTemplate
|
||
|
||
**ارسال async:**
|
||
|
||
```php
|
||
$this->smsService->dispatchAsync($mobile, $message);
|
||
// → Symfony Messenger → Redis → SendSmsHandler
|
||
```
|
||
|
||
### درگاه پرداخت
|
||
|
||
**بانک ملت (Mellat)**
|
||
|
||
- SOAP WebService: `https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl`
|
||
- تأیید: ResCode=0 + SettlePayment
|
||
|
||
**سپ (SEP)**
|
||
|
||
- REST API با ترمینال ID
|
||
|
||
### Redis
|
||
|
||
- صف پیام: `redis://redis:6379/messages`
|
||
- Cache: `redis://redis:6379`
|
||
- Refresh Token: کلید `refresh_<sha256>`
|
||
|
||
---
|
||
|
||
## ۱۳. ساختار دایرکتوری
|
||
|
||
```
|
||
clinic-pro-symfony/
|
||
├── assets/
|
||
│ └── admin/ # React SPA (پنل ادمین)
|
||
│ ├── App.tsx # React Router routes
|
||
│ ├── pages/ # یک فایل به ازای هر صفحه
|
||
│ ├── components/
|
||
│ │ ├── layout/ # AdminLayout, Sidebar, Topbar
|
||
│ │ └── ui/ # DataTable, Modal, ConfirmDialog, Pagination, ...
|
||
│ ├── lib/
|
||
│ │ ├── api.ts # fetch wrapper (JWT از localStorage)
|
||
│ │ └── utils.ts # formatRial, formatDate, formatDateTime, formatNumber
|
||
│ ├── stores/
|
||
│ │ ├── authStore.ts # Zustand — token, isAuthenticated
|
||
│ │ └── uiStore.ts # sidebar state
|
||
│ ├── types/index.ts # TypeScript interfaces
|
||
│ └── styles.css # کلاسهای template
|
||
├── config/
|
||
│ ├── packages/
|
||
│ │ ├── security.yaml # firewalls, access_control
|
||
│ │ └── lexik_jwt_authentication.yaml
|
||
│ └── services.yaml # dependency injection
|
||
├── docs/
|
||
│ └── tasks/ # مستندات فنی هر feature
|
||
├── migrations/ # Doctrine migrations
|
||
├── src/
|
||
│ ├── Admin/Controller/ # AdminApiController — همه endpoint های ادمین
|
||
│ ├── Appointment/ # نوبتدهی، زمانبندی، SlotCalculatorService
|
||
│ ├── Auth/ # احراز هویت، JWT، OTP، User entity
|
||
│ ├── Blog/ # بلاگ
|
||
│ ├── Category/ # کتگوری legacy (bundle system)
|
||
│ ├── Clinic/ # کلینیک
|
||
│ ├── ClinicInvitation/ # دعوتنامه پزشک به کلینیک
|
||
│ ├── Doctor/ # پزشک، آدرس مطب
|
||
│ ├── DoctorService/ # خدمات پزشکی
|
||
│ ├── Insurance/ # بیمه
|
||
│ ├── Location/ # استان، شهر
|
||
│ ├── Payment/ # پرداخت، درگاهها (Mellat, Sep)
|
||
│ ├── Rating/ # امتیاز، نظرات، لایک
|
||
│ ├── Representation/ # نمایندگان
|
||
│ ├── Secretary/ # منشی
|
||
│ ├── Settlement/ # تسویهحساب، کیف پول
|
||
│ ├── Sms/ # پیامک، قالب، لاگ
|
||
│ ├── Specialty/ # تخصص پزشکی
|
||
│ ├── Tag/ # تگ بلاگ
|
||
│ ├── UserProfile/ # پروفایل پزشکی کاربر
|
||
│ └── Shared/
|
||
│ ├── Controller/BaseController.php # success() / paginated() / error()
|
||
│ ├── Exception/AppException.php # domain exception با httpStatus
|
||
│ ├── Constant/ErrorCodes.php # کدهای خطا به صورت const string
|
||
│ └── EventSubscriber/ExceptionSubscriber.php
|
||
├── public/
|
||
│ └── build/ # webpack output (gitignore شده)
|
||
├── var/
|
||
│ └── uploads/ # فایلهای آپلودشده
|
||
├── .ddev/config.yaml # PHP 8.3، MariaDB 11.8، nginx-fpm
|
||
├── .env # متغیرهای پیشفرض (commit نشود .env.local)
|
||
└── webpack.config.js # Webpack Encore
|
||
```
|
||
|
||
---
|
||
|
||
> **یادآور توسعه:** تمام دستورات را با پیشوند `ddev exec` اجرا کنید.
|
||
> CSS فرانتاند از کلاسهای template اختصاصی استفاده میکند (نه Tailwind).
|
||
> بعد از هر تغییر backend: `cache:clear`
|
||
> بعد از هر تغییر entity: `migrations:diff` سپس `migrations:migrate`
|