diff --git a/README.MD b/README.MD index 06270681..2061d885 100644 --- a/README.MD +++ b/README.MD @@ -48,33 +48,33 @@ ### 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 | +| لایه | تکنولوژی | +| ------------- | -------------------------------- | +| 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 | +| لایه | تکنولوژی | +| ------------- | --------------------- | +| 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 | --- @@ -131,21 +131,21 @@ 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 | +| سرویس | آدرس | +| ---------------- | ------------------------------------ | +| **وب‌سایت** | 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` | +| فیلد | مقدار | +| -------------- | ----------- | +| **نام کاربری** | `admin` | +| **رمز عبور** | `clinic123` | پس از ورود، برای احراز هویت API درون Swagger ابتدا از `/api/v1/user/login` توکن بگیرید، سپس دکمه **Authorize** را بزنید و `Bearer ` وارد کنید. @@ -201,7 +201,7 @@ SEP_TERMINAL_ID=<شناسه> # ── Swagger UI ─────────────────────────────────────────────────── API_DOC_USERNAME=admin -API_DOC_PASSWORD= # plain text: clinic123 +API_DOC_PASSWORD= # plain text: admin@admin ``` --- @@ -261,25 +261,40 @@ POST /api/v1/user/login ### نقش‌های کاربری -| نقش | توضیح | دسترسی | -|-----|--------|--------| -| `ROLE_USER` | پایه — همه کاربران | نوبت‌گیری، پروفایل | -| `ROLE_ADMIN` | مدیر کل | پنل ادمین کامل | -| `ROLE_CLINIC` | صاحب کلینیک | مدیریت کلینیک خود | -| `ROLE_DOCTOR` | پزشک | مدیریت نوبت‌ها، پروفایل | -| `ROLE_SECRETARY` | منشی | بر اساس permissions دکتر | +| نقش | توضیح | دسترسی | +| ---------------- | ------------------ | ------------------------ | +| `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 } - } + "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 + } + } } ``` @@ -292,12 +307,12 @@ POST /api/v1/user/login ### 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 | +| 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 | --- @@ -305,47 +320,47 @@ POST /api/v1/user/login ### جداول اصلی -| جدول | موجودیت | توضیح | -|------|---------|--------| -| `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 | +| جدول | موجودیت | توضیح | +| --------------------------- | ---------------------- | ------------------------------------------------------------------------------------ | +| `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 | +| جدول | رابطه | +| -------------------- | ---------------------- | +| `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 | ### وابستگی‌های کلیدی @@ -374,137 +389,137 @@ Clinic ──ManyToOne──► User (owner) ### احراز هویت -| متد | آدرس | توضیح | دسترسی | -|-----|------|--------|--------| -| 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` | خروج | احراز هویت | +| متد | آدرس | توضیح | دسترسی | +| ---- | -------------------------- | ------------------ | ---------- | +| 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/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` | آپلود گالری | احراز هویت | +| متد | آدرس | توضیح | دسترسی | +| ----- | --------------------------------------------------- | -------------- | ---------- | +| 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` | رد کردن توسط دکتر | عمومی | +| متد | آدرس | توضیح | دسترسی | +| ------ | -------------------------------------------------- | ----------------- | ---------- | +| 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` | نوبت‌های کاربر | احراز هویت | +| متد | آدرس | توضیح | دسترسی | +| ----- | ------------------------------------------ | -------------- | ---------- | +| 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/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` | پرداخت اشتراک | احراز هویت | +| متد | آدرس | توضیح | دسترسی | +| -------- | ------------------------------------ | ---------------------- | ---------- | +| 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 | +| متد | آدرس | توضیح | دسترسی | +| ---- | ----------------------------------- | ------------------- | ---------- | +| 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/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 | `/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/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 | +| متد | آدرس | توضیح | دسترسی | +| --------------------- | ------------------------------------------- | ---------------- | ---------- | +| 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 | ### تخصص، خدمات، بیمه، تگ، موقعیت @@ -527,42 +542,42 @@ 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` | آپلود تصویر | احراز هویت | +| متد | آدرس | توضیح | دسترسی | +| ------ | ------------------------------------------ | ---------------------- | ---------- | +| 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` | قالب‌های پیامک | +| آدرس | توضیح | +| ------------------------------------------ | ------------------------------------------ | +| `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 @@ -586,40 +601,41 @@ POST/PATCH/DELETE /api/v1/admin/city/{id?} ROLE_ADMIN ### 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 | مدیریت منشی‌ها | +| مسیر | صفحه | توضیح | +| ------------------------------ | ------------------------ | ------------------------------------ | +| `/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>(`/api/v1/admin/...?page=${page}`), + queryKey: ["resource", page, filters], + queryFn: () => + api.get>(`/api/v1/admin/...?page=${page}`), }); -const items = data?.data ?? []; // آرایه آیتم‌ها +const items = data?.data ?? []; // آرایه آیتم‌ها const total = data?.meta?.totalRecords ?? 0; // Single resource (double-nested — برخی endpoint ها) @@ -654,27 +670,27 @@ 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** | +| فایل | توضیح | +| ------------------------- | ---------------------------------------------- | +| 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** | --- @@ -683,14 +699,17 @@ ddev exec php bin/console doctrine:migrations:status ### پیامک **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 @@ -699,13 +718,16 @@ $this->smsService->dispatchAsync($mobile, $message); ### درگاه پرداخت **بانک ملت (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_` diff --git a/docs/api/clinic.md b/docs/api/clinic.md index 0b57aa13..cbd09f07 100644 --- a/docs/api/clinic.md +++ b/docs/api/clinic.md @@ -192,11 +192,23 @@ List clinics with pagination. "success": true, "data": [ { + "id": "3426", "uuid": "...", "name": "کلینیک الوند", + "title": "کلینیک الوند", + "phone": "02112345678", + "phone_number": "02112345678", + "logo": "/uploads/clinics/logo/...", + "clinic_logo": "/uploads/clinics/logo/...", + "images_clinic": [{ "url": "/uploads/clinics/gallery/..." }], + "doctors_count": 4, + "is_active": true, + "created_at": 1781762386, "city": "تهران", - "clinic_logo": "https://...", - "is_active": true + "state": "تهران", + "specialties": [{ "uuid": "...", "id": "7", "name": "..." }], + "24_7": false, + "field_working_days": "شنبه تا پنجشنبه ۸ تا ۲۰" } ], "meta": { @@ -207,6 +219,15 @@ List clinics with pagination. } ``` +| Field | Type | Description | +|-------|------|-------------| +| `clinic_logo` / `logo` | string\|null | Logo path (relative `/uploads/...` or absolute URL) | +| `doctors_count` | integer | Number of doctors linked to the clinic | +| `city` | string\|null | City name, resolved from the clinic's address (`DoctorAddress`) | +| `state` | string\|null | Province name, resolved from the clinic's address (`DoctorAddress`) | +| `24_7` | boolean | Open 24/7 flag | +| `field_working_days` | string\|null | Working days/hours description | + --- ## GET `/api/v1/clinic/doctor-list/{clinicUuid}` diff --git a/src/Clinic/Controller/ClinicController.php b/src/Clinic/Controller/ClinicController.php index 9833b16a..0d093328 100644 --- a/src/Clinic/Controller/ClinicController.php +++ b/src/Clinic/Controller/ClinicController.php @@ -252,8 +252,14 @@ class ClinicController extends BaseController $filters = $request->query->all(); $result = $this->clinicRepo->findWithFilters($filters); + $clinicIds = array_map(fn(Clinic $c) => $c->getId(), $result['items']); + $locations = $this->clinicRepo->findAddressLocations($clinicIds); + return $this->paginated( - array_map(fn(Clinic $c) => $c->toListArray(), $result['items']), + array_map(function (Clinic $c) use ($locations) { + $loc = $locations[$c->getId()] ?? ['city' => null, 'state' => null]; + return $c->toListArray($loc['city'], $loc['state']); + }, $result['items']), $result['total'], $result['page'], $result['limit'] diff --git a/src/Clinic/Entity/Clinic.php b/src/Clinic/Entity/Clinic.php index c49dbd34..81c28431 100644 --- a/src/Clinic/Entity/Clinic.php +++ b/src/Clinic/Entity/Clinic.php @@ -211,7 +211,7 @@ class Clinic ]; } - public function toListArray(): array + public function toListArray(?string $city = null, ?string $state = null): array { return [ 'id' => (string) $this->id, @@ -226,10 +226,13 @@ class Clinic 'doctors_count' => $this->doctors->count(), 'is_active' => $this->isActive, 'created_at' => $this->createdAt, + 'city' => $city, + 'state' => $state, 'specialties' => array_map(fn(Specialty $s) => [ 'uuid' => $s->getUuid(), 'id' => (string) $s->getId(), 'name' => $s->getName(), ], $this->specialties->toArray()), - '24_7' => $this->is247, + '24_7' => $this->is247, + 'field_working_days' => $this->workingDays, ]; } } diff --git a/src/Clinic/Repository/ClinicRepository.php b/src/Clinic/Repository/ClinicRepository.php index d5bbcff2..c7c4b4d3 100644 --- a/src/Clinic/Repository/ClinicRepository.php +++ b/src/Clinic/Repository/ClinicRepository.php @@ -83,6 +83,40 @@ class ClinicRepository extends ServiceEntityRepository ]; } + /** + * City/province names for each clinic, taken from the clinic's address + * (DoctorAddress.clinicId). Returns [clinicId => ['city' => ?string, 'state' => ?string]]. + * + * @param int[] $clinicIds + * @return array + */ + public function findAddressLocations(array $clinicIds): array + { + if ($clinicIds === []) { + return []; + } + + $rows = $this->getEntityManager()->createQueryBuilder() + ->select('addr.clinicId AS clinic_id', 'cityCat.name AS city', 'provinceCat.name AS state') + ->from(DoctorAddress::class, 'addr') + ->leftJoin('addr.city', 'cityCat') + ->leftJoin('addr.province', 'provinceCat') + ->where('addr.clinicId IN (:ids)') + ->setParameter('ids', $clinicIds) + ->getQuery() + ->getResult(); + + $map = []; + foreach ($rows as $row) { + $id = (int) $row['clinic_id']; + if (!isset($map[$id])) { + $map[$id] = ['city' => $row['city'], 'state' => $row['state']]; + } + } + + return $map; + } + public function save(Clinic $clinic, bool $flush = true): void { $this->getEntityManager()->persist($clinic);