- Redesign MySecretariesPage pixel-perfect to clinic-pro-tauri (active/previous tabs, desktop table, mobile cards, add/edit/view modal with permission accordions, deactivate confirm) - Permission sections based on existing admin pages (appointments, patients, payments, insurances, addresses, clinic_info) - Extend DoctorSecretary with national_code + address columns (+migration); wire create/update in SecretaryController; add patients/payments to DEFAULT_PERMISSIONS - Extend Secretary/SecretaryPermissions types; update admin SecretariesPage - Backend + frontend tests; update docs/api/secretary.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
400 lines
13 KiB
Markdown
400 lines
13 KiB
Markdown
# Secretary API
|
|
|
|
> **Prefix:** `/api/v1/secretary`, `/api/v1/secretaries`
|
|
|
|
## مدل Scope
|
|
|
|
هر رابطه منشی-پزشک دارای یک **scope** است که از تداخل بین محیطهای مختلف جلوگیری میکند:
|
|
|
|
| Scope | `owner_type` | تعریفکننده | دسترسی |
|
|
| -------- | ------------ | ----------- | ------------------------------- |
|
|
| مطب شخصی | `doctor` | خود پزشک | فقط نوبتها و دادههای مطب شخصی |
|
|
| کلینیک | `clinic` | مدیر کلینیک | فقط نوبتها و دادههای کلینیک |
|
|
|
|
- یک منشی میتواند هم در مطب شخصی یک دکتر و هم در کلینیک همان دکتر فعال باشد (دو ردیف مجزا)
|
|
- منشی کلینیک میتواند به چند دکتر در همان کلینیک متصل باشد
|
|
- scope فعال در runtime از جدول `user_active_context` (db_uuid) خوانده میشود
|
|
|
|
Secretaries are linked to a doctor and have granular permissions controlling what they can do on behalf of the doctor.
|
|
|
|
---
|
|
|
|
## POST `/api/v1/secretary`
|
|
|
|
Create a secretary for a doctor.
|
|
|
|
**Permission:** `ROLE_DOCTOR` (must own the doctor — creates `owner_type='doctor'`) | `ROLE_CLINIC` (must have the doctor in its clinic — creates `owner_type='clinic'`) | `ROLE_ADMIN`
|
|
|
|
### Request Body (`application/json`)
|
|
|
|
```json
|
|
{
|
|
"doctor_uuid": "550e8400-...",
|
|
"mobile_number": "09123456789",
|
|
"name": "سارا احمدی",
|
|
"national_code": "1234567890",
|
|
"address": "یزد، خیابان تست",
|
|
"password": "secretaryPass123",
|
|
"permissions": {
|
|
"version": 1,
|
|
"resources": {
|
|
"appointments": {
|
|
"view": true,
|
|
"create": true,
|
|
"cancel": false,
|
|
"update_status": true
|
|
},
|
|
"patients": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"payments": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"insurances": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"addresses": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"clinic_info": { "view": true, "update": false }
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
| --------------- | ------------- | -------- | -------------------------------------------- |
|
|
| `doctor_uuid` | string (UUID) | ✅ | Doctor to assign secretary to |
|
|
| `mobile_number` | string | ✅ | Secretary's login mobile |
|
|
| `name` | string | ❌ | Full name (نام + نام خانوادگی) → `user_name` |
|
|
| `national_code` | string | ❌ | کد ملی منشی (nullable) |
|
|
| `address` | string | ❌ | آدرس منشی (nullable) |
|
|
| `password` | string | ❌ | Initial password (auto-generated if omitted) |
|
|
| `permissions` | object | ❌ | Permission set (see structure below) |
|
|
|
|
**Permissions Structure:**
|
|
|
|
مجموعهٔ منابع (resources) بر اساس صفحات موجود پنل ادمین است. `mergePermissions` هر منبع/اکشن ارسالشده را deep-merge میکند؛ فقط `appointments` در بکاند enforce میشود (`MyAppointmentsController`, `DashboardController`)، بقیه UI/ذخیرهای هستند.
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"resources": {
|
|
"appointments": {
|
|
"view": true, // Can view appointments list
|
|
"create": true, // Can book appointments
|
|
"cancel": false, // Can cancel appointments
|
|
"update_status": true // Can mark as completed/no_show
|
|
},
|
|
"patients": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"payments": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"insurances": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"addresses": {
|
|
"view": true,
|
|
"create": false,
|
|
"update": false,
|
|
"delete": false
|
|
},
|
|
"clinic_info": {
|
|
"view": true,
|
|
"update": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response `201`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "sec-uuid-...",
|
|
"user_name": "علی محمدی",
|
|
"mobile_number": "09123456789",
|
|
"doctor_name": "دکتر احمد رضایی",
|
|
"doctor_uuid": "...",
|
|
"owner_type": "doctor",
|
|
"clinic_uuid": null,
|
|
"is_active": true,
|
|
"national_code": "1234567890",
|
|
"address": "یزد، خیابان تست",
|
|
"permissions": { ... },
|
|
"created_at": 1717000000
|
|
}
|
|
}
|
|
```
|
|
|
|
**مقادیر `owner_type`:**
|
|
| مقدار | معنی |
|
|
|-------|------|
|
|
| `doctor` | منشی توسط خود دکتر تعریف شده — فقط مطب شخصی |
|
|
| `clinic` | منشی توسط مدیر کلینیک تعریف شده — فقط کلینیک |
|
|
|
|
**پیامک خوشآمد:** پس از ساخت موفق منشی، یک پیامک بهصورت async به `mobile_number` منشی ارسال میشود (تگ `secretary`). متن از قالب ویرایشپذیر DB میآید (fallback به پیشفرض) و placeholderهای `{owner}` (نام دکتر یا کلینیک بسته به `owner_type`)، `{username}` (موبایل منشی) و `{link}` (لینک ورود) را جایگزین میکند. ویرایش متن از `PATCH /api/v1/admin/sms/messages/secretary` — رجوع به [sms.md](sms.md).
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | ------------------------------------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_AUTH_006` | 403 | Not the doctor owner / clinic owner / admin |
|
|
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
|
| `ERR_CONFLICT_001` | 409 | Secretary already added for this doctor |
|
|
| `ERR_SECRETARY_001` | 422 | Plan limit for secretaries reached |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/secretary/{uuid}`
|
|
|
|
Get secretary detail.
|
|
|
|
**Permission:** `AUTH` — must be the linked doctor or `ROLE_ADMIN`
|
|
|
|
### Path Parameters
|
|
|
|
| Param | Type | Description |
|
|
| ------ | ------------- | -------------- |
|
|
| `uuid` | string (UUID) | Secretary UUID |
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "...",
|
|
"mobile_number": "09123456789",
|
|
"active": true,
|
|
"permissions": { ... },
|
|
"doctor": { "uuid": "...", "title": "دکتر علی احمدی" },
|
|
"created_at": 1717000000
|
|
}
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | ------------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not authorized |
|
|
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
|
|
|
---
|
|
|
|
## PATCH `/api/v1/secretary/{uuid}`
|
|
|
|
Update secretary active status, profile fields (name/national_code/address), or permissions. تمام فیلدها اختیاریاند و فقط موارد ارسالشده اعمال میشوند.
|
|
|
|
**Permission:** `ROLE_DOCTOR` — must be the linked doctor
|
|
|
|
### Request Body (`application/json`)
|
|
|
|
```json
|
|
{
|
|
"active": false,
|
|
"name": "نام جدید",
|
|
"national_code": "9999999999",
|
|
"address": "آدرس جدید",
|
|
"permissions": {
|
|
"version": 1,
|
|
"resources": {
|
|
"appointments": {
|
|
"view": true,
|
|
"create": false,
|
|
"cancel": false,
|
|
"update_status": false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
| --------------- | ------- | -------- | -------------------------------------------- |
|
|
| `active` | boolean | ❌ | Enable/disable secretary |
|
|
| `name` | string | ❌ | بهروزرسانی نام کامل منشی (`user_name`) |
|
|
| `national_code` | string | ❌ | بهروزرسانی کد ملی (nullable) |
|
|
| `address` | string | ❌ | بهروزرسانی آدرس (nullable) |
|
|
| `permissions` | object | ❌ | New permissions object (deep-merged) |
|
|
|
|
### Response `200`
|
|
|
|
Updated secretary object.
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | --------------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not the linked doctor |
|
|
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
|
|
|
---
|
|
|
|
## DELETE `/api/v1/secretary/{uuid}`
|
|
|
|
Delete a secretary.
|
|
|
|
**Permission:** `ROLE_DOCTOR` — must be the linked doctor
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{ "success": true, "data": { "message": "منشی حذف شد" } }
|
|
```
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | --------------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not the linked doctor |
|
|
| `ERR_NOT_FOUND_001` | 404 | Secretary not found |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/secretaries/{doctorUuid}`
|
|
|
|
Get all secretaries for a specific doctor.
|
|
|
|
**Permission:** `ROLE_DOCTOR` (must own doctor) | `ROLE_CLINIC` (must have doctor in clinic) | `ROLE_ADMIN`
|
|
|
|
### Path Parameters
|
|
|
|
| Param | Type | Description |
|
|
| ------------ | ------------- | ----------- |
|
|
| `doctorUuid` | string (UUID) | Doctor UUID |
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"user_name": "علی محمدی",
|
|
"mobile_number": "09...",
|
|
"doctor_name": "دکتر احمد رضایی",
|
|
"doctor_uuid": "...",
|
|
"is_active": true,
|
|
"permissions": { ... },
|
|
"created_at": 1717000000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | ---------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not authorized |
|
|
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |
|
|
|
|
---
|
|
|
|
## GET `/api/v1/secretaries/clinic/{clinicUuid}`
|
|
|
|
Get all secretaries across **all doctors** of a clinic.
|
|
|
|
**Permission:** `ROLE_CLINIC` (must own clinic) | `ROLE_ADMIN`
|
|
|
|
### Path Parameters
|
|
|
|
| Param | Type | Description |
|
|
| ------------ | ------------- | ----------- |
|
|
| `clinicUuid` | string (UUID) | Clinic UUID |
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"user_name": "علی محمدی",
|
|
"mobile_number": "09...",
|
|
"doctor_name": "دکتر احمد رضایی",
|
|
"doctor_uuid": "...",
|
|
"is_active": true,
|
|
"permissions": { ... },
|
|
"created_at": 1717000000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- این endpoint فقط منشی های را برمیگرداند که با `owner_type='clinic'` تعریف شدهاند
|
|
- منشی های که خود دکتر (با `owner_type='doctor'`) تعریف کرده از این لیست مخفی هستند
|
|
- یک منشی میتواند به چند دکتر در همان کلینیک متصل باشد — در لیست چندبار ظاهر میشود (یک ردیف به ازای هر دکتر)
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| ------------------- | ---- | ---------------- |
|
|
| `ERR_AUTH_001` | 401 | Missing token |
|
|
| `ERR_FORBIDDEN_001` | 403 | Not clinic owner |
|
|
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
|
|
|
|
---
|
|
|
|
## محدودیت پنل اشتراکی
|
|
|
|
تعداد منشیهای مجاز بر اساس پنل فعال doctor تعیین میشود:
|
|
|
|
| پنل | حداکثر منشی |
|
|
| ------------------ | ----------- |
|
|
| Free (بدون اشتراک) | ۱ |
|
|
| Basic | ۳ |
|
|
| Professional | ۱۰ |
|
|
|
|
اگر تعداد منشیهای فعال به حد مجاز رسیده باشد، ایجاد منشی جدید خطای زیر را برمیگرداند:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"errors": [
|
|
{
|
|
"code": "ERR_SECRETARY_001",
|
|
"message": "پلن فعلی اجازه منشی بیشتر را نمیدهد"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
برای افزایش محدودیت، باید پنل را از `POST /api/v1/subscription/trial` (تریال) یا `POST /api/v1/subscription-payment` (پرداخت) ارتقاء داد.
|