# Insurance API > **Prefix:** `/api/v1/insurances`, `/api/v1/insurance`, `/api/v1/admin/insurance` Two resource types: 1. **Insurance** — master list of insurance companies managed by admin 2. **DoctorInsurance** — a doctor's acceptance of a specific insurance (with optional price) --- ## GET `/api/v1/insurances` List all active insurances. **Permission:** `PUBLIC` ### Query Parameters | Param | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | ❌ | `"basic"` or `"supplementary"` | ### Response `200` ```json { "success": true, "data": [ { "id": 1, "name": "بیمه تأمین اجتماعی", "type": "basic", "logo_url": "https://...", "status": "active" }, { "id": 2, "name": "بیمه ایران", "type": "supplementary", "logo_url": "https://...", "status": "active" } ] } ``` --- ## GET `/api/v1/admin/insurances` List all insurances with pagination (admin view — includes inactive). **Permission:** `ROLE_ADMIN` ### Query Parameters | Param | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | ❌ | Default: 1 | | `limit` | integer | ❌ | Default: 20 | | `search` | string | ❌ | Search in name | | `type` | string | ❌ | `"basic"` or `"supplementary"` | ### Response `200` ```json { "success": true, "data": [ ... ], "meta": { "totalRecords": 15, "totalPages": 1, "currentPage": 1 } } ``` ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing token | | `ERR_AUTH_006` | 403 | Not admin | --- ## POST `/api/v1/admin/insurance` Create a new insurance. **Permission:** `ROLE_ADMIN` ### Request Body (`application/json`) ```json { "name": "بیمه تأمین اجتماعی", "type": "basic", "logo_url": "https://...", "status": "active" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | ✅ | Insurance name | | `type` | string | ✅ | `"basic"` or `"supplementary"` | | `logo_url` | string | ❌ | Logo image URL | | `status` | string | ❌ | `"active"` (default) or `"inactive"` | ### Response `201` Insurance object. ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing token | | `ERR_AUTH_006` | 403 | Not admin | | `ERR_VALIDATION_002` | 422 | Missing required field | --- ## PATCH `/api/v1/admin/insurance/{id}` Update an insurance. **Permission:** `ROLE_ADMIN` ### Path Parameters | Param | Type | Description | |-------|------|-------------| | `id` | integer | Insurance ID | All body fields optional. ### Response `200` Updated insurance object. ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing token | | `ERR_AUTH_006` | 403 | Not admin | | `ERR_NOT_FOUND_001` | 404 | Insurance not found | --- ## DELETE `/api/v1/admin/insurance/{id}` Delete an insurance. **Permission:** `ROLE_ADMIN` ### Response `200` ```json { "success": true, "data": { "message": "بیمه حذف شد" } } ``` --- ## POST `/api/v1/admin/insurance/{id}/upload-logo` Upload insurance logo. **Permission:** `ROLE_ADMIN` ### Request `Content-Type: multipart/form-data` | Field | Type | Required | |-------|------|----------| | `file` | binary | ✅ | ### Response `200` ```json { "success": true, "data": { "url": "https://...", "uuid": "...", "filename": "insurance_logo.png", "filemime": "image/png", "filesize": 51200 } } ``` --- ## POST `/api/v1/insurance/` Add an insurance to a doctor's accepted list. **Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.create` permission) ### Request Body (`application/json`) ```json { "doctor_id": 42, "insurance_id": 1, "price": 150000 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `doctor_id` | integer | ✅ | Doctor's numeric ID | | `insurance_id` | integer | ✅ | Insurance ID | | `price` | integer | ❌ | Visit price for this insurance (Rials) | ### Response `201` ```json { "success": true, "data": { "id": 10, "doctor_id": 42, "insurance": { "id": 1, "name": "بیمه تأمین اجتماعی", "type": "basic" }, "price": 150000 } } ``` ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing token | | `ERR_FORBIDDEN_001` | 403 | Not the doctor | | `ERR_NOT_FOUND_001` | 404 | Doctor or insurance not found | | `ERR_CONFLICT_001` | 409 | Insurance already added to doctor | --- ## GET `/api/v1/insurance/{id}` Get a doctor-insurance link. **Permission:** `PUBLIC` ### Response `200` DoctorInsurance object. --- ## PATCH `/api/v1/insurance/{id}` Update a doctor-insurance (e.g., change price). **Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.update` permission) ### Request Body ```json { "price": 200000 } ``` ### Response `200` Updated DoctorInsurance object. ### Errors | Code | HTTP | Description | |------|------|-------------| | `ERR_AUTH_001` | 401 | Missing token | | `ERR_FORBIDDEN_001` | 403 | Not the doctor | | `ERR_NOT_FOUND_001` | 404 | Link not found | --- ## DELETE `/api/v1/insurance/{id}` Remove an insurance from a doctor's list. **Permission:** `AUTH` — must be the doctor (or their secretary with `insurances.delete` permission) ### Response `200` ```json { "success": true, "data": { "message": "بیمه از لیست حذف شد" } } ``` --- ## EntityInsurancePricing — قیمت‌گذاری ویزیت بر اساس بیمه قیمت‌گذاری ویزیت برای entity جاری (پزشک یا کلینیک)، با تفکیک: - **ویزیت آزاد** (بدون بیمه) — یک مبلغ پایه (ردیفی با `insurance_id = null`) - **سهم بیمار به ازای هر بیمه** پایه/مکمل entity جاری از `#[CurrentUser]` resolve می‌شود: نقش `ROLE_DOCTOR` → `doctor`، نقش `ROLE_CLINIC` → `clinic`. ذخیره‌سازی polymorphic در جدول `entity_insurance_pricing` (`entity_type`, `entity_id`, `insurance_id` nullable, `patient_share_rials`). --- ## GET `/api/v1/insurance-pricing` قیمت‌گذاری بیمه‌ی entity جاری + لیست همه‌ی بیمه‌های فعال (با سهم بیمار اگر تعیین شده). **Permission:** `AUTH` (`ROLE_DOCTOR` یا `ROLE_CLINIC`) ### Response `200` ```json { "success": true, "data": { "entity_type": "doctor", "entity_id": 7, "free_visit_price_rials": 5000000, "insurances": [ { "insurance_id": 3, "insurance_name": "تأمین اجتماعی", "type": "basic", "patient_share_rials": 1500000 }, { "insurance_id": 9, "insurance_name": "دانا", "type": "supplementary", "patient_share_rials": null } ] } } ``` - `patient_share_rials = null` یعنی این بیمه پذیرفته نمی‌شود (قیمت‌گذاری ندارد). ### خطاها - `403` `ERR_FORBIDDEN_001` — پروفایل (doctor/clinic) برای کاربر یافت نشد. --- ## PUT `/api/v1/insurance-pricing` ذخیره/به‌روزرسانی قیمت ویزیت آزاد و سهم بیمار هر بیمه. عملیات upsert؛ ردیفی که `patient_share_rials = null` بفرستد حذف می‌شود. **Permission:** `AUTH` (`ROLE_DOCTOR` یا `ROLE_CLINIC`) ### Request Body ```json { "free_visit_price_rials": 5000000, "insurances": [ { "insurance_id": 3, "patient_share_rials": 1500000 }, { "insurance_id": 9, "patient_share_rials": null } ] } ``` | فیلد | نوع | توضیح | |------|-----|-------| | `free_visit_price_rials` | int | مبلغ ویزیت آزاد (ریال). اختیاری؛ اگر نباشد تغییر نمی‌کند. | | `insurances[].insurance_id` | int | شناسه‌ی بیمه (الزامی برای هر ردیف). | | `insurances[].patient_share_rials` | int \| null | سهم بیمار با این بیمه. `null` → ردیف حذف می‌شود. | ### Response `200` همان ساختار `GET /api/v1/insurance-pricing` (وضعیت پس از ذخیره). ### خطاها - `403` `ERR_FORBIDDEN_001` — پروفایل یافت نشد. --- ## TenantInsurance — قراردادهای بیمه‌ی tenant (فاز ۱ سیستم صورتحساب) قرارداد یک پزشک/کلینیک با یک بیمه: درصد پوشش، فرانشیز، سقف تعهد سالانه، نسخه‌بندی و وضعیت فعال. مبنای محاسبه‌ی سهم در سیستم صورتحساب (`docs/architecture/insurance-billing-system.md`). tenant از `#[CurrentUser]` (`ROLE_DOCTOR`→doctor، `ROLE_CLINIC`→clinic). جدول `tenant_insurances`. ### GET `/api/v1/billing/tenant-insurances` لیست قراردادهای فعال tenant جاری. **Permission:** `AUTH` (doctor/clinic) ```json { "success": true, "data": { "data": [ { "uuid": "…", "insurance_id": 3, "insurance_name": "تأمین اجتماعی", "insurance_kind": "basic", "version": 1, "is_active": true, "coverage_percent": 70, "franchise_rials": 0, "annual_ceiling_rials": null, "effective_from": 1718900000, "effective_to": null } ] } } ``` ### POST `/api/v1/billing/tenant-insurances` فعال‌سازی/به‌روزرسانی قرارداد. اگر قرارداد فعالی برای آن بیمه باشد ویرایش می‌شود، وگرنه نسخه‌ی جدید. **Body:** | فیلد | نوع | توضیح | |------|-----|-------| | `insurance_id` | int | الزامی | | `coverage_percent` | float | درصد پوشش (۰–۱۰۰) | | `franchise_rials` | int | فرانشیز ثابت سهم بیمار | | `annual_ceiling_rials` | int \| null | سقف تعهد (null = بی‌نهایت) | پاسخ `201`: `{ success, data: { …contract } }`. خطاها: `404 ERR_NOT_FOUND_001` بیمه یافت نشد · `422 ERR_VALIDATION_001` insurance_id الزامی · `403 ERR_FORBIDDEN_001` پروفایل یافت نشد. ### PATCH `/api/v1/billing/tenant-insurances/{uuid}` ویرایش `coverage_percent` / `franchise_rials` / `annual_ceiling_rials`. فقط قرارداد متعلق به tenant جاری. ### DELETE `/api/v1/billing/tenant-insurances/{uuid}` غیرفعال‌سازی نرم (soft) — `is_active=false` و `effective_to=now`. داده حذف نمی‌شود. ```json { "success": true, "data": { "message": "قرارداد بیمه غیرفعال شد" } } ``` > **Guard:** `TenantInsuranceService::assertActive()` هنگام پذیرش/صورتحساب فقط بیمه‌های فعالِ همان tenant را مجاز می‌داند؛ در غیر این صورت `422 ERR_VALIDATION_001` («این بیمه برای این کلینیک/پزشک فعال نیست»). --- ## TenantServiceCoverage — پوشش خدمت تحت یک قرارداد بیمه (فاز ۲) override پوشش یک خدمت خاص تحت قرارداد یک بیمه. فیلدهای `null` از خود قرارداد ارث می‌برند. اگر `covered=false` → آن خدمت تحت آن بیمه پوشش ندارد. جدول `tenant_service_coverage`. ### GET `/api/v1/billing/tenant-insurances/{uuid}/service-coverage` لیست overrideهای پوشش خدمات یک قرارداد. **Permission:** `AUTH` (مالک قرارداد) ```json { "success": true, "data": { "data": [ { "uuid": "…", "tenant_insurance_id": 4, "service_item_id": 12, "covered": true, "coverage_percent": 80, "franchise_rials": null, "ceiling_rials": null } ] } } ``` ### PUT `/api/v1/billing/tenant-insurances/{uuid}/service-coverage` تنظیم/به‌روزرسانی پوشش یک خدمت (upsert). **Body:** | فیلد | نوع | توضیح | |------|-----|-------| | `service_item_id` | int | الزامی | | `covered` | bool | پیش‌فرض true | | `coverage_percent` | float \| null | null = ارث از قرارداد | | `franchise_rials` | int \| null | null = ارث از قرارداد | | `ceiling_rials` | int \| null | null = ارث از قرارداد | پاسخ `200`: `{ success, data: { message } }`. خطاها: `404 ERR_NOT_FOUND_001` قرارداد یافت نشد · `422 ERR_VALIDATION_001` service_item_id الزامی. > منطق resolve: `TenantInsuranceService::coverageRuleForService()` ابتدا override خدمت را بررسی می‌کند؛ اگر `covered=false` → `CoverageRule::notCovered()`؛ در غیر این صورت فیلدهای null از قرارداد پر می‌شوند. این `CoverageRule` در فاز ۴ ورودی `BillingCalculator` است.