# Secretary API > **Prefix:** `/api/v1/secretary`, `/api/v1/secretaries` ## مدل Scope هر رابطه منشی-پزشک دارای یک **scope** است که از تداخل بین محیط‌های مختلف جلوگیری می‌کند: | Scope | `owner_type` | تعریف‌کننده | دسترسی | | -------- | ------------ | ----------- | ------------------------------- | | مطب شخصی | `doctor` | خود پزشک | فقط نوبت‌ها و داده‌های مطب شخصی | | کلینیک | `clinic` | مدیر کلینیک | فقط نوبت‌ها و داده‌های کلینیک | - یک منشی می‌تواند هم در مطب شخصی یک دکتر و هم در کلینیک همان دکتر فعال باشد (دو ردیف مجزا) - منشی کلینیک می‌تواند به چند دکتر در همان کلینیک متصل باشد - **یک منشی می‌تواند به همان دکتر در چند کلینیک متفاوت تخصیص یابد** (یک ردیف به ازای هر کلینیک). تا پیش از این، کلید یکتا فقط `(doctor_id, secretary_id, owner_type)` بود و کلینیکِ دوم را تکراری می‌شمرد؛ حالا خودِ محیط هم بخشی از هویت رابطه است - scope فعال در runtime از جدول `user_active_context` خوانده می‌شود — `db_uuid` به‌همراه `db_type` که می‌گوید 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 }, "inventory": { "view": false, "create": false, "update": false, "delete": false }, "tags": { "view": false, "create": false, "update": false, "delete": false }, "services": { "view": false, "create": false, "update": false, "delete": false }, "staff": { "view": false, "create": false, "update": false, "delete": false }, "discounts": { "view": false, "create": false, "update": false, "delete": false }, "sms": { "view": false, "create": false, "update": false, "delete": false }, "appointment_settings": { "view": false, "update": false }, "clinic_doctors": { "view": false, "create": false, "update": false, "delete": false }, "subscription": { "view": false, "create": 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) بر اساس صفحات و ماژول‌های در دسترسِ منشی است: `appointments`, `patients`, `payments`, `insurances`, `addresses`, `clinic_info`, `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors`, `subscription`. منبعِ `subscription` فقط `view/create` دارد. `mergePermissions` هر منبع/اکشن ارسال‌شده را deep-merge می‌کند. منابعِ `inventory`, `tags`, `services`, `staff`, `discounts`, `sms`, `appointment_settings`, `clinic_doctors` به‌صورت پیش‌فرض همه `false`‌اند (default-deny)؛ بقیه طبق `DEFAULT_PERMISSIONS`. منبعِ `appointment_settings` فقط `view/update` دارد. منبعِ `clinic_doctors` **فقط در حالت کلینیک** معنا دارد (پزشک مستقل نه toggle نه منو). **اعمال (enforcement):** همهٔ منابع در بک‌اند enforce می‌شوند، نه فقط `appointments`. منبعِ حقیقت، ستون JSON `permission` روی ردیفِ فعالِ `DoctorSecretary` در محیطِ فعالِ کاربر (`UserActiveContext.db_uuid`) است؛ نقطهٔ مرکزی `App\Secretary\Security\SecretaryAccessChecker` (`can` / `canOrNonSecretary` / `denyUnlessGranted`). نبودِ مجوز → `403 ERR_FORBIDDEN_001`. نقشه: | Resource | Enforced in | Action → endpoint | | --- | --- | --- | | `appointments` | `AppointmentAccessChecker`, `MyAppointmentsController`, `DashboardController` | view/create/cancel/update_status | | `patients` | `PatientController` (خواندن‌ها via `scope()` → بدون `view` هیچ پرونده‌ای؛ افزودن/ویرایشِ زیرآیتم‌ها = `update`؛ **حذفِ** یادداشت/سند/رکورد/تماس/پیام = `delete` — جدا از `update`) | view/create/update/delete | | `payments` | `PaymentController::myPayments`, `PaymentMethodController` (bank/pos), `PatientController` (کیف‌پول + پرداختِ جلسه) | view/create/update/delete | | `insurances` | `InsuranceController` (insurance-pricing, tenant-insurances, service-coverage, doctor-insurance) | view/create/update/delete | | `inventory` | `InventoryController` (items + packages) | view/create/update/delete | | `tags` | `TenantTagController` (لیست با `tags.view` یا `patients.view`؛ نوشتن‌ها با `tags.*`) | view/create/update/delete | | `services` | `ClinicServiceController` (sections + items + tariffs). owner از محیطِ فعال با `SecretaryAccessChecker::resolveOwnerEntity` حل می‌شود چون `EntityContextResolver` منشی را نمی‌شناسد. گیتِ `services.*` پیش از گیتِ اشتراک اجرا می‌شود | view/create/update/delete | | `staff` | `StaffController` (resolveEntity منشی‌آگاه) | view/create/update/delete | | `discounts` | `DiscountController` (CRUD؛ `suggestions` جزو flowِ جلسه است و با discounts گِیت نمی‌شود) | view/create/update/delete | | `sms` | `SmsWalletController` (balance/charge/logs/settings). endpointهای admin (قالب/ارسال) همچنان `ROLE_ADMIN` | view/create/update | | `appointment_settings` | `AppointmentSettingsController::denyDoctorAccess` → `SecretaryAccessChecker::canForDoctor` (اسکوپِ پزشکِ تخصیص‌یافته + توگل). `clinic_uuid` برای محیطِ کلینیک لازم است | view/update | | `clinic_doctors` (فقط کلینیک) | `ClinicController::detachDoctor` (delete)، `ClinicDoctorPermissionController` (view/update)، `ClinicInvitationController` (create/view/update/delete) via `SecretaryAccessChecker::canForClinic` | view/create/update/delete | | `subscription` | `SubscriptionController::my` (view) و `trial` (create)، `PaymentController::initiateSubscription` (create). resolveEntity از قبل منشی‌آگاه است | view/create | نقش‌های غیرمنشی (`ROLE_CLINIC`/`ROLE_DOCTOR`/`ROLE_ADMIN`) از این چک عبور می‌کنند (`canOrNonSecretary` برایشان `true`). منشیِ بدون رابطهٔ فعال/context هیچ مجوزی ندارد → همه‌چیز `403`. ```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 }, "inventory": { "view": false, // انبار: مشاهده "create": false, // ایجاد کالا/بسته "update": false, "delete": false }, "tags": { "view": false, // تگ‌ها؛ لیست با tags.view یا patients.view "create": false, "update": false, "delete": 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 **in this same environment** — همان منشی برای همان پزشک در کلینیکِ دیگر ۴۰۹ نمی‌گیرد | | `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` (پرداخت) ارتقاء داد. --- ## سهم منشی از نوبت‌های آنلاین (درآمد و تسویه) ادمین می‌تواند برای هر رابطهٔ منشی–پزشک/کلینیک، محاسبهٔ درآمد از نوبت‌های آنلاین را فعال کند و درصد بدهد ([admin.md](admin.md#put-apiv1adminsecretaryuuidonline-share)). سهم از **مبلغ خالص** نوبت گرفته می‌شود: ابتدا هزینهٔ پنل پیامک، بعد مالیات، سپس درصدِ منشی روی «خالصِ پس از مالیات» — همان مبنایی که پورسانت نماینده از آن محاسبه می‌شود ([settlement.md](settlement.md)). **«آنلاین» یعنی چه؟** تقسیم مالی تنها پس از پرداخت موفق درگاه (`PaymentManager`) اجرا می‌شود؛ نوبتی که در پنل ثبت و «قطعی» می‌شود از این مسیر عبور نمی‌کند و سهمی نمی‌سازد. انتساب بر پایهٔ محیط نوبت است: کلینیکِ نوبت، وگرنه خودِ پزشک. اگر چند منشیِ سهم‌بر وجود داشته باشد، **هر کدام درصد خودش** را می‌گیرد (تقسیم نمی‌شود)؛ اگر مجموع درصدها از ۱۰۰ بگذرد به نسبت کلیپ می‌شود و هشدار لاگ می‌گردد تا سهم سیستم منفی نشود. سهم هر منشی در جدول `secretary_earnings` ثبت و به‌صورت اعتبار در کیف پول همان کاربر منظور می‌شود؛ برداشت از طریق `POST /api/v1/settlement` انجام می‌گیرد. --- ### GET `/api/v1/secretary/earnings/summary` خلاصهٔ درآمد منشیِ جاری. **Permission:** `AUTH` (کاربر منشی) #### Response `200` ```json { "success": true, "data": { "data": { "enabled": true, "share_percent": 5, "relations": [ { "relation_uuid": "rel-…", "doctor_name": "دکتر احمدی", "clinic_name": null, "share_percent": 5 } ], "today_rials": 500000, "this_month_rials": 3000000, "total_rials": 9000000, "appointments_count": 4, "wallet_balance_rials": 9000000 } } } ``` | فیلد | توضیح | |------|-------| | `enabled` | `false` یعنی هیچ رابطهٔ فعالی با سهمِ روشن ندارد؛ پنل پیام «فعال نیست» نشان می‌دهد (خطا نمی‌دهیم) | | `share_percent` | درصد اولین رابطهٔ سهم‌بر؛ تفکیک کامل در `relations` | | `today_rials` | از نیمه‌شب امروز | | `this_month_rials` | ۳۰ روز گذشته | | `wallet_balance_rials` | موجودی کیف پول همان کاربر (مبنای تسویه) | --- ### GET `/api/v1/secretary/earnings/report` گزارش سطر-به-سطر سهم منشی (paginated). **Permission:** `AUTH` (کاربر منشی) #### Query Parameters | Param | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | ❌ | پیش‌فرض ۱ | | `limit` | integer | ❌ | پیش‌فرض ۱۵، حداکثر ۱۰۰ | | `from` | integer | ❌ | Unix — از تاریخ | | `to` | integer | ❌ | Unix — تا تاریخ | #### Response `200` ```json { "success": true, "data": [ { "uuid": "earning-uuid-…", "appointment_uuid": "appt-uuid-…", "doctor_name": "دکتر احمدی", "gross_rials": 10000000, "sms_fee_rials": 1000000, "tax_rials": 818182, "net_after_tax_rials": 8181818, "share_percent": 5, "share_rials": 409091, "created_at": 1700000000 } ], "meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 } } ``` منشیِ بدون سهم، پاسخ `200` با آرایهٔ خالی می‌گیرد (نه `403`). --- ### GET `/api/v1/secretary/me` پروفایل منشیِ جاری: رابطه‌ها با تنظیمات سهم + شماره‌های شبا. **Permission:** `AUTH` (کاربر منشی) #### Response `200` ```json { "success": true, "data": { "data": { "full_name": "زهرا رضایی", "mobile": "0912…", "bank_account": [ { "id": "iban-uuid-…", "iban": "IR…", "bank_name": "ملی", "owner_name": null, "verified": false, "created_at": 1700000000 } ], "relations": [ { "relation_uuid": "rel-…", "doctor_name": "دکتر احمدی", "clinic_name": null, "online_share_enabled": true, "online_share_percent": 5 } ] } } } ``` --- ### POST `/api/v1/secretary/iban` افزودن شماره شبا (حداکثر ۲) به پروفایل کاربرِ منشی — مثل پنل نماینده. **Permission:** `AUTH` (کاربر منشی) #### Request Body (`application/json`) ```json { "iban": "IR123456789012345678901234", "bank_name": "ملی", "owner_name": "زهرا رضایی" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `iban` | string | ✅ | الگوی `IR` + ۲۴ رقم (فاصله‌ها حذف می‌شود) | | `bank_name` | string | ❌ | نام بانک | | `owner_name` | string | ❌ | نام صاحب حساب | #### Response `201` ```json { "success": true, "data": { "data": { "bank_account": [ { "id": "…", "iban": "IR…", "verified": false } ] } } } ``` `verified` همیشه `false` ثبت می‌شود؛ **تأیید فقط از سمت ادمین** انجام می‌گیرد و تسویه تنها با شبای تأییدشده مجاز است. #### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_VALIDATION_001` | 422 | شبا نامعتبر (`field: iban`) | | `ERR_VALIDATION_001` | 422 | بیش از دو شبا (`field: iban`) | --- ### DELETE `/api/v1/secretary/iban/{id}` حذف یکی از شباهای منشیِ جاری. **Permission:** `AUTH` (کاربر منشی) #### Response `200` `{ success, data: { data: { bank_account: [...] } } }` #### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_NOT_FOUND_001` | 404 | پروفایل/شبا یافت نشد | > کیف پول و تسویه اندپوینت اختصاصی ندارند: `GET /api/v1/wallet/balance`، `GET /api/v1/wallet/transactions` و `POST /api/v1/settlement` کاربر-محورند ([settlement.md](settlement.md)).