feat(secretary): implement multi-doctor assignment for clinic secretaries
- Added functionality to assign a single secretary to multiple doctors within a clinic, allowing for scoped access to appointments. - Introduced `SecretaryService` to handle the logic for assigning and syncing doctors for a secretary. - Updated `SecretaryController` to support multi-doctor assignment via new endpoints and modified existing ones. - Enhanced `DoctorSecretary` entity to include secretary UUID in its serialized output. - Implemented repository methods to facilitate the retrieval and management of doctor-secretary relationships. - Adjusted appointment filtering in `MyAppointmentsController` to ensure secretaries only see appointments for assigned doctors. - Created tests to validate the new multi-doctor assignment functionality and appointment access restrictions. - Updated frontend components to support multi-select for doctors in the secretary management UI.
This commit is contained in:
+81
-2
@@ -14,6 +14,7 @@
|
||||
- یک منشی میتواند هم در مطب شخصی یک دکتر و هم در کلینیک همان دکتر فعال باشد (دو ردیف مجزا)
|
||||
- منشی کلینیک میتواند به چند دکتر در همان کلینیک متصل باشد
|
||||
- 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.
|
||||
|
||||
@@ -76,7 +77,8 @@ Create a secretary for a doctor.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| --------------- | ------------- | -------- | -------------------------------------------- |
|
||||
| `doctor_uuid` | string (UUID) | ✅ | Doctor to assign secretary to |
|
||||
| `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 |
|
||||
| `name` | string | ❌ | Full name (نام + نام خانوادگی) → `user_name` |
|
||||
| `national_code` | string | ❌ | کد ملی منشی (nullable) |
|
||||
@@ -84,6 +86,25 @@ Create a secretary for a doctor.
|
||||
| `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/ذخیرهای هستند.
|
||||
@@ -344,6 +365,7 @@ Get all secretaries across **all doctors** of a clinic.
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"secretary_uuid": "...",
|
||||
"user_name": "علی محمدی",
|
||||
"mobile_number": "09...",
|
||||
"doctor_name": "دکتر احمد رضایی",
|
||||
@@ -360,7 +382,7 @@ Get all secretaries across **all doctors** of a clinic.
|
||||
|
||||
- این endpoint فقط منشی های را برمیگرداند که با `owner_type='clinic'` تعریف شدهاند
|
||||
- منشی های که خود دکتر (با `owner_type='doctor'`) تعریف کرده از این لیست مخفی هستند
|
||||
- یک منشی میتواند به چند دکتر در همان کلینیک متصل باشد — در لیست چندبار ظاهر میشود (یک ردیف به ازای هر دکتر)
|
||||
- یک منشی میتواند به چند دکتر در همان کلینیک متصل باشد — در لیست چندبار ظاهر میشود (یک ردیف به ازای هر دکتر). برای گروهبندی «یک منشی، چند پزشک» از `secretary_uuid` (uuid کاربرِ منشی) استفاده کنید
|
||||
|
||||
### Errors
|
||||
|
||||
@@ -372,6 +394,63 @@ Get all secretaries across **all doctors** of a clinic.
|
||||
|
||||
---
|
||||
|
||||
## PUT `/api/v1/secretaries/clinic/{clinicUuid}/doctors`
|
||||
|
||||
همگامسازی مجموعهی پزشکانِ یک منشیِ کلینیک (owner_type='clinic'): پزشکانِ خواستهشده افزوده/فعال و بقیه غیرفعال میشوند. برای «افزودن/حذف پزشک از یک منشی موجود» بدون تغییر ساختاری.
|
||||
|
||||
**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 تعیین میشود:
|
||||
|
||||
Reference in New Issue
Block a user