Files
clinicpro/README.MD
T
hamed e88ae9bf9c feat: enhance ClinicDetailPage with dynamic tab management in EditModal
- Added initialTab prop to EditModal for setting the active tab on open.
- Updated state management in ClinicDetailPage to handle initial tab for editing.
- Refactored openEdit function to set the initial tab before opening the edit modal.
- Combined specialties, insurances, and services sections in the sidebar for better organization.
- Improved modal rendering using createPortal for better context handling.

style: increase z-index for modal overlay

- Updated the z-index of the overlay class in styles.css to ensure modals appear above other elements.

feat: implement multi-role dashboard functionality

- Created a new prompt for multi-role dashboard implementation.
- Defined roles and their access levels in the admin panel.
- Updated backend to support user role identification and context retrieval.
- Enhanced frontend to dynamically render components based on user roles.
- Added new routes and components for role-specific dashboards.

chore: add skills for admin endpoint and page creation

- Created SKILL.md files for adding admin endpoints and pages.
- Provided templates and guidelines for implementing new admin features.

chore: sync database after entity changes

- Added a new skill for syncing the database after any entity modifications.
2026-06-11 09:28:51 +03:30

783 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
# ۶. نصب وابستگی‌های 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: clinic123
```
---
## ۶. دستورات 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`