feat: insurance & medical billing system (6 phases)
Multi-tenant insurance contracts, service coverage, versioned tariffs, invoice calculation, and insurance claims with debt reporting. - TenantInsurance: per-tenant insurance contracts (coverage/franchise/ceiling, versioning, soft-deactivate) + active guard - ServiceItem.insuranceCovered + TenantServiceCoverage per-service overrides - Tariff: versioned yearly tariffs with fallback to ServiceItem price - Billing domain: Money/ShareBreakdown VOs, BillingCalculator (unit-tested), Invoice/InvoiceItem aggregate, InvoiceService.createFromSession - Claim/ClaimItem with state machine (pending->submitted->approved/rejected->paid), ClaimService, insurance-debt report - ClaimSubmitterInterface + ManualClaimSubmitter (future insurance API ready) - Admin UI: insurance-pricing page, claims page, service tariff modal, service insurance toggle; routes + sidebar entries - Architecture doc + billing/insurance/clinic-services API docs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# Billing API — صورتحساب (فاز ۴ سیستم صورتحساب/بیمه)
|
||||
|
||||
> **Prefix:** `/api/v1/billing`
|
||||
> دامنه: `App\Billing`. مرجع معماری: `docs/architecture/insurance-billing-system.md`.
|
||||
> tenant از `#[CurrentUser]` resolve میشود (`ROLE_DOCTOR`→doctor، `ROLE_CLINIC`→clinic).
|
||||
|
||||
صورتحساب (`Invoice`) از یک Encounter (`PatientSession`) ساخته میشود. برای هر آیتم سهم بیمهی پایه، بیمهی مکمل و بیمار با `BillingCalculator` محاسبه میشود:
|
||||
|
||||
- تعرفهی خدمت از `Tariff` سال جاری (با fallback به `ServiceItem.priceRials`).
|
||||
- قانون پوشش از قرارداد بیمهی tenant (`TenantInsurance`) + override خدمت (`TenantServiceCoverage`).
|
||||
- ترتیب محاسبه: کل → پوشش پایه (با سقف) → باقیمانده → پوشش مکمل → فرانشیز سهم بیمار.
|
||||
|
||||
نمونه: کل ۶۰۰٬۰۰۰ · پایه ۷۰٪ → ۴۲۰٬۰۰۰ · مکمل روی باقیمانده → ۱۲۰٬۰۰۰ · بیمار ۶۰٬۰۰۰.
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/billing/invoices
|
||||
|
||||
ساخت صورتحساب از یک مراجعه. اگر صورتحساب برای آن مراجعه قبلاً ساخته شده، همان برگردانده میشود (idempotent).
|
||||
|
||||
**Permission:** `AUTH` (مالک مراجعه)
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{ "session_uuid": "…" }
|
||||
```
|
||||
|
||||
**Response 201:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "…",
|
||||
"entity_type": "doctor",
|
||||
"entity_id": 7,
|
||||
"patient_session_id": 33,
|
||||
"base_insurance_id": 3,
|
||||
"supplementary_insurance_id": 9,
|
||||
"total_rials": 600000,
|
||||
"base_insurance_rials": 420000,
|
||||
"supplementary_rials": 120000,
|
||||
"patient_rials": 60000,
|
||||
"status": "draft",
|
||||
"issued_at": 1718900000,
|
||||
"items": [
|
||||
{
|
||||
"uuid": "…",
|
||||
"service_item_id": 12,
|
||||
"title": "ویزیت",
|
||||
"tariff_rials": 600000,
|
||||
"quantity": 1,
|
||||
"total_rials": 600000,
|
||||
"base_insurance_rials": 420000,
|
||||
"supplementary_rials": 120000,
|
||||
"patient_rials": 60000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:** `422 ERR_VALIDATION_001` session_uuid الزامی · `404 ERR_NOT_FOUND_001` مراجعه یافت نشد · `403 ERR_FORBIDDEN_001` پروفایل یافت نشد.
|
||||
|
||||
---
|
||||
|
||||
## GET /api/v1/billing/invoices/{uuid}
|
||||
|
||||
دریافت صورتحساب (فقط مالک tenant).
|
||||
|
||||
**Response 200:** همان ساختار بالا.
|
||||
**Errors:** `404 ERR_NOT_FOUND_001`.
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/billing/invoices/{uuid}/finalize
|
||||
|
||||
نهاییسازی صورتحساب (`draft` → `finalized`). صورتحساب نهاییشده مبنای ساخت Claim (فاز ۵) است.
|
||||
|
||||
**Response 200:** صورتحساب با `status: "finalized"`.
|
||||
**Errors:** `404 ERR_NOT_FOUND_001`.
|
||||
|
||||
---
|
||||
|
||||
## وضعیتهای Invoice
|
||||
`draft` (پیشنویس، قابل بازسازی) → `finalized` (نهایی) → `paid` (پرداختشده) · `void` (باطل).
|
||||
|
||||
## نکات
|
||||
- پول: integer ریال. تاریخ: Unix timestamp.
|
||||
- `BillingCalculator` خالص و واحد-تستشده است (`tests/Billing/BillingCalculatorTest.php`).
|
||||
- این فاز جایگزین تدریجی `PatientService::calculateFinalPrice` است؛ آن متد فعلاً برای سازگاری باقی مانده.
|
||||
|
||||
---
|
||||
|
||||
# Claims — مطالبات بیمه (فاز ۵)
|
||||
|
||||
مطالبه (`Claim`) از یک صورتحساب **نهاییشده** ساخته میشود: یک Claim برای بیمهی پایه و یک Claim برای بیمهی مکمل (فقط اگر سهم بیمه > ۰). هر `ClaimItem` به یک `InvoiceItem` ارجاع میدهد. چرخهی وضعیت با state machine.
|
||||
|
||||
**وضعیتها:** `pending → submitted → {approved → paid | rejected}`. از `paid`/`rejected` خروجی ندارد.
|
||||
|
||||
## POST /api/v1/billing/claims
|
||||
ساخت مطالبات از یک صورتحساب نهاییشده.
|
||||
|
||||
**Body:** `{ "invoice_uuid": "…" }`
|
||||
**Response 201:** `{ success, data: [ …claims ] }` (یک یا دو مطالبه: پایه/مکمل).
|
||||
**Errors:** `422` صورتحساب نهایی نشده / سهم بیمه ندارد · `404` صورتحساب یافت نشد.
|
||||
|
||||
## GET /api/v1/billing/claims?status=
|
||||
لیست مطالبات tenant. فیلتر اختیاری `status` (`pending|submitted|approved|rejected|paid`).
|
||||
|
||||
## POST /api/v1/billing/claims/{uuid}/{action}
|
||||
انتقال وضعیت. `action` ∈ `submit|approve|reject|pay`.
|
||||
|
||||
| action | body اختیاری | اثر |
|
||||
|--------|--------------|-----|
|
||||
| submit | — | pending → submitted |
|
||||
| approve | `approved_rials` | submitted → approved (پیشفرض = کل ادعا) |
|
||||
| reject | `reason` (الزامی) | submitted → rejected |
|
||||
| pay | `paid_rials` | approved → paid (پیشفرض = approved) |
|
||||
|
||||
**Errors:** `422` انتقال نامعتبر یا دلیل رد خالی · `404` مطالبه یافت نشد.
|
||||
|
||||
## GET /api/v1/billing/reports/insurance-debt
|
||||
گزارش بدهی بیمهها برای tenant (group بر اساس بیمه).
|
||||
|
||||
**Response 200:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": [
|
||||
{ "insurance_id": 3, "claimed": 840000, "approved": 800000, "paid": 500000, "debt": 340000 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
`debt = claimed - paid` (حداقل صفر).
|
||||
|
||||
---
|
||||
|
||||
## ارسال مطالبه (ClaimSubmitter)
|
||||
|
||||
عملِ `submit` از طریق interface `App\Billing\Contract\ClaimSubmitterInterface` انجام میشود. پیادهسازی پیشفرض `ManualClaimSubmitter` است (ارسال دستی/آفلاین — همیشه موفق). برای اتصال آینده به API شرکتهای بیمهی ایران کافی است یک پیادهسازی جدید از این interface ساخته و در `config/services.yaml` bind شود؛ `ClaimService` تغییر نمیکند (Dependency Inversion). اگر `submit` ناموفق باشد، انتقال وضعیت با `422` متوقف میشود.
|
||||
Reference in New Issue
Block a user