# پایگاه داده — تسک ۱۱: پنل اشتراکی (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`