feat(subscription): implement resource quota management based on subscription plans

This commit is contained in:
hamed
2026-08-04 19:38:49 +03:30
parent 0dca245246
commit 3e7028d77a
16 changed files with 408 additions and 18 deletions
+22
View File
@@ -193,6 +193,28 @@
| `attributes` | object | — | حداکثر ۲۰ کلید · کلید `[a-z_]{1,40}` · مقدار فقط اسکالر |
| `active` | bool | — | پیش‌فرض `true` |
### سقف منابع بر اساس پلن اشتراک (2026-08)
ساخت منبع به سقفِ پلنِ **مؤثرِ** محیط محدود است — `subscription_plans.max_resources`:
| پلن | سقف |
|---|---|
| بدون اشتراک فعال (`free`) | ۱ منبع |
| `basic` — شامل دورهٔ آزمایشی | ۳ منبع |
| `professional` | نامحدود (`-1`) |
- شمارش روی **همهٔ** منابع همان جفتِ محیط است: فعال و غیرفعال، و منابعِ پلِ پزشک/پرسنل
هم شمرده می‌شوند. غیرفعال‌کردن جای خالی نمی‌سازد؛ فقط حذف می‌سازد.
- سقف پیش از هر اعتبارسنجی دیگری سنجیده می‌شود، پس بدنهٔ ناقص هم همین خطا را می‌گیرد.
- سقف فقط روی همین اندپوینت است. پلِ خودکارِ منبع برای پزشک/پرسنل مسدود نمی‌شود، ولی
در شمارش می‌آید.
- **۴۲۲ `ERR_RESOURCE_LIMIT_001`:** «پلن فعلی حداکثر N منبع را پشتیبانی می‌کند؛ برای
افزودن، پنل را ارتقا دهید».
سقف در `GET /api/v1/subscription/my` زیر `effective_plan.max_resources` می‌آید؛ پنل با
همان و شمارشِ `GET /api/v1/resources` دکمهٔ افزودن را می‌بندد. تست:
`tests/Resource/ResourceQuotaTest.php`.
### شعبه از منابع حذف شد (2026-08)
منابع دامنهٔ «شعبه» ندارند: دستگاه و اتاق مالِ خودِ کلینیک‌اند، و آن انتخابگر همیشه یک
+11 -4
View File
@@ -8,7 +8,9 @@
لیست پنل‌ها با دوره‌های فعال (عمومی — بدون auth).
> پلن `free`: همه امکانات (`patient_records`, `services`, `sms_panel`, `insurance`) فعال‌اند؛ تنها محدودیت آن تعداد منشی (`max_secretaries`) است.
> پلن `free`: همه امکانات (`patient_records`, `services`, `sms_panel`, `insurance`) فعال‌اند؛ محدودیت‌هایش عددی‌اند — تعداد منشی (`max_secretaries`) و تعداد منبع (`max_resources`).
> `max_resources` سقف منابع محیط است. مقدار `-1` یعنی نامحدود. مقادیر شیپ‌شده: `free` = ۱، `basic` = ۳، `professional` = `-1`. اجرای این سقف در `POST /api/v1/resource` است — [resource.md](resource.md).
**Response 200:**
```json
@@ -20,6 +22,7 @@
"name": "free",
"level": 0,
"max_secretaries": 1,
"max_resources": 1,
"features": { "patient_records": true, "services": true, "sms_panel": true, "insurance": true },
"active": true,
"periods": []
@@ -29,6 +32,7 @@
"name": "basic",
"level": 1,
"max_secretaries": 3,
"max_resources": 3,
"features": { "patient_records": true, "services": true, "sms_panel": false },
"active": true,
"periods": [
@@ -65,7 +69,7 @@
"data": {
"subscription": {
"uuid": "...",
"plan": { "name": "basic", "level": 1, "max_secretaries": 3, "features": {...} },
"plan": { "name": "basic", "level": 1, "max_secretaries": 3, "max_resources": 3, "features": {...} },
"period": { "label": "یک ماهه", "duration_months": 1, "price_rials": 290000 },
"is_trial": false,
"starts_at": 1718000000,
@@ -74,7 +78,7 @@
"is_active": true
},
"used_trial": false,
"effective_plan": { "name": "basic", "level": 1, "max_secretaries": 3, "features": {...} }
"effective_plan": { "name": "basic", "level": 1, "max_secretaries": 3, "max_resources": 3, "features": {...} }
}
}
```
@@ -94,7 +98,7 @@
{"success":true,"data":{
"subscription": null,
"used_trial": false,
"effective_plan": { "features": { "patient_records": true, "…": true }, "max_secretaries": 1 }
"effective_plan": { "features": { "patient_records": true, "…": true }, "max_secretaries": 1, "max_resources": 1 }
}}
```
@@ -180,10 +184,13 @@ callback درگاه پرداخت — پس از پرداخت موفق، `ClinicSu
"name": "enterprise",
"level": 3,
"max_secretaries": 20,
"max_resources": -1,
"features": { "patient_records": true, "services": true, "sms_panel": true }
}
```
`max_resources` اختیاری است و پیش‌فرضش `1` — پلنِ ناشناخته نباید بی‌صدا نامحدود شود. مقدار `-1` یعنی نامحدود. در `PATCH` هم همین فیلد پذیرفته می‌شود.
**خطاها:**
| کد | HTTP | شرح |