Implement SMS panel user flow and patient records system; add wallet charging, automatic reminders, and patient session management with detailed database schema and user flows.

This commit is contained in:
hamed
2026-06-14 21:15:40 +03:30
parent a72a6da621
commit b58aacc37f
28 changed files with 3169 additions and 0 deletions
@@ -0,0 +1,160 @@
# تسک ۱۱: پنل اشتراکی (Subscription Tiers)
## توضیح
پیاده‌سازی سیستم پنل‌های اشتراکی سه‌سطحی. ادمین دوره‌ها و قیمت‌ها را تعریف می‌کند.
این تسک **gate check** برای تسک‌های ۱۲، ۱۳، ۱۵ فراهم می‌کند.
**نکته مهم:** `/api/v1/subscription-payment` و callback آن از قبل در `PaymentController` موجود است.
در این تسک فقط باید بعد از callback موفق، `ClinicSubscription` ساخته شود + endpoint های Subscription خودش.
## سطوح پنل
| Level | Name | منشی | patient_records | services |
|-------|------|------|----------------|---------|
| 0 | free | ۱ | ❌ | ❌ |
| 1 | basic | ۲ | ✅ | ✅ |
| 2 | professional | ۵ | ✅ | ✅ |
## Endpoint ها
| متد | مسیر | Permission | توضیح |
|-----|------|-----------|-------|
| GET | `/api/v1/subscription/plans` | public | لیست پنل‌ها + دوره‌ها + قیمت |
| GET | `/api/v1/subscription/my` | doctor/clinic | اشتراک فعال + `used_trial` |
| 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 | گزارش فروش + تریال‌ها |
**موجود (تغییر نمی‌کند):**
- `POST /api/v1/subscription-payment` — شروع پرداخت (body: `{ period_uuid }`)
- `GET|POST /api/v1/subscription-payment/callback/{gateway}` — callback gateway
## پیش‌نیازها
- تسک ۰۲ (Auth/JWT)
- تسک ۱۵-payment (PaymentController — موجود)
## زمان تخمینی
۱۴ تا ۱۶ ساعت
## نمونه Request
### POST /api/v1/subscription/trial
```json
{}
```
(body خالی — entity از JWT گرفته می‌شود)
### POST /api/v1/admin/subscription/period
```json
{
"plan_uuid": "uuid-of-basic-plan",
"label": "۶ ماهه",
"duration_months": 6,
"price_rials": 1200000,
"is_trial": false,
"sort_order": 3
}
```
### POST /api/v1/subscription-payment (موجود)
```json
{
"period_uuid": "uuid-of-selected-period",
"gateway": "mellat"
}
```
## نمونه Response
### GET /api/v1/subscription/plans
```json
{
"success": true,
"data": [
{
"uuid": "...",
"name": "basic",
"level": 1,
"max_secretaries": 2,
"features": { "patient_records": true, "services": true, "sms_panel": false },
"periods": [
{ "uuid": "...", "label": "تریال ۱ ماهه", "duration_months": 1, "price_rials": 0, "is_trial": true },
{ "uuid": "...", "label": "۱ ماهه", "duration_months": 1, "price_rials": 250000, "is_trial": false },
{ "uuid": "...", "label": "۶ ماهه", "duration_months": 6, "price_rials": 1200000, "is_trial": false }
]
}
]
}
```
### 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,
"days_remaining": 42
}
}
```
وقتی اشتراک فعال ندارد:
```json
{
"success": true,
"data": {
"plan": { "name": "free", "level": 0 },
"expires_at": null,
"used_trial": false
}
}
```
### POST /api/v1/subscription/trial (موفق)
```json
{
"success": true,
"data": {
"plan": "basic",
"starts_at": 1718000000,
"expires_at": 1720678400
}
}
```
### POST /api/v1/subscription/trial (خطا — قبلاً استفاده شده)
```json
{
"success": false,
"errors": [{ "code": "ERR_TRIAL_ALREADY_USED", "message": "قبلاً از تریال استفاده کرده‌اید" }]
}
```
## قوانین تریال
- فقط برای پنل **basic** (level=1)
- هر entity یک‌بار — constraint `UNIQUE(entity_type, entity_id, is_trial)` + مقدار `is_trial=1` در DB
- بدون پرداخت — `payment_id = null`
- بعد از انقضا → برگشت به Free (داده‌ها حفظ می‌شوند)
- ادمین می‌تواند تریال را کلاً غیرفعال کند: `SiteConfig.trial_enabled = false`
## قانون تمدید
```
expires_at جدید = max(expires_at فعلی, time()) + duration_months × 30 × 86400
```
یعنی اگر اشتراک هنوز منقضی نشده، تمدید از تاریخ انقضا محاسبه می‌شود (نه از now).
## gate check در تسک‌های بعدی
```php
// SubscriptionService::hasFeature('patient_records') → bool
// false → $this->error(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, '...', 403)
```