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,92 @@
# پایگاه داده — تسک ۱۱: پنل اشتراکی (Subscription Tiers)
## جدول: subscription_plans
| ستون | نوع | توضیح |
|------|-----|-------|
| id | INT UNSIGNED AUTO_INCREMENT PK | |
| uuid | CHAR(36) UNIQUE NOT NULL | |
| name | VARCHAR(30) NOT NULL | `'free'` \| `'basic'` \| `'professional'` |
| level | TINYINT NOT NULL | 0, 1, یا 2 |
| max_secretaries | TINYINT NOT NULL | 1, 2، یا 5 |
| features | JSON NOT NULL | `{"patient_records": bool, "services": bool, "sms_panel": bool}` |
| active | TINYINT(1) NOT NULL DEFAULT 1 | |
| created_at | INT NOT NULL | Unix timestamp |
| updated_at | INT NOT NULL | Unix timestamp |
## جدول: subscription_periods
| ستون | نوع | توضیح |
|------|-----|-------|
| id | INT UNSIGNED AUTO_INCREMENT PK | |
| uuid | CHAR(36) UNIQUE NOT NULL | |
| plan_id | INT NOT NULL FK→subscription_plans.id ON DELETE CASCADE | |
| label | VARCHAR(50) NOT NULL | مثال: `'۶ ماهه'`, `'تریال ۱ ماهه'` |
| duration_months | TINYINT NOT NULL | مدت به ماه |
| price_rials | INT NOT NULL | `0` برای تریال |
| is_trial | TINYINT(1) NOT NULL DEFAULT 0 | |
| active | TINYINT(1) NOT NULL DEFAULT 1 | |
| sort_order | TINYINT NOT NULL DEFAULT 0 | ترتیب نمایش |
| created_at | INT NOT NULL | |
| updated_at | INT NOT NULL | |
## جدول: clinic_subscriptions
| ستون | نوع | توضیح |
|------|-----|-------|
| 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 | id دکتر یا کلینیک |
| plan_id | INT NOT NULL FK→subscription_plans.id | |
| period_id | INT NOT NULL FK→subscription_periods.id | |
| payment_id | INT NULL FK→payments.id ON DELETE SET NULL | `NULL` برای تریال |
| is_trial | TINYINT(1) NOT NULL DEFAULT 0 | |
| starts_at | INT NOT NULL | Unix timestamp |
| expires_at | INT NULL | `NULL` = بی‌نهایت (Free) |
| created_at | INT NOT NULL | |
## ایندکس‌ها
```sql
-- جستجوی سریع اشتراک فعال
CREATE INDEX idx_clinic_subscriptions_entity
ON clinic_subscriptions(entity_type, entity_id, expires_at);
-- جلوگیری از استفاده مکرر از تریال
-- توجه: این constraint روی is_trial=1 کار می‌کند چون is_trial=0 می‌تواند تکراری باشد
-- بنابراین در application check می‌کنیم نه UNIQUE index
CREATE INDEX idx_clinic_subscriptions_trial
ON clinic_subscriptions(entity_type, entity_id, is_trial);
```
## Seed Data (Migration اولیه)
```sql
-- سه پنل پایه — باید در migration ایجاد شوند
INSERT INTO subscription_plans (uuid, name, level, max_secretaries, features, active, created_at, updated_at) VALUES
(UUID(), 'free', 0, 1, '{"patient_records":false,"services":false,"sms_panel":false}', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 'basic', 1, 2, '{"patient_records":true,"services":true,"sms_panel":false}', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 'professional', 2, 5, '{"patient_records":true,"services":true,"sms_panel":true}', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- دوره‌های نمونه برای basic (ادمین بعداً قیمت‌ها را ویرایش می‌کند)
-- plan_id=2 = basic
INSERT INTO subscription_periods (uuid, plan_id, label, duration_months, price_rials, is_trial, active, sort_order, created_at, updated_at) VALUES
(UUID(), 2, 'تریال ۱ ماهه', 1, 0, 1, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 2, '۱ ماهه', 1, 250000, 0, 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 2, '۳ ماهه', 3, 690000, 0, 1, 2, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 2, '۶ ماهه', 6, 1200000, 0, 1, 3, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(UUID(), 2, '۱۲ ماهه', 12, 2000000, 0, 1, 4, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
```
## SiteConfig key های جدید
| کلید | نوع | مقدار پیش‌فرض | توضیح |
|------|-----|--------------|-------|
| `trial_enabled` | string `'1'`\|`'0'` | `'1'` | ادمین می‌تواند تریال را غیرفعال کند |
## نکات مهم
- `expires_at = NULL` فقط برای رکوردهای Free plan است — این‌ها بی‌نهایت معتبرند
- query اشتراک فعال: `WHERE entity_type=? AND entity_id=? AND (expires_at IS NULL OR expires_at > UNIX_TIMESTAMP()) ORDER BY id DESC LIMIT 1`
- تریال check در application: `SELECT COUNT(*) FROM clinic_subscriptions WHERE entity_type=? AND entity_id=? AND is_trial=1`