# تسک ۱۴: ماژول منشی ## توضیح مدیریت منشی‌های دکترها که می‌توانند نوبت‌ها و پرداخت‌ها را مدیریت کنند. هر دکتر بسته به پلن اشتراک می‌تواند ۱ یا ۳ منشی فعال داشته باشد. ## Endpoint ها | متد | مسیر | توضیح | نیاز به Auth | |-----|------|-------|-------------| | POST | `/api/v1/secretary` | ایجاد منشی | بله (Doctor/Admin) | | PATCH | `/api/v1/secretary/{uuid}` | ویرایش منشی | بله (Doctor/Admin) | | GET | `/api/v1/secretary/{uuid}` | دریافت اطلاعات منشی | بله | | DELETE | `/api/v1/secretary/{uuid}` | حذف منشی | بله (Doctor/Admin) | | GET | `/api/v1/secretaries/{doctorUuid}` | لیست منشی‌های دکتر | بله | ## پیش‌نیازها - تسک ۰۱، ۰۲، ۰۵ (Doctor) ## زمان تخمینی ۵ تا ۶ ساعت --- ## سیستم مجوزها — Resource-Based Permissions (مقیاس‌پذیر) فیلد `permissions` در جدول `doctor_secretaries` یک JSON ساختاریافته با نسخه‌بندی است. طراحی به گونه‌ای است که در آینده بتوان منابع (`resources`) و عملیات (`actions`) جدید اضافه کرد بدون تغییر در ساختار جدول. ### ساختار JSON ```json { "version": 1, "resources": { "appointments": { "view": true, "create": true, "cancel": false, "update_status": true }, "addresses": { "view": true, "create": true, "update": true, "delete": false }, "clinic_info": { "view": true, "update": false }, "insurances": { "view": true, "create": true, "update": true, "delete": false } } } ``` ### منابع و عملیات فعلی | Resource | Actions | توضیح | |----------|---------|-------| | `appointments` | `view`, `create`, `cancel`, `update_status` | نوبت‌ها | | `addresses` | `view`, `create`, `update`, `delete` | آدرس‌های مطب/کلینیک | | `clinic_info` | `view`, `update` | اطلاعات مطب یا کلینیک | | `insurances` | `view`, `create`, `update`, `delete` | بیمه‌ها | ### مقیاس‌پذیری — اضافه کردن Resource جدید در آینده برای اضافه کردن Resource جدید (مثلاً `patients` یا `reports`) فقط کافی است: 1. کلید جدید به JSON اضافه شود — بدون migration جدید 2. کد Permission Checker به صورت خودکار آن را پشتیبانی می‌کند 3. منشی‌های موجود که کلید جدید را ندارند، به صورت پیش‌فرض `false` دارند ### پیاده‌سازی PHP — SecretaryPermissionChecker ```php // src/Secretary/Security/SecretaryPermissionChecker.php class SecretaryPermissionChecker { /** * بررسی مجوز منشی برای یک عملیات روی یک منبع * مثال: $checker->can($secretary, 'appointments', 'create') */ public function can(Secretary $secretary, string $resource, string $action): bool { if (!$secretary->isActive()) { return false; } $permissions = $secretary->getPermissions(); return (bool) ($permissions['resources'][$resource][$action] ?? false); } /** * بررسی دسترسی کامل به یک منبع (همه actions باید true باشند) */ public function canAll(Secretary $secretary, string $resource, array $actions): bool { return array_reduce( $actions, fn($carry, $action) => $carry && $this->can($secretary, $resource, $action), true ); } } ``` **مثال استفاده در Controller:** ```php // در AppointmentController if (!$this->permissionChecker->can($secretary, 'appointments', 'create')) { throw new AccessDeniedHttpException('منشی مجاز به ثبت نوبت نیست'); } // در InsuranceController if (!$this->permissionChecker->can($secretary, 'insurances', 'delete')) { throw new AccessDeniedHttpException('منشی مجاز به حذف بیمه نیست'); } ``` ### پیش‌فرض هنگام ایجاد منشی ```json { "version": 1, "resources": { "appointments": { "view": true, "create": true, "cancel": false, "update_status": true }, "addresses": { "view": true, "create": false, "update": false, "delete": false }, "clinic_info": { "view": true, "update": false }, "insurances": { "view": true, "create": false, "update": false, "delete": false } } } ``` --- ## POST /api/v1/secretary ```json // Request { "mobile_number": "09120671756", "doctor_uuid": "61be915b-...", "permissions": { "version": 1, "resources": { "appointments": { "view": true, "create": true, "cancel": false, "update_status": true }, "addresses": { "view": true, "create": false, "update": false, "delete": false }, "clinic_info": { "view": true, "update": false }, "insurances": { "view": true, "create": false, "update": false, "delete": false } } } } // Response 201 { "success": true, "data": { "uuid": "...", "user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" }, "doctor": { "uuid": "...", "name": "دکتر احمدی" }, "active": true, "permissions": { ... }, "created_at": 1748000000 } } // Response 422 — حد مجاز منشی { "success": false, "errors": [{ "code": "ERR_SECRETARY_001", "message": "پلن فعلی اجازه منشی بیشتر را نمی‌دهد" }] } ``` **قانون بررسی پلن (سمت سرور):** ``` پلن بیسیک → max 1 منشی فعال پلن پیشرفته → max 3 منشی فعال هنگام POST /secretary: activeCount = COUNT(*) WHERE doctor_id=X AND active=true if activeCount >= maxAllowed → 422 ``` --- ## PATCH /api/v1/secretary/{uuid} ```json // Request (فقط resources موردنظر — deep merge با پیش‌فرض‌ها) { "active": false, "permissions": { "resources": { "insurances": { "create": true, "update": true } } } } // نکته: فقط resources/actions ارسال‌شده تغییر می‌کنند — بقیه دست‌نخورده می‌مانند ``` // Response 200 { "success": true, "data": { ... } } ``` --- ## GET /api/v1/secretary/{uuid} ```json { "success": true, "data": { "uuid": "...", "user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756", "picture": null }, "doctor": { "uuid": "...", "name": "دکتر احمدی" }, "active": true, "permissions": { "version": 1, "resources": { "appointments": { "view": true, "create": true, "cancel": false, "update_status": true }, "addresses": { "view": true, "create": false, "update": false, "delete": false }, "clinic_info": { "view": true, "update": false }, "insurances": { "view": true, "create": false, "update": false, "delete": false } } }, "created_at": 1748000000 } } ``` --- ## GET /api/v1/secretaries/{doctorUuid} ```json { "success": true, "data": [ { "uuid": "...", "user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" }, "active": true, "permissions": { ... }, "created_at": 1748000000 } ] } ``` --- ## نکات مهم - **کاربر منشی:** هنگام ایجاد منشی با mobile_number، ابتدا بررسی می‌شود آیا کاربر با این شماره وجود دارد — اگر نه، کاربر جدید ایجاد می‌شود - **ROLE:** کاربر منشی باید role `doctor_s_secretary` داشته باشد - **لاگین منشی:** منشی می‌تواند با username/password لاگین کند (تسک ۰۲) - **بررسی پلن:** کاملاً سمت سرور انجام می‌شود، قابل دور زدن نیست - **نوبت آفلاین:** نوبتی که منشی ثبت می‌کند (`appointments.create`) کمیسیون نماینده ندارد - **PATCH permissions:** فقط resources/actions ارسال‌شده تغییر می‌کنند (deep merge) — بقیه دست‌نخورده - **Resource ناشناخته:** اگر resource جدیدی در JSON باشد که سرور نمی‌شناسد، نادیده گرفته می‌شود (forward compat) - **پیش‌فرض `false`:** اگر resource یا action در JSON وجود نداشته باشد → `false` (deny by default)