Update PRD for ClinicPro Phase 2: Enhance project requirements, add new modules, and refine existing functionalities. Include detailed specifications for Staff, Subscription tiers, Clinic services, Patient records, SMS panel, and more. Revise API documentation and frontend requirements for better clarity and implementation guidance.

This commit is contained in:
hamed
2026-06-14 20:53:49 +03:30
parent a1d3442b9b
commit 6ddc66ad74
+392 -321
View File
@@ -1,449 +1,520 @@
# PRD — ClinicPro Phase 2
**تاریخ:** ۲۴ خرداد ۱۴۰۵
**نسخه:** 1.0
**تاریخ:** ۲۴ خرداد ۱۴۰۵
**نسخه:** 1.2
**وضعیت:** Draft
---
## خلاصه اجرایی
این سند نیازمندی‌های فاز ۲ پلتفرم **ClinicPro** را پوشش می‌دهد. هدف: تبدیل پلتفرم از یک سیستم نوبت‌دهی ساده به یک سیستم مدیریت کلینیک کامل با پنل‌های اشتراکی، پرونده بیمار، مدیریت پرسنل و پنل پیامکی.
فاز ۲ پلتفرم **ClinicPro** را از یک سیستم نوبت‌دهی ساده به یک سیستم مدیریت کلینیک کامل تبدیل می‌کند. شامل پنل‌های اشتراکی، مدیریت پرسنل، پرونده بیمار، سرویس‌های درمانی و پنل پیامکی اختصاصی برای هر کلینیک/مطب.
---
## وضعیت فعلی پروژه
| ماژول | وضعیت |
|--------|--------|
| Auth (OTP + JWT) | ✅ کامل |
| Doctor profile | ✅ کامل |
| Clinic management | ✅ کامل |
| Appointment booking | ✅ کامل |
| Secretary entity (DoctorSecretary) | ✅ موجود — نیاز به UI و permissions بیشتر |
| Payment gateway (Mellat, SEP) | ✅ کامل — برای نوبت + subscription |
| SMS (SmsLog, SmsTemplate) | ✅ زیرساخت موجود — نیاز به پنل کلینیک |
| Wallet / Settlement | ✅ کامل |
| Site config (SiteConfig key-value) | ✅ موجود |
| **Staff (پرسنل)** | ❌ موجود نیست |
| **Panel subscription tiers** | ❌ موجود نیست |
| **Patient records (پرونده بیمار)** | ❌ موجود نیست |
| **Clinic services (خدمات)** | ❌ موجود نیست |
| **SMS panel per clinic** | ❌ UI نیاز دارد |
| ماژول | Backend | API Doc |
|--------|---------|---------|
| Auth (OTP + JWT + switch-context) | ✅ کامل | `docs/api/auth.md` |
| Doctor profile | ✅ کامل | `docs/api/doctor.md` |
| Clinic management | ✅ کامل | `docs/api/clinic.md` |
| Appointment booking + settings | ✅ کامل | `docs/api/appointment.md`, `appointment-settings.md` |
| Secretary (`DoctorSecretary` entity) | ✅ entity + API موجود | `docs/api/secretary.md` |
| Payment gateway (Mellat, SEP) | ✅ کامل — `Payment.type='subscription'` هم موجود | `docs/api/payment.md` |
| SMS log + template | ✅ زیرساخت موجود | `docs/api/sms.md` |
| Wallet / Settlement | ✅ کامل | `docs/api/settlement.md` |
| SiteConfig (key-value store) | ✅ موجود | `docs/api/admin.md` §Settings |
| Dashboard (clinic/doctor/secretary) | ✅ کامل | `docs/api/dashboard.md` |
| Rating, Blog, Representation, Location | ✅ کامل | فایل‌های مربوطه در `docs/api/` |
| **Staff (پرسنل)** | ❌ entity وجود ندارد | ❌ فایل doc وجود ندارد |
| **Subscription tiers** | ❌ entity وجود ندارد | ❌ فایل doc وجود ندارد |
| **Clinic services** | ❌ entity وجود ندارد | ❌ فایل doc وجود ندارد |
| **Patient records** | ❌ entity وجود ندارد | ❌ فایل doc وجود ندارد |
| **SMS wallet/settings per clinic** | ❌ entity وجود ندارد | ❌ بخش در sms.md وجود ندارد |
---
## اپیک‌ها و قابلیت‌ها
## اپیک‌ها
---
### اپیک ۱ — پرسنل (Staff)
#### توضیح
مدیر کلینیک یا دکتر مستقل می‌تواند پرسنلی که نیازی به لاگین ندارند را ثبت کند. این پرسنل در سرویس‌دهی، سشن‌ها و گزارشات استفاده می‌شوند.
مدیر کلینیک یا دکتر مستقل می‌تواند پرسنل کلینیک را ثبت کند. پرسنل حساب کاربری ندارند و نیازی به لاگین ندارند. در سرویس‌دهی، سشن‌ها و گزارشات به آن‌ها ارجاع می‌شود.
#### نیازمندی‌های کارکردی
- **F1.1** — ایجاد / ویرایش / غیرفعال‌سازی پرسنل (حذف نرم — soft delete)
- **F1.2** — فیلدهای پرسنل:
- نام و نام خانوادگی (اجباری)
- شماره تماس
- عنوان شغل (مثال: پرستار، منشی، تکنسین)
- آدرس
- کد ملی
- فعال / غیرفعال
- **F1.3** — پرسنل به کلینیک یا مطب دکتر وابسته است (FK به clinic_id یا doctor_id)
- **F1.4** — لیست پرسنل فقط در scope کلینیک/مطب فعال نمایش داده می‌شود
- **F1.5** — در ایجاد سرویس، از لیست پرسنل انتخاب می‌شود
#### موجودیت‌های جدید مورد نیاز
| کد | نیازمندی |
|----|---------|
| F1.1 | ایجاد / ویرایش / غیرفعال‌سازی پرسنل — **حذف سخت ممنوع** (تاریخچه حفظ شود) |
| F1.2 | فیلدها: نام و نام‌خانوادگی (اجباری)، شماره تماس، عنوان شغل، آدرس، کد ملی، وضعیت فعال |
| F1.3 | پرسنل به `entity_type + entity_id` وابسته است (کلینیک یا مطب) |
| F1.4 | لیست پرسنل فقط در scope کلینیک/مطب کاربر جاری نمایش داده می‌شود |
| F1.5 | در ایجاد ServiceItem و SessionService از لیست پرسنل انتخاب می‌شود |
#### Entity جدید
```
ClinicStaff:
- id, uuid
- clinic_id (FK → clinics) OR doctor_id (FK → doctors) [یکی از دو]
- full_name, phone, job_title, address, national_code
- active (bool, default: true)
- created_at, updated_at (Unix timestamp)
clinic_staff:
id, uuid
entity_type: 'doctor' | 'clinic'
entity_id: int
full_name: string (required)
phone: string|null
job_title: string|null
address: text|null
national_code: string(10)|null
active: bool = true
created_at, updated_at: int (Unix)
```
#### API endpoints
| Method | Path | Permission |
|--------|------|-----------|
| `GET` | `/api/v1/staff` | clinic/doctor |
| `POST` | `/api/v1/staff` | clinic/doctor |
| `PATCH` | `/api/v1/staff/{uuid}` | clinic/doctor |
| `PATCH` | `/api/v1/staff/{uuid}/toggle` | clinic/doctor |
#### API
| Method | Path | توضیح |
|--------|------|-------|
| `GET` | `/api/v1/staff` | لیست پرسنل کاربر جاری |
| `POST` | `/api/v1/staff` | ایجاد پرسنل جدید |
| `PATCH` | `/api/v1/staff/{uuid}` | ویرایش اطلاعات |
| `PATCH` | `/api/v1/staff/{uuid}/toggle` | فعال/غیرفعال (soft delete) |
#### Frontend
- صفحه: `StaffPage.tsx` لیست با DataTable + Modal ایجاد/ویرایش
- در Sidebar زیر بخش «تنظیمات»
- `StaffPage.tsx` — DataTable + Modal ایجاد/ویرایش
- Sidebar: زیر «تنظیمات»
#### فایل‌های جدید
- `src/Staff/Entity/ClinicStaff.php`
- `src/Staff/Controller/StaffController.php`
- `src/Staff/Repository/ClinicStaffRepository.php`
- `docs/api/staff.md`**باید ایجاد شود**
---
### اپیک ۲ — پنل اشتراکی (Subscription Tiers)
#### توضیح
سه سطح پنل وجود دارد. برای هر پنل، ادمین می‌تواند چند **دوره زمانی** با قیمت‌های مختلف تعریف کند (مثلاً ۱ ماهه، ۶ ماهه، ۱۲ ماهه). کلینیک/دکتر دوره و پنل مورد نظر را انتخاب و پرداخت می‌کند.
سه سطح پنل با قابلیت‌های متفاوت. ادمین برای هر پنل دوره‌های زمانی با قیمت مشخص تعریف می‌کند. کلینیک/دکتر دوره + پنل انتخاب کرده و پرداخت می‌کند.
#### پنل‌ها
| سطح | نام | قابلیت‌ها |
|-----|-----|----------|
| 0 | Free | نوبت‌دهی + ۱ منشی — رایگان، بدون زمان انقضا |
| 1 | Basic | ۲ منشی + پرونده بیمار + مدیریت سرویس‌ها |
| 2 | Professional | ۵ منشی + پرونده بیمار + مدیریت سرویس‌ها |
#### سطوح پنل
#### دوره‌های اشتراک (مثال)
| دوره | مدت (ماه) | نوع | قیمت |
|------|-----------|-----|------|
| تریال | 1 | trial — رایگان | ۰ ریال |
| ۱ ماهه | 1 | paid | ادمین تعیین می‌کند |
| ۳ ماهه | 3 | paid | ادمین تعیین می‌کند |
| ۶ ماهه | 6 | paid | ادمین تعیین می‌کند |
| ۱۲ ماهه | 12 | paid | ادمین تعیین می‌کند |
| Level | Name | منشی | قابلیت‌های اضافه |
|-------|------|------|----------------|
| 0 | Free | ۱ منشی | نوبت‌دهی — رایگان، بدون انقضا |
| 1 | Basic | ۲ منشی | پرونده بیمار + سرویس‌ها |
| 2 | Professional | ۵ منشی | پرونده بیمار + سرویس‌ها |
> ادمین می‌تواند دوره‌های دلخواه اضافه، ویرایش یا غیرفعال کند.
#### دوره‌های اشتراک (مثال — ادمین تنظیم می‌کند)
#### دوره تریال
- تریال فقط برای پنل **Basic** فعال است
- هر کلینیک/دکتر فقط **یک بار** می‌تواند از تریال استفاده کند (بررسی در `ClinicSubscription`)
- تریال نیازی به پرداخت ندارد — مستقیم فعال می‌شود
- ادمین می‌تواند تریال را به‌طور کلی فعال/غیرفعال کند (`trial_enabled` در SiteConfig)
- بعد از انقضای تریال، سیستم به پنل Free بازمی‌گردد (نه حذف داده)
| نوع | مدت | قیمت |
|-----|-----|------|
| تریال | ۱ ماه | ۰ ریال — فقط برای Basic، یک‌بار |
| ۱ ماهه | ۱ ماه | ادمین تعیین می‌کند |
| ۳ ماهه | ۳ ماه | ادمین تعیین می‌کند |
| ۶ ماهه | ۶ ماه | ادمین تعیین می‌کند |
| ۱۲ ماهه | ۱۲ ماه | ادمین تعیین می‌کند |
#### قوانین تریال
- فقط برای پنل **Basic**
- هر کلینیک/دکتر **یک‌بار** — بعد از استفاده، دکمه تریال نمایش داده نمی‌شود
- بدون پرداخت — endpoint مستقل دارد
- بعد از انقضا → برگشت به Free (داده‌ها حفظ می‌شوند)
- ادمین می‌تواند تریال را کلاً غیرفعال کند (`trial_enabled` در SiteConfig)
#### نیازمندی‌های کارکردی
- **F2.1** — ادمین برای هر ترکیب (پنل × دوره) یک قیمت تعریف می‌کند
- **F2.2** — ادمین می‌تواند دوره‌های جدید اضافه کند (مثلاً ۲ ماهه) یا دوره‌ای را غیرفعال کند
- **F2.3** — کلینیک/دکتر پنل + دوره را انتخاب و از طریق gateway موجود (Mellat/SEP) پرداخت می‌کند
- **F2.4** — پس از پرداخت موفق، اشتراک فعال می‌شود: `starts_at = now`, `expires_at = now + duration_months * 30 * 86400`
- **F2.5** — تمدید: اگر اشتراک فعال باشد، `starts_at` از تاریخ انقضای قبلی محاسبه می‌شود (نه از now)
- **F2.6** — gate: `hasFeature('patient_records')` — بررسی اشتراک فعال و expired نبودن
- **F2.7** — گزارش فروش پنل‌ها در داشبورد ادمین (تعداد، درآمد، بر اساس دوره و سطح)
- **F2.8** — نمایش تاریخ انقضا و هشدار ۷ روز قبل به کلینیک/دکتر
- **F2.9** — تریال Basic: فعال‌سازی رایگان یک‌بار — endpoint جداگانه `POST /api/v1/subscription/trial`
- **F2.10** — بررسی استفاده قبلی از تریال قبل از فعال‌سازی (`used_trial: bool` روی entity)
#### موجودیت‌های جدید مورد نیاز
| کد | نیازمندی |
|----|---------|
| F2.1 | ادمین قیمت هر ترکیب (پنل × دوره) را تعریف می‌کند |
| F2.2 | ادمین می‌تواند دوره‌های جدید اضافه یا غیرفعال کند |
| F2.3 | پرداخت از طریق gateway موجود (Mellat/SEP) — `Payment.type='subscription'` ✅ |
| F2.4 | پس از پرداخت موفق: `starts_at=now`, `expires_at=now + months*30*86400` |
| F2.5 | تمدید از تاریخ انقضای قبلی محاسبه می‌شود (نه از now) |
| F2.6 | gate check: `hasFeature('patient_records')` — اشتراک فعال و منقضی نشده |
| F2.7 | هشدار ۷ روز قبل از انقضا به کلینیک/دکتر |
| F2.8 | گزارش فروش برای ادمین (تعداد، درآمد، بر اساس سطح و دوره) |
#### Entities جدید
```
SubscriptionPlan:
- id, uuid
- name: 'free' | 'basic' | 'professional'
- level: int (0/1/2)
- max_secretaries: int (1/2/5)
- features: json { patient_records: bool, services: bool, sms_panel: bool }
- active: bool
- created_at, updated_at
subscription_plans:
id, uuid
name: 'free' | 'basic' | 'professional'
level: int (0/1/2)
max_secretaries: int (1/2/5)
features: json { patient_records, services, sms_panel }
active: bool
created_at, updated_at
SubscriptionPeriod:
- id, uuid
- plan_id (FK → subscription_plans)
- label: string (مثال: "۶ ماهه", "تریال ۱ ماهه")
- duration_months: int (1, 3, 6, 12, ...)
- price_rials: int (0 برای تریال)
- is_trial: bool (default: false — فقط یک دوره trial در Basic)
- active: bool
- sort_order: int (ترتیب نمایش)
- created_at, updated_at
subscription_periods:
id, uuid
plan_id → subscription_plans
label: string ('۶ ماهه', 'تریال ۱ ماهه')
duration_months: int
price_rials: int 0 برای تریال
is_trial: bool = false
active: bool
sort_order: int
created_at, updated_at
ClinicSubscription:
- id, uuid
- entity_type: 'doctor' | 'clinic'
- entity_id: int
- plan_id (FK → subscription_plans)
- period_id (FK → subscription_periods)
- payment_id (FK → payments, nullable — برای تریال null است)
- is_trial: bool (کپی از period — برای گزارش راحت‌تر)
- starts_at: int (Unix timestamp)
- expires_at: int (Unix timestamp — null = بی‌نهایت برای Free)
- created_at
clinic_subscriptions:
id, uuid
entity_type: 'doctor' | 'clinic'
entity_id: int
plan_id → subscription_plans
period_id → subscription_periods
payment_id → payments nullable (تریال = null)
is_trial: bool
starts_at: int
expires_at: int null = بی‌نهایت (Free)
created_at
```
#### وابستگی به Payment موجود
- `Payment.type = 'subscription'` ✅ (از قبل موجود)
- Callback در `PaymentController::subscriptionCallback()`: ایجاد `ClinicSubscription` پس از تأیید
#### API endpoints
| Method | Path | Permission |
|--------|------|-----------|
| `GET` | `/api/v1/subscription/plans` | public — لیست پنل‌ها با دوره‌ها، قیمت‌ها و دوره تریال |
| `GET` | `/api/v1/subscription/my` | clinic/doctor — اشتراک فعال + تاریخ انقضا + `used_trial` |
| `POST` | `/api/v1/subscription/trial` | clinic/doctor — فعال‌سازی تریال Basic (رایگان، یک‌بار) |
| `POST` | `/api/v1/subscription-payment` | clinic/doctor — `{ period_uuid }` |
| `GET` | `/api/v1/admin/subscription/plans` | admin — مدیریت پنل‌ها |
| `POST` | `/api/v1/admin/subscription/plan` | admin |
| `PATCH` | `/api/v1/admin/subscription/plan/{uuid}` | admin |
| `POST` | `/api/v1/admin/subscription/period` | admin — افزودن دوره جدید |
| `PATCH` | `/api/v1/admin/subscription/period/{uuid}` | admin — ویرایش قیمت/مدت/تریال |
| `DELETE` | `/api/v1/admin/subscription/period/{uuid}` | admin — غیرفعال‌سازی |
| `GET` | `/api/v1/admin/subscription/report` | admin — گزارش فروش + تعداد تریال‌های فعال‌شده |
#### API
| Method | Path | Permission | توضیح |
|--------|------|-----------|-------|
| `GET` | `/api/v1/subscription/plans` | public | لیست پنل‌ها با دوره‌ها و قیمت‌ها |
| `GET` | `/api/v1/subscription/my` | clinic/doctor | اشتراک فعال + `used_trial` + تاریخ انقضا |
| `POST` | `/api/v1/subscription/trial` | clinic/doctor | فعال‌سازی تریال (رایگان، یک‌بار) |
| `POST` | `/api/v1/subscription-payment` | clinic/doctor | شروع پرداخت — body: `{ period_uuid }` |
| `GET` | `/api/v1/admin/subscription/plans` | admin | لیست پنل‌ها |
| `POST` | `/api/v1/admin/subscription/plan` | admin | ایجاد پنل |
| `PATCH` | `/api/v1/admin/subscription/plan/{uuid}` | admin | ویرایش پنل |
| `POST` | `/api/v1/admin/subscription/period` | admin | افزودن دوره جدید |
| `PATCH` | `/api/v1/admin/subscription/period/{uuid}` | admin | ویرایش قیمت/مدت |
| `DELETE` | `/api/v1/admin/subscription/period/{uuid}` | admin | غیرفعال‌سازی دوره |
| `GET` | `/api/v1/admin/subscription/report` | admin | گزارش فروش + تریال‌ها |
#### Frontend
- صفحه کلینیک/دکتر: `SubscriptionPage.tsx`
- اگر `used_trial = false`: بنر «۱ ماه رایگان امتحان کنید» با دکمه فعال‌سازی تریال
- نمایش پنل فعال + تاریخ انقضا (شمسی) + badge «تریال» اگر is_trial باشد
- جدول مقایسه پنل‌ها با انتخاب دوره (toggle)
- `SubscriptionPage.tsx`:
- بنر تریال اگر `used_trial=false`
- وضعیت پنل فعال + تاریخ انقضا (شمسی) + badge «تریال»
- جدول مقایسه پنل‌ها با انتخاب دوره
- دکمه خرید → redirect به gateway
- صفحه ادمین: `AdminSubscriptionPage.tsx`
- تب «پنل‌ها»: ویرایش قیمت دوره‌ها + مشخص کردن کدام دوره تریال است
- تب «گزارش»: فروش + تریال‌ها بر اساس بازه زمانی
- `AdminSubscriptionPage.tsx`:
- تب «پنل‌ها»: ویرایش دوره‌ها و قیمت‌ها
- تب «گزارش»: فروش بر اساس بازه زمانی
#### فایل‌های جدید
- `src/Subscription/Entity/SubscriptionPlan.php`
- `src/Subscription/Entity/SubscriptionPeriod.php`
- `src/Subscription/Entity/ClinicSubscription.php`
- `src/Subscription/Controller/SubscriptionController.php`
- `src/Subscription/Service/SubscriptionService.php`
- `docs/api/subscription.md`**باید ایجاد شود**
---
### اپیک ۳ — منشی (Secretary) — تکمیل
#### توضیح
جدول `doctor_secretaries` و entity `DoctorSecretary` موجود است. نیاز به:
- محدودیت تعداد منشی بر اساس پنل
- صفحه فرانت‌اند کامل برای مدیریت منشیان و تنظیم دسترسی‌ها
Entity `DoctorSecretary` و API کامل موجود است (`docs/api/secretary.md`). باید:
- محدودیت تعداد بر اساس پنل اعمال شود
- UI مدیریت permissions تکمیل شود
#### نیازمندی‌های کارکردی
- **F3.1** — بررسی سقف تعداد منشی قبل از ایجاد (free→1, basic→2, pro→5)
- **F3.2** — UI کامل: لیست منشیان + اضافه کردن + ویرایش دسترسی + غیرفعال‌سازی
- **F3.3** — پنل دسترسی: checkbox matrix برای هر منشی (permissions JSON موجود)
- نوبت‌ها: view, create, cancel, update_status
- آدرس‌ها: view, create, update, delete
- اطلاعات کلینیک: view, update
- بیمه‌ها: view, create, update, delete
- **F3.4** — منشی هرگز حذف نمی‌شود (soft delete با `active: false`)
#### وضعیت فعلی
```php
// DoctorSecretary.DEFAULT_PERMISSIONS — موجود
// API موجود: GET /api/v1/secretaries/{doctorUuid}, POST /api/v1/secretary, PATCH, DELETE
// صفحه SecretariesPage.tsx موجود — نیاز به بررسی کامل بودن
```
| کد | نیازمندی |
|----|---------|
| F3.1 | بررسی سقف پنل قبل از ایجاد: Free→1، Basic→2، Pro→5 |
| F3.2 | UI لیست منشیان + اضافه‌کردن + ویرایش + غیرفعال‌سازی |
| F3.3 | ویرایشگر permissions: checkbox matrix (موجود در `DoctorSecretary.DEFAULT_PERMISSIONS`) |
| F3.4 | منشی هرگز حذف نمی‌شود — `active: false` |
#### تغییرات مورد نیاز
- Backend: اضافه کردن بررسی سقف پنل در `SecretaryController::create()`
- Frontend: ویرایشگر گرافیکی permissions (checkbox matrix در Modal)
- **Backend:** `SecretaryController::create()` — بررسی سقف پنل با `SubscriptionService`
- **Frontend:** Modal با checkbox matrix برای permissions در `SecretariesPage.tsx`
#### API موجود (نیاز به تغییر ندارند)
`GET /api/v1/secretaries/{doctorUuid}` | `POST /api/v1/secretary` | `PATCH /api/v1/secretary/{uuid}` | `DELETE /api/v1/secretary/{uuid}`
---
### اپیک ۴ — سرویس‌های کلینیک (Clinic Services)
#### توضیح
دسترسی فقط در پنل Basic و بالاتر. سرویس‌ها در دو سطح تعریف می‌شوند: بخش و زیر بخش.
#### ساختار سرویس
```
ServiceSection (بخش — مثال: تزریقات، پانسمان)
└── ServiceItem (زیر بخش — مثال: سرم)
- section_id (FK)
- staff_id (FK → clinic_staff) — پرسنل انجام‌دهنده
- price (ریال)
- active
```
دسترسی فقط در پنل Basic+. سرویس‌ها در دو سطح: بخش (مثلاً «تزریقات») و زیربخش (مثلاً «سرم»).
#### نیازمندی‌های کارکردی
- **F4.1** — CRUD بخش‌ها (ServiceSection)
- **F4.2** — CRUD زیر بخش‌ها (ServiceItem) با انتخاب بخش و پرسنل
- **F4.3** — قیمت هر زیر بخش قابل ویرایش
- **F4.4** — gate: `hasFeature('services')` — بدون Basic+ → 403
#### موجودیت‌های جدید مورد نیاز
| کد | نیازمندی |
|----|---------|
| F4.1 | CRUD بخش‌ها (ServiceSection) |
| F4.2 | CRUD زیربخش‌ها (ServiceItem) با انتخاب بخش + پرسنل انجام‌دهنده |
| F4.3 | قیمت هر زیربخش قابل ویرایش |
| F4.4 | gate: `hasFeature('services')` — بدون Basic+ → 403 |
| F4.5 | لیست ServiceItems در ثبت سشن بیمار استفاده می‌شود (وابستگی اپیک ۶) |
#### Entities جدید
```
ServiceSection:
- id, uuid
- entity_type: 'doctor' | 'clinic', entity_id
- name
- active, created_at, updated_at
service_sections:
id, uuid
entity_type: 'doctor' | 'clinic'
entity_id: int
name: string
active: bool
created_at, updated_at
ServiceItem:
- id, uuid
- section_id (FK → service_sections)
- staff_id (FK → clinic_staff, nullable)
- name, price_rials
- active, created_at, updated_at
service_items:
id, uuid
section_id → service_sections
staff_id → clinic_staff nullable
name: string
price_rials: int
active: bool
created_at, updated_at
```
#### API endpoints
| Method | Path | Permission |
|--------|------|-----------|
| `GET` | `/api/v1/service-sections` | clinic/doctor |
| `POST` | `/api/v1/service-section` | clinic/doctor (basic+) |
| `PATCH` | `/api/v1/service-section/{uuid}` | clinic/doctor |
| `DELETE` | `/api/v1/service-section/{uuid}` | clinic/doctor |
| `GET` | `/api/v1/service-items/{sectionUuid}` | clinic/doctor |
| `POST` | `/api/v1/service-item` | clinic/doctor (basic+) |
| `PATCH` | `/api/v1/service-item/{uuid}` | clinic/doctor |
| `DELETE` | `/api/v1/service-item/{uuid}` | clinic/doctor |
#### API
| Method | Path | توضیح |
|--------|------|-------|
| `GET` | `/api/v1/service-sections` | لیست بخش‌ها |
| `POST` | `/api/v1/service-section` | ایجاد بخش (Basic+) |
| `PATCH` | `/api/v1/service-section/{uuid}` | ویرایش |
| `DELETE` | `/api/v1/service-section/{uuid}` | حذف |
| `GET` | `/api/v1/service-items/{sectionUuid}` | لیست زیربخش‌های یک بخش |
| `POST` | `/api/v1/service-item` | ایجاد زیربخش (Basic+) |
| `PATCH` | `/api/v1/service-item/{uuid}` | ویرایش |
| `DELETE` | `/api/v1/service-item/{uuid}` | حذف |
#### Frontend
- صفحه: `ServicesPage.tsx` با دو سطح accordion: بخش‌ها و زیر بخش‌ها
- `ServicesPage.tsx` accordion دو سطحی: بخش‌ها زیربخش‌ها
#### فایل‌های جدید
- `src/ClinicService/Entity/ServiceSection.php`
- `src/ClinicService/Entity/ServiceItem.php`
- `src/ClinicService/Controller/ClinicServiceController.php`
- `docs/api/clinic-services.md`**باید ایجاد شود**
---
### اپیک ۵ — پنل پیامکی (SMS Panel)
#### توضیح
زیرساخت SMS (SmsLog, SmsTemplate, SmsProvider) موجود است. باید پنل شارژی برای هر کلینیک/دکتر اضافه شود.
زیرساخت SMS موجود است (`SmsLog`, `SmsTemplate`, `SmsProvider` در `src/Sms/`). باید:
- کیف پول پیامک اختصاصی برای هر کلینیک/دکتر
- تنظیمات ارسال پیامک (یادآوری، بعد از ویزیت)
- گزارش لاگ برای کلینیک و ادمین
#### نیازمندی‌های کارکردی
- **F5.1** — کیف پول پیامک جداگانه از کیف پول مالی (یا همان wallet با type مجزا)
- **F5.2** — شارژ کردن پنل پیامک از طریق gateway موجود
- **F5.3** — ادمین تعرفه هر پیامک را در SiteConfig تعیین می‌کند (`sms_price_rials`)
- **F5.4** — کسر خودکار هزینه از پنل پس از هر ارسال موفق
- **F5.5** — تنظیمات ارسال پیامک (در صفحه تنظیمات کلینیک):
- یادآوری نوبت: فعال/غیرفعال + چند ساعت قبل
- پیام بعد از ویزیت: فعال/غیرفعال + متن قابل تنظیم
- **F5.6** — گزارش لاگ پیامک‌های هر کلینیک/دکتر
- **F5.7** — گزارش ادمین: درآمد پیامکی، مصرف هر کلینیک
#### موجودیت‌های جدید/تغییر یافته
| کد | نیازمندی |
|----|---------|
| F5.1 | کیف پول پیامک جدا از کیف پول مالی |
| F5.2 | شارژ از طریق gateway موجود |
| F5.3 | ادمین تعرفه هر پیامک را تعیین می‌کند (`sms_price_rials` در SiteConfig) |
| F5.4 | کسر خودکار هزینه بعد از هر ارسال موفق |
| F5.5 | تنظیمات: یادآوری نوبت (فعال/غیرفعال + چند ساعت قبل) |
| F5.6 | تنظیمات: پیام بعد از ویزیت (فعال/غیرفعال + متن) |
| F5.7 | گزارش لاگ پیامک‌های کلینیک/مطب |
| F5.8 | گزارش ادمین: مصرف هر کلینیک + درآمد پیامکی |
#### Entities جدید
```
SmsWallet (یا افزودن فیلد به Doctor/Clinic):
- entity_type, entity_id
- balance_rials (موجودی)
- created_at, updated_at
sms_wallets:
id
entity_type: 'doctor' | 'clinic'
entity_id: int
balance_rials: int = 0
created_at, updated_at
SmsSettings:
- entity_type, entity_id
- reminder_enabled (bool)
- reminder_hours_before (int)
- post_visit_enabled (bool)
- post_visit_text (text)
- updated_at
sms_settings:
id
entity_type: 'doctor' | 'clinic'
entity_id: int
reminder_enabled: bool = false
reminder_hours_before: int = 2
post_visit_enabled: bool = false
post_visit_text: text|null
updated_at
```
#### API endpoints
| Method | Path | Permission |
|--------|------|-----------|
| `GET` | `/api/v1/sms/wallet/balance` | clinic/doctor |
| `POST` | `/api/v1/sms/wallet/charge` | clinic/doctor |
| `GET` | `/api/v1/sms/wallet/logs` | clinic/doctor |
| `GET` | `/api/v1/sms/settings` | clinic/doctor |
| `PATCH` | `/api/v1/sms/settings` | clinic/doctor |
| `GET` | `/api/v1/admin/sms/wallet-report` | admin |
#### API
| Method | Path | Permission | توضیح |
|--------|------|-----------|-------|
| `GET` | `/api/v1/sms/wallet/balance` | clinic/doctor | موجودی کیف پول پیامک |
| `POST` | `/api/v1/sms/wallet/charge` | clinic/doctor | شارژ — body: `{ gateway, amount_rials }` |
| `GET` | `/api/v1/sms/wallet/logs` | clinic/doctor | تاریخچه کسر/شارژ |
| `GET` | `/api/v1/sms/settings` | clinic/doctor | تنظیمات پیامک |
| `PATCH` | `/api/v1/sms/settings` | clinic/doctor | ذخیره تنظیمات |
| `GET` | `/api/v1/admin/sms/wallet-report` | admin | گزارش مصرف و درآمد |
#### Frontend
- تب «پیامک» در صفحه تنظیمات کلینیک/دکتر
- نمایش موجودی + دکمه شارژ + لاگ
- نمایش موجودی + دکمه شارژ + لاگ + فرم تنظیمات
#### تأثیر بر فایل‌های موجود
- `docs/api/sms.md`**بخش جدید wallet/settings اضافه شود**
---
### اپیک ۶ — پرونده بیمار (Patient Records)
#### توضیح
دسترسی در پنل Basic+. پرونده بیمار به صورت خودکار پس از تأیید نوبت ایجاد می‌شود یا دستی توسط منشی/دکتر.
#### ساختار پرونده
```
PatientRecord (پرونده بیمار):
- entity_type, entity_id (کلینیک/مطب که پرونده به آن تعلق دارد)
- user_id (FK → users — بیمار)
- created_by (منشی/دکتر که ایجاد کرده)
- created_at
PatientSession (مراجعه/ویزیت):
- record_id (FK → patient_records)
- appointment_id (FK → appointments, nullable — اگر از نوبت)
- insurance_base_id (FK → categories, bundle='insurance_type')
- insurance_supplementary_id (FK → categories)
- visit_price_rials (مبلغ کل ویزیت)
- base_insurance_discount_percent (کسر بیمه پایه)
- supplementary_discount_percent (کسر بیمه مکمل)
- final_price_rials (مبلغ نهایی پس از کسورات)
- payment_method: 'cash' | 'card' | 'insurance' | 'pending'
- notes (text)
- created_at, updated_at
SessionService (سرویس انجام‌شده در یک مراجعه):
- session_id (FK → patient_sessions)
- service_item_id (FK → service_items)
- staff_id (FK → clinic_staff — پرسنل انجام‌دهنده)
- price_rials (قیمت در زمان ارائه)
- created_at
```
دسترسی فقط در پنل Basic+. پرونده به صورت خودکار پس از تأیید نوبت ایجاد می‌شود یا دستی توسط منشی/دکتر. هر مراجعه یک سشن است که شامل ویزیت + سرویس‌ها است.
#### نیازمندی‌های کارکردی
- **F6.1** — ایجاد خودکار PatientRecord پس از تأیید appointment (اگر وجود ندارد)
- **F6.2** — ایجاد دستی توسط منشی یا دکتر
- **F6.3** — ثبت سشن جدید با:
- انتخاب بیمه پایه و تکمیلی (از categories)
- تعیین مبلغ ویزیت (از پیش تعیین‌شده یا قابل ویرایش)
- محاسبه خودکار مبلغ نهایی
- ثبت سرویس‌های انجام شده (از ServiceItems)
- تعیین پرسنل انجام‌دهنده
- **F6.4** — gate: `hasFeature('patient_records')` — بدون Basic+ → 403
- **F6.5** — تاریخچه مراجعات یک بیمار قابل نمایش
#### API endpoints
| Method | Path | Permission |
|--------|------|-----------|
| `GET` | `/api/v1/patients` | clinic/doctor (basic+) |
| `POST` | `/api/v1/patient` | clinic/doctor (basic+) |
| `GET` | `/api/v1/patient/{uuid}` | clinic/doctor |
| `GET` | `/api/v1/patient/{uuid}/sessions` | clinic/doctor |
| `POST` | `/api/v1/patient/{uuid}/session` | clinic/doctor |
| `PATCH` | `/api/v1/session/{uuid}` | clinic/doctor |
| کد | نیازمندی |
|----|---------|
| F6.1 | ایجاد خودکار `PatientRecord` بعد از تأیید appointment (اگر وجود نداشت) |
| F6.2 | ایجاد دستی توسط منشی یا دکتر |
| F6.3 | ثبت سشن: بیمه پایه + مکمل، مبلغ ویزیت (پیش‌فرض یا دستی)، محاسبه خودکار کسورات |
| F6.4 | ثبت سرویس‌های انجام‌شده در سشن با پرسنل انجام‌دهنده |
| F6.5 | gate: `hasFeature('patient_records')` — بدون Basic+ → 403 |
| F6.6 | تاریخچه کامل مراجعات یک بیمار |
#### محاسبه مبلغ نهایی سشن
```
visit_price_rials ← ادمین/دکتر تعیین می‌کند
- base_insurance_discount% ← بیمه پایه
- supplementary_discount% ← بیمه مکمل
+ service_items total ← مجموع سرویس‌های انجام‌شده
= final_price_rials
```
#### Entities جدید
```
patient_records:
id, uuid
entity_type: 'doctor' | 'clinic'
entity_id: int
user_id → users ← بیمار
created_by_type: 'doctor' | 'secretary' | 'system'
created_by_id: int
created_at
patient_sessions:
id, uuid
record_id → patient_records
appointment_id → appointments ← nullable
insurance_base_id → categories (bundle='insurance_type') ← nullable
insurance_supplementary_id → categories ← nullable
visit_price_rials: int
base_insurance_discount_percent: float = 0
supplementary_discount_percent: float = 0
final_price_rials: int
payment_method: 'cash' | 'card' | 'insurance' | 'pending'
notes: text|null
created_at, updated_at
session_services:
id, uuid
session_id → patient_sessions
service_item_id → service_items
staff_id → clinic_staff ← nullable
price_rials: int ← قیمت در زمان ارائه (کپی از service_item)
created_at
```
#### API
| Method | Path | Permission | توضیح |
|--------|------|-----------|-------|
| `GET` | `/api/v1/patients` | clinic/doctor (basic+) | لیست بیماران با جستجو |
| `POST` | `/api/v1/patient` | clinic/doctor (basic+) | ایجاد پرونده دستی |
| `GET` | `/api/v1/patient/{uuid}` | clinic/doctor | جزئیات پرونده |
| `GET` | `/api/v1/patient/{uuid}/sessions` | clinic/doctor | لیست مراجعات |
| `POST` | `/api/v1/patient/{uuid}/session` | clinic/doctor | ثبت مراجعه جدید |
| `PATCH` | `/api/v1/session/{uuid}` | clinic/doctor | ویرایش مراجعه |
#### Frontend
- صفحه `PatientsPage.tsx` — لیست بیماران با جستجو
- صفحه `PatientDetailPage.tsx` — پرونده کامل + تاریخچه مراجعات
- Modal ثبت سشن جدید
- `PatientsPage.tsx` — لیست بیماران + جستجو
- `PatientDetailPage.tsx` — پرونده + تاریخچه مراجعات
- Modal ثبت سشن جدید (بیمه + ویزیت + سرویس‌ها)
#### فایل‌های جدید
- `src/Patient/Entity/PatientRecord.php`
- `src/Patient/Entity/PatientSession.php`
- `src/Patient/Entity/SessionService.php`
- `src/Patient/Controller/PatientController.php`
- `docs/api/patient.md`**باید ایجاد شود**
---
### اپیک ۷ — داشبورد هوشمند (Smart Dashboard)
#### توضیح
هر داشبورد (ادمین، کلینیک، مطب، منشی) باید چارت‌های مناسب با فیلترهای role-based داشته باشد.
داشبوردهای موجود (clinic/doctor/secretary در `docs/api/dashboard.md`) باید با چارت و فیلتر بازه زمانی تکمیل شوند.
#### نیازمندی‌های کارکردی
- **F7.1** — داشبورد ادمین:
- چارت: درآمد پلتفرم (ماهانه، هفتگی)
- چارت: نوبت‌های ثبت‌شده در بازه زمانی
- آمار: تعداد کلینیک‌ها، دکترها، کاربران فعال
- گزارش پنل‌های فروخته‌شده
- **F7.2** — داشبورد کلینیک/مطب:
- نوبت‌های امروز (موجود)
- درآمد ماه جاری
- تعداد بیماران منحصربه‌فرد
- موجودی پیامک
- **F7.3** — فیلتر بازه زمانی در همه چارت‌ها (این هفته / این ماه / ۳ ماه / سفارشی)
- **F7.4** — فیلتر بر اساس نقش (منشی فقط نوبت‌ها را می‌بیند)
| کد | نیازمندی |
|----|---------|
| F7.1 | داشبورد ادمین: چارت درآمد + چارت نوبت‌ها + گزارش فروش پنل‌ها |
| F7.2 | داشبورد کلینیک/مطب: درآمد ماه جاری + بیماران منحصربه‌فرد + موجودی پیامک |
| F7.3 | فیلتر بازه زمانی: این هفته / این ماه / ۳ ماه / سفارشی |
| F7.4 | فیلتر نقش: منشی فقط نوبت‌ها را می‌بیند |
#### API موجود (نیاز به endpoint جدید ندارد)
`GET /api/v1/admin/dashboard/stats` | `/charts` | `/recent`
`GET /api/v1/dashboard/clinic` | `/doctor` | `/secretary`
#### تغییر مورد نیاز
- backend: اضافه کردن پارامترهای `from` و `to` به endpoint های charts
- frontend: `DashboardPage.tsx` — افزودن date range selector + نمودارها (Recharts/Chart.js)
---
## اولویت‌بندی پیاده‌سازی
| اولویت | اپیک | دلیل |
|--------|------|-------|
| 1 | اپیک ۱ (Staff) | پیش‌نیاز اپیک ۴ و ۶ |
| 2 | اپیک ۲ (Subscription) | پیش‌نیاز gate checks |
| 3 | اپیک ۳ (Secretary - تکمیل) | وابسته به پنل |
| 4 | اپیک ۴ (Services) | پیش‌نیاز اپیک ۶ |
| 5 | اپیک ۵ (SMS Panel) | مستقل از بقیه |
| 6 | اپیک ۶ (Patient Records) | بیشترین وابستگی |
| 7 | اپیک ۷ (Dashboard) | آخر — نیاز به داده واقعی |
| اولویت | اپیک | وابستگی |
|--------|------|---------|
| ۱ | اپیک ۱ Staff | پیش‌نیاز اپیک ۴ و ۶ |
| ۲ | اپیک ۲ Subscription | پیش‌نیاز gate در اپیک ۳، ۴، ۶ |
| ۳ | اپیک ۳ Secretary تکمیل | وابسته به اپیک ۲ |
| ۴ | اپیک ۴ Services | وابسته به اپیک ۱ و ۲ |
| ۵ | اپیک ۵ SMS Panel | مستقل |
| ۶ | اپیک ۶ Patient Records | وابسته به اپیک ۱، ۲، ۴ |
| ۷ | اپیک ۷ Dashboard | آخر — نیاز به داده واقعی |
---
## وابستگی‌های فنی
## نقشه Entity‌های جدید
### وضعیت زیرساخت موجود و قابل استفاده
```
clinic_staff ←──────────────── service_items ←─── session_services
↑ ↑ ↑
│ service_sections patient_sessions
│ ↑ ↑
│ entity_type/id patient_records
│ ↑
subscription_plans ←── subscription_periods entity_type/id
└──────────── clinic_subscriptions
payment (موجود)
| زیرساخت | فایل | نحوه استفاده در فاز ۲ |
|---------|------|----------------------|
| Payment gateway | `src/Payment/` | پرداخت پنل اشتراکی و شارژ SMS |
| Wallet | `src/Settlement/Entity/WalletTransaction.php` | بررسی مدل برای SMS wallet |
| SiteConfig | `src/Config/Entity/SiteConfig.php` | ذخیره قیمت پنل‌ها + تعرفه SMS |
| Categories (insurance) | `bundle='insurance_type'` | انتخاب بیمه در سشن |
| DoctorSecretary | `src/Secretary/Entity/DoctorSecretary.php` | افزودن بررسی سقف پنل |
| SmsLog/SmsTemplate | `src/Sms/Entity/` | زیرساخت ارسال موجود |
sms_wallets sms_settings
(entity_type/id)
```
### نکات معماری
---
- همه entity‌های جدید از الگوی Unix timestamp استفاده می‌کنند
## زیرساخت موجود قابل استفاده
| زیرساخت | مسیر | استفاده در فاز ۲ |
|---------|------|-----------------|
| Payment gateway | `src/Payment/` | پرداخت subscription + شارژ SMS wallet |
| `Payment.type='subscription'` | `Payment::TYPE_SUBSCRIPTION` | پرداخت پنل اشتراکی — موجود ✅ |
| SiteConfig (key-value) | `src/Config/Entity/SiteConfig.php` | `sms_price_rials`, `trial_enabled` |
| Categories (insurance) | `bundle='insurance_type'` | انتخاب بیمه در PatientSession |
| DoctorSecretary | `src/Secretary/Entity/` | اضافه کردن gate check |
| SmsLog/SmsTemplate | `src/Sms/Entity/` | ارسال پیامک‌های خودکار |
| WalletTransaction | `src/Settlement/Entity/` | الگوی مشابه برای SmsWallet |
---
## معماری مشترک
- همه entity‌های جدید: Unix timestamp (`int`) برای `created_at`, `updated_at`
- همه controller‌های جدید از `BaseController` ارث می‌برند
- gate check برای features با یک helper در `BaseController` یا `SubscriptionService` پیاده‌سازی شود
- `entity_type` + `entity_id` pattern (polymorphic) برای Staff، Services، SmsSettings، Patient Records
- پاسخ‌ها: `$this->success()` / `$this->paginated()` / `$this->error()`
- polymorphic pattern: `entity_type: 'doctor'|'clinic'` + `entity_id`
- gate check: `SubscriptionService::hasFeature(string $feature): bool`
---
## معیارهای پذیرش (Definition of Done)
## Definition of Done (هر اپیک)
برای هر اپیک:
- [ ] Entity + Migration ایجاد شده
- [ ] Controller + Repository + Service نوشته شده
- [ ] API endpoints تست‌پذیر در Swagger (`/api/doc`)
- [ ] Frontend page کامل با CRUD
- [ ] مستندات `docs/api/` به‌روزرسانی شده
- [ ] بررسی gate/permission برای plan-gated features
- [ ] Migration ایجاد و اجرا شده
- [ ] Entity + Repository + Service نوشته شده
- [ ] Controller با همه endpoints کامل شده
- [ ] Type check frontend بدون خطا (`yarn dev`)
- [ ] API قابل تست در Swagger (`/api/doc`)
- [ ] فایل `docs/api/xxx.md` ایجاد یا بروزرسانی شده