- Removed the "دکتر" prefix from doctor names in various components and API responses to ensure consistency and clarity. - Updated the AppointmentDetailPage, CommentsPage, DashboardPage, RatingsPage, SecretariesPage, and other relevant files to reflect the changes in doctor name formatting. - Adjusted API documentation to align with the new naming conventions. - Implemented validation to prevent the creation of clinics without a name and restricted users to a single clinic. - Added tests to verify that doctor names are stored without titles and that clinic creation adheres to the new validation rules.
481 lines
18 KiB
Markdown
481 lines
18 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) خوانده میشود
|
|
- **محدودسازی به پزشکانِ تخصیصیافته:** منشیِ کلینیک فقط نوبتهای پزشکانی را میبیند/رزرو میکند که واقعاً به او تخصیص داده شدهاند — نه همهی پزشکان کلینیک. لیست نوبت (`GET /api/v1/my/appointments`) با `a.doctor IN (پزشکانِ تخصیصیافته)` فیلتر میشود و گیت رزرو (`POST /api/v1/my/appointment`) رابطهی فعالِ همان (منشی، کلینیک، پزشک) را چک میکند. permission رزرو از همان ردیفِ پزشک خوانده میشود
|
|
|
|
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) | ✅\* | Single doctor to assign (legacy/doctor flow) |
|
|
| `doctor_uuids` | string[] (UUID) | ✅\* | **Clinic only** — assign one secretary to several clinic doctors at once. When present (non-empty) and caller is `ROLE_CLINIC`, this multi-doctor path is used instead of `doctor_uuid` |
|
|
| `mobile_number` | string | ✅ | Secretary's login mobile. Persian/Arabic digits are accepted and normalized server-side — see [README → Persian digit normalization](README.md#persian-digit-normalization-global) |
|
|
| `name` | string | ❌ | Full name (نام + نام خانوادگی) → `user_name` |
|
|
| `national_code` | string | ❌ | کد ملی منشی (nullable). Persian/Arabic digits accepted and normalized |
|
|
| `address` | string | ❌ | آدرس منشی (nullable) |
|
|
| `password` | string | ❌ | Initial password (auto-generated if omitted) |
|
|
| `permissions` | object | ❌ | Permission set (see structure below) |
|
|
|
|
\* یکی از `doctor_uuid` (تکپزشکی) یا `doctor_uuids` (چندپزشکیِ کلینیک) الزامی است.
|
|
|
|
**پاسخِ حالت چندپزشکی (`doctor_uuids` + `ROLE_CLINIC`) — `201`:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"secretary_uuid": "550e8400-...",
|
|
"created": [ { "uuid": "...", "secretary_uuid": "...", "doctor_uuid": "...", "...": "..." } ],
|
|
"skipped_duplicate": [],
|
|
"skipped_limit": [],
|
|
"skipped_not_in_clinic": []
|
|
}
|
|
}
|
|
```
|
|
|
|
- `created`: ردیفهای تازهساخته/فعالشده · `skipped_duplicate`: قبلاً متصل بوده · `skipped_limit`: سقفِ پلنِ آن پزشک پر است · `skipped_not_in_clinic`: پزشک عضو کلینیک نیست. حلقه اتمیک است و بقیهی پزشکان ادامه مییابند.
|
|
|
|
**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": "...",
|
|
"secretary_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'`) تعریف کرده از این لیست مخفی هستند
|
|
- یک منشی میتواند به چند دکتر در همان کلینیک متصل باشد — در لیست چندبار ظاهر میشود (یک ردیف به ازای هر دکتر). برای گروهبندی «یک منشی، چند پزشک» از `secretary_uuid` (uuid کاربرِ منشی) استفاده کنید
|
|
|
|
### 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 |
|
|
|
|
---
|
|
|
|
## PUT `/api/v1/secretaries/clinic/{clinicUuid}/doctors`
|
|
|
|
همگامسازی مجموعهی پزشکانِ یک منشیِ کلینیک (owner_type='clinic'): پزشکانِ خواستهشده افزوده/فعال و بقیه غیرفعال میشوند. برای «افزودن/حذف پزشک از یک منشی موجود» بدون تغییر ساختاری.
|
|
|
|
> ردیفهای تازهساختهشده `national_code`، `address` و `permissions` را از ردیفهای موجودِ همان منشی کپی میکنند تا پروفایل یک شخص روی همهی پزشکانش یکسان بماند. اگر همراه با ویرایش پروفایل صدا زده میشود، اول `PATCH /api/v1/secretary/{uuid}` روی ردیفهای موجود و بعد این اندپوینت را فراخوانی کنید.
|
|
|
|
**Permission:** `ROLE_CLINIC` (must own clinic) | `ROLE_ADMIN`
|
|
|
|
### Path Parameters
|
|
|
|
| Param | Type | Description |
|
|
| ------------ | ------------- | ----------- |
|
|
| `clinicUuid` | string (UUID) | Clinic UUID |
|
|
|
|
### Request Body (`application/json`)
|
|
|
|
```json
|
|
{
|
|
"secretary_uuid": "550e8400-...",
|
|
"doctor_uuids": ["uuid-doc-a", "uuid-doc-b"]
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
| ---------------- | --------------- | -------- | ------------------------------------------------------------ |
|
|
| `secretary_uuid` | string (UUID) | ✅ | uuid کاربرِ منشی (همان `secretary_uuid` خروجی لیست/ساخت) |
|
|
| `doctor_uuids` | string[] (UUID) | ✅ | مجموعهی نهاییِ پزشکان؛ نبودها افزوده، اضافهها غیرفعال میشوند |
|
|
|
|
### Response `200`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"added": 1,
|
|
"removed": 1,
|
|
"skipped_limit": [],
|
|
"skipped_not_in_clinic": []
|
|
}
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
| ----------------------- | -------- | ------------------------------------------------ |
|
|
| `added` | int | تعداد ردیفهای افزوده/فعالشده |
|
|
| `removed` | int | تعداد ردیفهای غیرفعالشده |
|
|
| `skipped_limit` | string[] | uuid پزشکانی که به سقفِ پلن رسیدهاند (نادیده گرفته) |
|
|
| `skipped_not_in_clinic` | string[] | uuid پزشکانی که عضو این کلینیک نیستند |
|
|
|
|
### Errors
|
|
|
|
| Code | HTTP | Description |
|
|
| -------------------- | ---- | ------------------------------------ |
|
|
| `ERR_AUTH_006` | 403 | Not clinic owner nor admin |
|
|
| `ERR_VALIDATION_001` | 422 | `secretary_uuid`/`doctor_uuids` missing |
|
|
| `ERR_VALIDATION_002` | 404 | Clinic or secretary 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` (پرداخت) ارتقاء داد.
|