feat(patient): services tab with billing (services, debt, pay, invoice)
Expose session services + per-session billing summary (invoice_uuid, invoice_status, patient_debt_rials, is_paid) on the sessions endpoint. Add 'سرویسها' card tab in my-patients: cost, remaining debt, complete payment (PATCH session), and invoice view modal. Grant secretary access in BillingController.resolveEntity. Update patient.md + billing.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Prefix:** `/api/v1/billing`
|
||||
> دامنه: `App\Billing`. مرجع معماری: `docs/architecture/insurance-billing-system.md`.
|
||||
> tenant از `#[CurrentUser]` resolve میشود (`ROLE_DOCTOR`→doctor، `ROLE_CLINIC`→clinic).
|
||||
> tenant از `#[CurrentUser]` resolve میشود (`ROLE_DOCTOR`→doctor، `ROLE_CLINIC`→clinic، و `ROLE_SECRETARY`→ همان مطب/کلینیکِ فعال بر اساس `db_uuid` و رابطهی فعالِ منشی). یعنی منشیِ فعال هم میتواند صورتحسابهای همان tenant را ببیند/بسازد.
|
||||
|
||||
صورتحساب (`Invoice`) از یک Encounter (`PatientSession`) ساخته میشود. برای هر آیتم سهم بیمهی پایه، بیمهی مکمل و بیمار با `BillingCalculator` محاسبه میشود:
|
||||
|
||||
|
||||
@@ -256,6 +256,23 @@ Returns paginated sessions for a patient record.
|
||||
"services_total_rials": 50000,
|
||||
"final_price_rials": 230000,
|
||||
"payment_method": "cash",
|
||||
"is_paid": true,
|
||||
"services": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"service_item_uuid": "...",
|
||||
"service_name": "کندلا ۲۰۲۱",
|
||||
"staff_uuid": "...",
|
||||
"staff_name": "ژیلا فتحی",
|
||||
"price_rials": 50000,
|
||||
"quantity": 1,
|
||||
"line_total_rials": 50000,
|
||||
"created_at": 1718375000
|
||||
}
|
||||
],
|
||||
"invoice_uuid": "...",
|
||||
"invoice_status": "finalized",
|
||||
"patient_debt_rials": 0,
|
||||
"notes": "...",
|
||||
"created_at": 1718375000,
|
||||
"updated_at": 1718375000
|
||||
@@ -265,6 +282,18 @@ Returns paginated sessions for a patient record.
|
||||
}
|
||||
```
|
||||
|
||||
**فیلدهای غنیسازیشده (برای تب «سرویسها»):**
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `is_paid` | bool | `true` وقتی `payment_method !== "pending"` |
|
||||
| `services` | array | سرویسهای ثبتشده در این session (نام، انجامدهنده/`staff`, قیمت، تعداد) |
|
||||
| `invoice_uuid` | string\|null | uuid فاکتور مرتبط (اگر ساخته شده باشد؛ برای «مشاهده فاکتور») |
|
||||
| `invoice_status` | string\|null | `draft`\|`finalized`\|`paid`\|`void` |
|
||||
| `patient_debt_rials` | int | مانده بدهی سهم بیمار؛ `0` اگر تسویه شده، وگرنه سهم بیمارِ فاکتور یا `final_price_rials` |
|
||||
|
||||
> **ثبت پرداخت («تکمیل پرداخت»):** از همان `PATCH /api/v1/session/{uuid}` با بدنهی `{ "payment_method": "cash" }` استفاده میشود؛ پس از آن `is_paid=true` و `patient_debt_rials=0` میشود. مشاهدهی فاکتور از `GET /api/v1/billing/invoices/{invoice_uuid}` (این endpoint اکنون برای منشیِ فعال هم در دسترس است).
|
||||
|
||||
**Errors:**
|
||||
|
||||
| Code | HTTP | Description |
|
||||
|
||||
@@ -36,6 +36,8 @@ class BillingController extends BaseController
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly InsuranceRepository $insuranceRepo,
|
||||
private readonly \App\Secretary\Repository\DoctorSecretaryRepository $secretaryRepo,
|
||||
private readonly \App\Auth\Repository\UserActiveContextRepository $contextRepo,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -290,6 +292,19 @@ class BillingController extends BaseController
|
||||
$clinic = $this->clinicRepo->findByUser($user);
|
||||
return $clinic !== null ? ['clinic', $clinic->getId()] : ['clinic', null];
|
||||
}
|
||||
if ($user->hasRole('ROLE_SECRETARY')) {
|
||||
$dbUuid = $this->contextRepo->findByUser($user)?->getDbUuid();
|
||||
if ($dbUuid !== null) {
|
||||
$clinic = $this->clinicRepo->findByUuid($dbUuid);
|
||||
if ($clinic !== null && $this->secretaryRepo->findActiveBySecretaryForClinic($user, $clinic) !== null) {
|
||||
return ['clinic', $clinic->getId()];
|
||||
}
|
||||
$doctor = $this->doctorRepo->findByUuid($dbUuid);
|
||||
if ($doctor !== null && $this->secretaryRepo->findActiveBySecretaryForDoctor($user, $doctor) !== null) {
|
||||
return ['doctor', $doctor->getId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['unknown', null];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user