feat: create phase 2 task documentation for staff management, subscription, secretary completion, clinic services, SMS panel, patient records, and smart dashboard
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
# ساخت تسکهای فاز ۲ — ClinicPro
|
||||
|
||||
## زمینه
|
||||
|
||||
PRD فاز ۲ در `docs/PRD/prd.md` تعریف شده و شامل ۷ اپیک است. باید برای هر اپیک یک پوشه تسک در `docs/tasks/` با فایلهای `task.md`، `architecture.md`، `database.md` و `user_flow.md` ایجاد شود — دقیقاً مطابق ساختار `docs/tasks/task-09-appointment-settings/`.
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
### ساختار نمونه (task-09):
|
||||
```
|
||||
docs/tasks/task-09-appointment-settings/
|
||||
task.md ← endpoints، نمونه request/response، پیشنیازها، زمان تخمینی
|
||||
architecture.md ← ساختار فایلهای src/، نمونه entity با Doctrine annotations
|
||||
database.md ← جداول، ستونها با نوع دقیق، ایندکسها، ساختار JSON ها
|
||||
user_flow.md ← جریان کاربری با pseudocode و diagram ASCII
|
||||
```
|
||||
|
||||
### مسیر نهایی تسکهای جدید:
|
||||
```
|
||||
docs/phase2_taskes/task-10-staff/
|
||||
docs/phase2_taskes/task-11-subscription/
|
||||
docs/phase2_taskes/task-12-secretary-completion/
|
||||
docs/phase2_taskes/task-13-clinic-services/
|
||||
docs/phase2_taskes/task-14-sms-panel/
|
||||
docs/phase2_taskes/task-15-patient-records/
|
||||
docs/phase2_taskes/task-16-smart-dashboard/
|
||||
```
|
||||
|
||||
### API های موجود (نیازی به پیادهسازی ندارند):
|
||||
- `/api/v1/secretary*` — SecretaryController موجود است
|
||||
- `/api/v1/subscription-payment` و `/api/v1/subscription-payment/callback/{gateway}` — موجود است
|
||||
- `/api/v1/sms/send`, `/api/v1/sms/template*` — موجود است
|
||||
- `/api/v1/wallet/*` — WalletTransaction موجود است (برای SMS wallet الگو میگیریم)
|
||||
|
||||
### Entities موجود مرتبط:
|
||||
- `src/Secretary/Entity/DoctorSecretary.php` — DEFAULT_PERMISSIONS ساختار permissions
|
||||
- `src/Settlement/Entity/WalletTransaction.php` — الگوی wallet برای SmsWallet
|
||||
- `src/Payment/Entity/Payment.php` — `TYPE_SUBSCRIPTION` const موجود است
|
||||
- `src/Sms/Entity/SmsLog.php`, `SmsTemplate.php` — زیرساخت SMS موجود
|
||||
|
||||
## قوانین کلی ساختار فایل
|
||||
|
||||
### قوانین entity در architecture.md:
|
||||
```php
|
||||
// همه entity های جدید از این الگو پیروی میکنند:
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'table_name')]
|
||||
class EntityName
|
||||
{
|
||||
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
// polymorphic pattern:
|
||||
#[ORM\Column(type: 'string', length: 10)]
|
||||
private string $entityType; // 'doctor' | 'clinic'
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $entityId;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $updatedAt;
|
||||
}
|
||||
```
|
||||
|
||||
### قوانین controller:
|
||||
```php
|
||||
// همه controller ها از BaseController ارث میبرند
|
||||
class XxxController extends BaseController
|
||||
{
|
||||
// پاسخ موفق: $this->success($data)
|
||||
// لیست paginated: $this->paginated($items, $total, $page, $limit)
|
||||
// خطا: $this->error(ErrorCodes::ERR_XXX, 'پیام', Response::HTTP_XXX)
|
||||
// خطای validation: $this->validationError($violations)
|
||||
}
|
||||
```
|
||||
|
||||
### قوانین database.md:
|
||||
- تاریخها همه `INT NOT NULL` (Unix timestamp) هستند — هیچگاه DATETIME نه
|
||||
- `uuid` همیشه `CHAR(36) UNIQUE NOT NULL`
|
||||
- `entity_type` همیشه `VARCHAR(10)` با مقادیر `'doctor'` یا `'clinic'`
|
||||
- `entity_id` همیشه `INT NOT NULL` (FK به doctors.id یا clinics.id بسته به entity_type)
|
||||
|
||||
---
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. ساخت `docs/tasks/task-10-staff/`
|
||||
|
||||
**پیشنیاز اپیک ۴ و ۶ — باید اول پیادهسازی شود.**
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۰: مدیریت پرسنل (Staff)»
|
||||
- پیشنیازها: تسک ۰۲ (Auth)، تسک ۰۵ (Doctor)، تسک ۰۶ (Clinic)
|
||||
- زمان تخمینی: ۶ تا ۸ ساعت
|
||||
- Endpoints:
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| GET | `/api/v1/staff` | لیست پرسنل context جاری | بله (Doctor/Clinic) |
|
||||
| POST | `/api/v1/staff` | ایجاد پرسنل جدید | بله |
|
||||
| PATCH | `/api/v1/staff/{uuid}` | ویرایش اطلاعات | بله (مالک) |
|
||||
| PATCH | `/api/v1/staff/{uuid}/toggle` | فعال/غیرفعال | بله (مالک) |
|
||||
|
||||
نمونه Request و Response واقعی با json کامل.
|
||||
|
||||
#### `architecture.md`:
|
||||
```
|
||||
src/Staff/
|
||||
├── Controller/StaffController.php
|
||||
├── Entity/ClinicStaff.php
|
||||
├── Repository/ClinicStaffRepository.php
|
||||
└── Service/StaffService.php
|
||||
```
|
||||
Entity `ClinicStaff` با تمام فیلدها با Doctrine annotations دقیق.
|
||||
|
||||
#### `database.md`:
|
||||
جدول `clinic_staff`:
|
||||
- id INT UNSIGNED AUTO_INCREMENT PK
|
||||
- uuid CHAR(36) UNIQUE NOT NULL
|
||||
- entity_type VARCHAR(10) NOT NULL (`'doctor'` | `'clinic'`)
|
||||
- entity_id INT NOT NULL
|
||||
- full_name VARCHAR(200) NOT NULL
|
||||
- phone VARCHAR(20) NULL
|
||||
- job_title VARCHAR(100) NULL
|
||||
- address TEXT NULL
|
||||
- national_code CHAR(10) NULL
|
||||
- active TINYINT(1) NOT NULL DEFAULT 1
|
||||
- created_at INT NOT NULL
|
||||
- updated_at INT NOT NULL
|
||||
|
||||
ایندکس: `idx_clinic_staff_entity ON clinic_staff(entity_type, entity_id, active)`
|
||||
|
||||
#### `user_flow.md`:
|
||||
جریان ایجاد پرسنل + جریان غیرفعالسازی (چرا حذف ممنوع است: تاریخچه سرویسها حفظ میشود).
|
||||
|
||||
---
|
||||
|
||||
### ۲. ساخت `docs/tasks/task-11-subscription/`
|
||||
|
||||
**پیشنیاز همه gate check ها — باید دوم پیادهسازی شود.**
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۱: پنل اشتراکی (Subscription Tiers)»
|
||||
- پیشنیازها: تسک ۰۲، تسک ۱۵ (Payment موجود)
|
||||
- زمان تخمینی: ۱۴ تا ۱۶ ساعت
|
||||
- Endpoints (جدید — هیچکدام موجود نیستند به جز `/api/v1/subscription-payment` که payment callback را handle میکند):
|
||||
|
||||
| متد | مسیر | Permission | توضیح |
|
||||
|-----|------|-----------|-------|
|
||||
| GET | `/api/v1/subscription/plans` | public | لیست پنلها + دورهها + قیمتها |
|
||||
| GET | `/api/v1/subscription/my` | doctor/clinic | اشتراک فعال + `used_trial` flag |
|
||||
| POST | `/api/v1/subscription/trial` | doctor/clinic | فعالسازی تریال Basic (یکبار) |
|
||||
| GET | `/api/v1/admin/subscription/plans` | ROLE_ADMIN | لیست پنلها |
|
||||
| POST | `/api/v1/admin/subscription/plan` | ROLE_ADMIN | ایجاد پنل |
|
||||
| PATCH | `/api/v1/admin/subscription/plan/{uuid}` | ROLE_ADMIN | ویرایش پنل |
|
||||
| POST | `/api/v1/admin/subscription/period` | ROLE_ADMIN | افزودن دوره |
|
||||
| PATCH | `/api/v1/admin/subscription/period/{uuid}` | ROLE_ADMIN | ویرایش دوره |
|
||||
| DELETE | `/api/v1/admin/subscription/period/{uuid}` | ROLE_ADMIN | غیرفعالسازی |
|
||||
| GET | `/api/v1/admin/subscription/report` | ROLE_ADMIN | گزارش فروش |
|
||||
|
||||
نکته: `/api/v1/subscription-payment` قبلاً در `PaymentController` موجود است — callback آن هم موجود است. فقط باید بعد از callback موفق، `ClinicSubscription` ساخته شود (این وابستگی باید توضیح داده شود).
|
||||
|
||||
نمونه Response برای `GET /api/v1/subscription/my`:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"plan": { "name": "basic", "level": 1, "features": {"patient_records": true, "services": true} },
|
||||
"period": { "label": "۶ ماهه", "duration_months": 6 },
|
||||
"is_trial": false,
|
||||
"starts_at": 1718000000,
|
||||
"expires_at": 1733360000,
|
||||
"used_trial": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `architecture.md`:
|
||||
```
|
||||
src/Subscription/
|
||||
├── Controller/SubscriptionController.php
|
||||
├── Entity/
|
||||
│ ├── SubscriptionPlan.php
|
||||
│ ├── SubscriptionPeriod.php
|
||||
│ └── ClinicSubscription.php
|
||||
├── Repository/
|
||||
│ ├── SubscriptionPlanRepository.php
|
||||
│ ├── SubscriptionPeriodRepository.php
|
||||
│ └── ClinicSubscriptionRepository.php
|
||||
└── Service/
|
||||
└── SubscriptionService.php ← hasFeature() gate method
|
||||
```
|
||||
|
||||
`SubscriptionService::hasFeature(string $feature): bool`:
|
||||
```php
|
||||
public function hasFeature(string $feature): bool
|
||||
{
|
||||
// 1. entity_type + entity_id از authStore JWT claim
|
||||
// 2. آخرین ClinicSubscription فعال: expires_at > time() OR expires_at IS NULL
|
||||
// 3. plan->features[$feature] === true
|
||||
// برگشتی false → caller باید $this->error(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, ..., 403) بزند
|
||||
}
|
||||
```
|
||||
|
||||
Entityها با Doctrine annotations دقیق (نوع `json` برای features در SubscriptionPlan).
|
||||
|
||||
#### `database.md`:
|
||||
سه جدول کامل:
|
||||
|
||||
**subscription_plans:**
|
||||
- id, uuid, name VARCHAR(30) ('free'|'basic'|'professional'), level TINYINT, max_secretaries TINYINT
|
||||
- features JSON (e.g. `{"patient_records": true, "services": true, "sms_panel": false}`)
|
||||
- active TINYINT(1), created_at INT, updated_at INT
|
||||
|
||||
**subscription_periods:**
|
||||
- id, uuid, plan_id INT FK→subscription_plans.id
|
||||
- label VARCHAR(50), duration_months TINYINT, price_rials INT
|
||||
- is_trial TINYINT(1) DEFAULT 0, active TINYINT(1), sort_order TINYINT
|
||||
- created_at INT, updated_at INT
|
||||
|
||||
**clinic_subscriptions:**
|
||||
- id, uuid, entity_type VARCHAR(10), entity_id INT
|
||||
- plan_id INT FK→subscription_plans.id, period_id INT FK→subscription_periods.id
|
||||
- payment_id INT NULL FK→payments.id (NULL برای تریال)
|
||||
- is_trial TINYINT(1), starts_at INT, expires_at INT NULL (NULL = Free بینهایت)
|
||||
- created_at INT
|
||||
|
||||
ایندکسها:
|
||||
```sql
|
||||
INDEX idx_clinic_subscriptions_entity ON clinic_subscriptions(entity_type, entity_id, expires_at);
|
||||
UNIQUE idx_subscription_trial_once ON clinic_subscriptions(entity_type, entity_id, is_trial) -- جلوگیری از تریال مکرر
|
||||
```
|
||||
|
||||
**Seed data اولیه** (باید در migration یا fixture باشد):
|
||||
```sql
|
||||
INSERT INTO subscription_plans (uuid, name, level, max_secretaries, features, active) VALUES
|
||||
(UUID(), 'free', 0, 1, '{"patient_records":false,"services":false,"sms_panel":false}', 1),
|
||||
(UUID(), 'basic', 1, 2, '{"patient_records":true,"services":true,"sms_panel":false}', 1),
|
||||
(UUID(), 'professional', 2, 5, '{"patient_records":true,"services":true,"sms_panel":true}', 1);
|
||||
```
|
||||
|
||||
#### `user_flow.md`:
|
||||
- جریان خرید اشتراک (انتخاب پنل + دوره → payment → callback → ClinicSubscription)
|
||||
- جریان تریال (check used_trial → POST /trial → ClinicSubscription بدون payment_id)
|
||||
- جریان تمدید (expires_at قبلی + duration_months × 30 × 86400)
|
||||
- gate check flow: هر endpoint protected → `hasFeature()` → 403 اگر false
|
||||
|
||||
---
|
||||
|
||||
### ۳. ساخت `docs/tasks/task-12-secretary-completion/`
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۲: تکمیل منشی — محدودیت پنل + UI Permissions»
|
||||
- پیشنیازها: تسک ۱۴ (Secretary — موجود)، **تسک ۱۱** (Subscription)
|
||||
- زمان تخمینی: ۴ تا ۵ ساعت
|
||||
- **هیچ endpoint جدیدی نیست** — فقط تغییر در کد موجود:
|
||||
- Backend: `src/Secretary/Controller/SecretaryController.php::create()` — اضافه کردن gate check
|
||||
- Frontend: `assets/admin/pages/SecretariesPage.tsx` — Modal ویرایش permissions
|
||||
|
||||
نمونه کد Backend تغییر (قبل/بعد):
|
||||
```php
|
||||
// قبل:
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
// validate و ذخیره مستقیم
|
||||
|
||||
// بعد — باید اضافه شود:
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$limit = $this->subscriptionService->getSecretaryLimit(); // 1 یا 2 یا 5
|
||||
$current = $this->secretaryRepo->countActive($doctorId);
|
||||
if ($current >= $limit) {
|
||||
return $this->error(ErrorCodes::ERR_SECRETARY_LIMIT_REACHED, 'سقف منشی پنل رسیده است', 403);
|
||||
}
|
||||
```
|
||||
|
||||
نمونه کد Frontend permissions checkbox:
|
||||
```tsx
|
||||
// در SecretariesPage.tsx — Modal ویرایش
|
||||
// DoctorSecretary::DEFAULT_PERMISSIONS ساختار را نشان میدهد:
|
||||
// resources: { appointments: { view, create, cancel, update_status }, addresses: {...}, ... }
|
||||
// باید یک checkbox matrix رندر شود
|
||||
```
|
||||
|
||||
#### `architecture.md`:
|
||||
فایلهایی که تغییر میکنند (نه فایل جدید):
|
||||
- `src/Secretary/Controller/SecretaryController.php` — inject `SubscriptionService`
|
||||
- `assets/admin/pages/SecretariesPage.tsx` — Modal با checkbox matrix
|
||||
|
||||
#### `database.md`:
|
||||
- هیچ migration لازم نیست
|
||||
- توضیح ستون `permissions` در جدول `doctor_secretaries` (JSON)
|
||||
|
||||
#### `user_flow.md`:
|
||||
- جریان تلاش برای افزودن منشی جدید وقتی سقف پنل رسیده
|
||||
- جریان ویرایش permissions منشی موجود
|
||||
|
||||
---
|
||||
|
||||
### ۴. ساخت `docs/tasks/task-13-clinic-services/`
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۳: سرویسهای کلینیک (Clinic Services)»
|
||||
- پیشنیازها: **تسک ۱۰** (Staff)، **تسک ۱۱** (Subscription — gate check)
|
||||
- زمان تخمینی: ۸ تا ۱۰ ساعت
|
||||
- gate: همه write endpoints نیاز به `hasFeature('services')` دارند (Basic+ فقط)
|
||||
- Endpoints:
|
||||
|
||||
| متد | مسیر | توضیح |
|
||||
|-----|------|-------|
|
||||
| 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}` | حذف |
|
||||
|
||||
#### `architecture.md`:
|
||||
```
|
||||
src/ClinicService/
|
||||
├── Controller/ClinicServiceController.php
|
||||
├── Entity/
|
||||
│ ├── ServiceSection.php
|
||||
│ └── ServiceItem.php
|
||||
└── Repository/
|
||||
├── ServiceSectionRepository.php
|
||||
└── ServiceItemRepository.php
|
||||
```
|
||||
|
||||
#### `database.md`:
|
||||
|
||||
**service_sections:**
|
||||
- id, uuid, entity_type VARCHAR(10), entity_id INT, name VARCHAR(200), active TINYINT(1) DEFAULT 1
|
||||
- created_at INT, updated_at INT
|
||||
- INDEX: `idx_service_sections_entity ON service_sections(entity_type, entity_id)`
|
||||
|
||||
**service_items:**
|
||||
- id, uuid, section_id INT FK→service_sections.id ON DELETE CASCADE
|
||||
- staff_id INT NULL FK→clinic_staff.id ON DELETE SET NULL
|
||||
- name VARCHAR(200), price_rials INT NOT NULL DEFAULT 0, active TINYINT(1) DEFAULT 1
|
||||
- created_at INT, updated_at INT
|
||||
|
||||
#### `user_flow.md`:
|
||||
- جریان ایجاد ساختار دو سطحی (بخش → زیربخش)
|
||||
- جریان انتخاب پرسنل انجامدهنده
|
||||
- gate check — کاربر Free تلاش میکند سرویس بسازد → 403
|
||||
|
||||
---
|
||||
|
||||
### ۵. ساخت `docs/tasks/task-14-sms-panel/`
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۴: پنل پیامکی — کیف پول + تنظیمات»
|
||||
- پیشنیازها: تسک ۱۷ (SMS infrastructure موجود)، تسک ۱۵ (Payment موجود)
|
||||
- زمان تخمینی: ۱۰ تا ۱۲ ساعت
|
||||
- نکته: `src/Sms/` و `src/Settlement/` موجودند — کد جدید در `src/Sms/` اضافه میشود
|
||||
- Endpoints جدید:
|
||||
|
||||
| متد | مسیر | Permission | توضیح |
|
||||
|-----|------|-----------|-------|
|
||||
| GET | `/api/v1/sms/wallet/balance` | doctor/clinic | موجودی |
|
||||
| POST | `/api/v1/sms/wallet/charge` | doctor/clinic | شارژ — `{ gateway, amount_rials }` |
|
||||
| GET | `/api/v1/sms/wallet/logs` | doctor/clinic | تاریخچه کسر/شارژ |
|
||||
| GET | `/api/v1/sms/settings` | doctor/clinic | تنظیمات |
|
||||
| PATCH | `/api/v1/sms/settings` | doctor/clinic | ذخیره تنظیمات |
|
||||
| GET | `/api/v1/admin/sms/wallet-report` | ROLE_ADMIN | گزارش مصرف |
|
||||
|
||||
#### `architecture.md`:
|
||||
Entity های جدید در `src/Sms/Entity/`:
|
||||
```php
|
||||
// SmsWallet.php — الگو از WalletTransaction.php در src/Settlement/Entity/
|
||||
// SmsSettings.php — key-value per entity
|
||||
```
|
||||
|
||||
Controller جدید: `src/Sms/Controller/SmsWalletController.php`
|
||||
شارژ کیف پول: redirect به gateway موجود با `Payment.type = 'sms_wallet'` (const جدید در Payment entity)
|
||||
|
||||
#### `database.md`:
|
||||
|
||||
**sms_wallets:**
|
||||
- id, entity_type VARCHAR(10), entity_id INT (UNIQUE PAIR)
|
||||
- balance_rials INT NOT NULL DEFAULT 0, created_at INT, updated_at INT
|
||||
- UNIQUE INDEX: `idx_sms_wallets_entity ON sms_wallets(entity_type, entity_id)`
|
||||
|
||||
**sms_wallet_transactions:**
|
||||
- id, uuid, sms_wallet_id INT FK→sms_wallets.id
|
||||
- type VARCHAR(10) ('credit'|'debit'), amount_rials INT, description VARCHAR(255) NULL
|
||||
- payment_id INT NULL FK→payments.id, created_at INT
|
||||
|
||||
**sms_settings:**
|
||||
- id, entity_type VARCHAR(10), entity_id INT (UNIQUE PAIR)
|
||||
- reminder_enabled TINYINT(1) DEFAULT 0, reminder_hours_before TINYINT DEFAULT 2
|
||||
- post_visit_enabled TINYINT(1) DEFAULT 0, post_visit_text TEXT NULL, updated_at INT
|
||||
- UNIQUE INDEX: `idx_sms_settings_entity ON sms_settings(entity_type, entity_id)`
|
||||
|
||||
SiteConfig key جدید: `sms_price_rials` (ادمین مقدار میدهد)
|
||||
|
||||
#### `user_flow.md`:
|
||||
- جریان شارژ کیف پول (مثل payment اما `type='sms_wallet'`)
|
||||
- جریان کسر خودکار هنگام ارسال پیامک (hook در SmsService)
|
||||
- جریان تنظیم یادآوری خودکار
|
||||
|
||||
---
|
||||
|
||||
### ۶. ساخت `docs/tasks/task-15-patient-records/`
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۵: پرونده بیمار (Patient Records)»
|
||||
- پیشنیازها: **تسک ۱۰** (Staff)، **تسک ۱۱** (Subscription)، **تسک ۱۳** (Services)
|
||||
- زمان تخمینی: ۱۶ تا ۲۰ ساعت
|
||||
- gate: همه endpoints نیاز به `hasFeature('patient_records')` (Basic+ فقط)
|
||||
- Endpoints:
|
||||
|
||||
| متد | مسیر | توضیح |
|
||||
|-----|------|-------|
|
||||
| GET | `/api/v1/patients` | لیست بیماران — query: `?search=` |
|
||||
| POST | `/api/v1/patient` | ایجاد پرونده دستی |
|
||||
| GET | `/api/v1/patient/{uuid}` | جزئیات پرونده + خلاصه |
|
||||
| GET | `/api/v1/patient/{uuid}/sessions` | لیست مراجعات |
|
||||
| POST | `/api/v1/patient/{uuid}/session` | ثبت مراجعه جدید |
|
||||
| PATCH | `/api/v1/session/{uuid}` | ویرایش مراجعه |
|
||||
|
||||
نمونه Request ثبت سشن:
|
||||
```json
|
||||
{
|
||||
"appointment_uuid": "optional-uuid",
|
||||
"insurance_base_uuid": "category-uuid",
|
||||
"insurance_supplementary_uuid": null,
|
||||
"visit_price_rials": 500000,
|
||||
"base_insurance_discount_percent": 30,
|
||||
"supplementary_discount_percent": 0,
|
||||
"payment_method": "cash",
|
||||
"notes": "...",
|
||||
"services": [
|
||||
{ "service_item_uuid": "...", "staff_uuid": "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
نمونه Response (final_price محاسبهشده):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"visit_price_rials": 500000,
|
||||
"base_insurance_discount_percent": 30,
|
||||
"supplementary_discount_percent": 0,
|
||||
"services_total_rials": 150000,
|
||||
"final_price_rials": 500000,
|
||||
"payment_method": "cash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
فرمول محاسبه:
|
||||
```
|
||||
after_base = visit_price × (1 - base_discount/100)
|
||||
after_supp = after_base × (1 - supp_discount/100)
|
||||
final = round(after_supp) + sum(service_item.price_rials)
|
||||
```
|
||||
|
||||
#### `architecture.md`:
|
||||
```
|
||||
src/Patient/
|
||||
├── Controller/PatientController.php
|
||||
├── Entity/
|
||||
│ ├── PatientRecord.php
|
||||
│ ├── PatientSession.php
|
||||
│ └── SessionService.php
|
||||
├── Repository/
|
||||
│ ├── PatientRecordRepository.php
|
||||
│ └── PatientSessionRepository.php
|
||||
└── Service/
|
||||
└── PatientService.php ← calculateFinalPrice(), autoCreateOnAppointmentConfirm()
|
||||
```
|
||||
|
||||
نکته در architecture.md: `autoCreateOnAppointmentConfirm()` باید از `AppointmentController::updateStatus()` صدا شود — وقتی status به `'confirmed'` تغییر میکند، PatientRecord چک/ایجاد میشود.
|
||||
|
||||
#### `database.md`:
|
||||
|
||||
**patient_records:**
|
||||
- id, uuid, entity_type VARCHAR(10), entity_id INT
|
||||
- user_id INT FK→users.id ON DELETE RESTRICT
|
||||
- created_by_type VARCHAR(15) ('doctor'|'secretary'|'system'), created_by_id INT
|
||||
- created_at INT
|
||||
- UNIQUE: `idx_patient_record_unique ON patient_records(entity_type, entity_id, user_id)`
|
||||
|
||||
**patient_sessions:**
|
||||
- id, uuid, record_id INT FK→patient_records.id ON DELETE CASCADE
|
||||
- appointment_id INT NULL FK→appointments.id ON DELETE SET NULL
|
||||
- insurance_base_id INT NULL FK→categories.id, insurance_supplementary_id INT NULL FK→categories.id
|
||||
- visit_price_rials INT NOT NULL DEFAULT 0
|
||||
- base_insurance_discount_percent DECIMAL(5,2) DEFAULT 0
|
||||
- supplementary_discount_percent DECIMAL(5,2) DEFAULT 0
|
||||
- services_total_rials INT NOT NULL DEFAULT 0
|
||||
- final_price_rials INT NOT NULL
|
||||
- payment_method VARCHAR(15) ('cash'|'card'|'insurance'|'pending')
|
||||
- notes TEXT NULL, created_at INT, updated_at INT
|
||||
|
||||
**session_services:**
|
||||
- id, uuid, session_id INT FK→patient_sessions.id ON DELETE CASCADE
|
||||
- service_item_id INT FK→service_items.id ON DELETE RESTRICT
|
||||
- staff_id INT NULL FK→clinic_staff.id ON DELETE SET NULL
|
||||
- price_rials INT NOT NULL ← کپی از service_item.price_rials در زمان ثبت
|
||||
- created_at INT
|
||||
|
||||
ایندکسها:
|
||||
```sql
|
||||
INDEX idx_patient_records_entity ON patient_records(entity_type, entity_id);
|
||||
INDEX idx_patient_sessions_record ON patient_sessions(record_id, created_at);
|
||||
```
|
||||
|
||||
#### `user_flow.md`:
|
||||
- جریان خودکار: تأیید نوبت → چک PatientRecord → ایجاد اگر نبود → ایجاد PatientSession
|
||||
- جریان دستی: منشی/دکتر → جستجو بیمار → ایجاد پرونده → ثبت مراجعه + سرویسها
|
||||
- محاسبه final_price با step by step مثال عددی
|
||||
- جریان جستجو بیمار (search در patients API)
|
||||
|
||||
---
|
||||
|
||||
### ۷. ساخت `docs/tasks/task-16-smart-dashboard/`
|
||||
|
||||
#### `task.md`:
|
||||
- عنوان: «تسک ۱۶: داشبورد هوشمند — چارت + فیلتر زمانی»
|
||||
- پیشنیازها: همه تسکهای قبل — نیاز به داده واقعی دارد
|
||||
- زمان تخمینی: ۸ تا ۱۰ ساعت
|
||||
- **هیچ endpoint جدیدی نیست** — فقط پارامتر `from` و `to` به endpoint های موجود اضافه میشود:
|
||||
- `GET /api/v1/admin/dashboard/charts?from=UNIX&to=UNIX`
|
||||
- `GET /api/v1/dashboard/clinic?from=UNIX&to=UNIX`
|
||||
- `GET /api/v1/dashboard/doctor?from=UNIX&to=UNIX`
|
||||
|
||||
فیلدهای جدید در response داشبورد کلینیک/دکتر:
|
||||
```json
|
||||
{
|
||||
"sms_wallet_balance": 150000,
|
||||
"unique_patients_count": 45,
|
||||
"revenue_current_month": 12500000
|
||||
}
|
||||
```
|
||||
|
||||
#### `architecture.md`:
|
||||
فایلهایی که تغییر میکنند:
|
||||
- `src/Dashboard/Controller/DashboardController.php` — اضافه کردن `from`/`to` به query
|
||||
- `assets/admin/pages/DashboardPage.tsx` — date range selector + نمودار (Recharts)
|
||||
|
||||
نکته: Recharts پیشنهادی است (سبک، tree-shakeable). نصب با `yarn add recharts`.
|
||||
|
||||
#### `database.md`:
|
||||
- هیچ migration لازم نیست
|
||||
- query های DQL که باید به `from`/`to` محدود شوند
|
||||
|
||||
#### `user_flow.md`:
|
||||
- کاربر date range انتخاب میکند → charts refresh میشوند
|
||||
- preset ها: «این هفته» / «این ماه» / «۳ ماه» / «سفارشی»
|
||||
- تبدیل تاریخ شمسی به Unix timestamp برای ارسال به API
|
||||
|
||||
---
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **ترتیب پیادهسازی اجباری است**: task-10 → task-11 → task-12 → task-13 → task-14 → task-15 → task-16
|
||||
- **حذف سخت ممنوع** در ClinicStaff — فقط `active=false`
|
||||
- **UNIQUE constraint در تریال**: یک entity نمیتواند دو بار از تریال استفاده کند — index روی `(entity_type, entity_id, is_trial)` در `clinic_subscriptions`
|
||||
- **price_rials در session_services کپی میشود** — تغییر قیمت سرویس در آینده تأثیری روی سشنهای قبلی ندارد
|
||||
- **patient_records UNIQUE per entity+user** — یک بیمار یک پرونده در هر کلینیک/مطب دارد (مراجعات چندگانه = سشنهای جداگانه در همان پرونده)
|
||||
- **فایل doc** برای هر اپیک بعد از پیادهسازی باید ایجاد شود (`docs/api/staff.md`، `docs/api/subscription.md`، `docs/api/clinic-services.md`، `docs/api/patient.md`)
|
||||
Reference in New Issue
Block a user