# Settlement & Wallet API

> **Prefix:** `/api/v1/wallet`, `/api/v1/settlement`

---

## GET `/api/v1/wallet/balance`

Get authenticated user's wallet balance.

**Permission:** `AUTH`

### Response `200`
```json
{
  "success": true,
  "data": {
    "balance_rials": 2500000,
    "recent_transactions": [
      {
        "uuid": "...",
        "type": "credit",
        "amount_rials": 500000,
        "balance_after": 2500000,
        "description": "دریافت از نوبت شماره ...",
        "created_at": 1717000000
      }
    ]
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |

---

## GET `/api/v1/wallet/transactions`

Get wallet transaction history for the authenticated user (paginated, newest first).

**Permission:** `AUTH`

### Query Parameters
| پارامتر | نوع | توضیح |
|---------|-----|-------|
| `page` | int | شماره صفحه (پیش‌فرض ۱) |
| `limit` | int | تعداد در هر صفحه (پیش‌فرض ۵۰، حداکثر ۱۰۰) |

### Response `200`
```json
{
  "success": true,
  "data": {
    "data": [
      {
        "uuid": "...",
        "type": "credit",
        "amount_rials": 500000,
        "balance_after": 2500000,
        "description": "دریافت از نوبت",
        "created_at": 1717000000
      },
      {
        "uuid": "...",
        "type": "debit",
        "amount_rials": 200000,
        "balance_after": 2300000,
        "description": "تسویه‌حساب",
        "created_at": 1716900000
      }
    ],
    "meta": { "totalRecords": 124, "totalPages": 3, "currentPage": 1 }
  }
}
```

**Transaction Type Values:**
| Value | Description |
|-------|-------------|
| `credit` | Money added to wallet |
| `debit` | Money removed from wallet |

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |

---

## POST `/api/v1/settlement`

Request a settlement (withdrawal from wallet to bank account).

**Permission:** `AUTH`

### Request Body (`application/json`)
```json
{
  "amount_rials": 1000000,
  "iban_id": "iban-uuid-..."
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `amount_rials` | integer | ✅ | Amount to withdraw (must be ≤ wallet balance) |
| `iban_id` | string | ✅ | شناسه‌ی یکی از شباهای **تأییدشده‌ی همان کاربر** |

**شبا از کجا خوانده می‌شود؟** تسویه دیگر مخصوص نماینده نیست: `UserIbanResolver` ابتدا شبای
نماینده (`GET /api/v1/representation/me` → `bank_account[].id`) و در نبودش شبای پروفایل
کاربر (`GET /api/v1/secretary/me` → `bank_account[].id`) را بررسی می‌کند. بنابراین هر نقشی
که موجودی کیف پول دارد — از جمله **منشی** با سهم نوبت‌های آنلاین — می‌تواند برداشت کند.

> شبای انتخابی به‌صورت snapshot (`iban`, `bank_name`, `owner_name`) داخل خود رکورد تسویه ذخیره می‌شود؛ حذف بعدی شبا در پروفایل، این رکورد را تغییر نمی‌دهد. مبلغ همان لحظه‌ی ثبت از کیف‌پول کسر (debit) می‌شود.

### Response `201`
```json
{
  "success": true,
  "data": {
    "uuid": "settle-uuid-...",
    "amount_rials": 1000000,
    "status": "pending",
    "bank_account": {
      "iban": "IR...",
      "bank_name": "بانک ملت",
      "owner_name": "علی احمدی"
    },
    "created_at": 1717000000
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_VALIDATION_001` | 422 | مبلغ نامعتبر/بیش از موجودی، یا شبا تأییدنشده/نامعتبر (`field: iban_id`) |
| `ERR_VALIDATION_002` | 422 | `iban_id` ارسال نشده (`field: iban_id`) |
| `ERR_CONFLICT_001` | 422 | موجودی کافی نیست |

---

## GET `/api/v1/settlement`

Get authenticated user's settlement requests (paginated, newest first).

**Permission:** `AUTH`

> **صفحه‌بندی:** `?page` و `?limit` (پیش‌فرض ۵۰، حداکثر ۱۰۰). پاسخ علاوه بر `data.data` یک `data.meta` (`totalRecords`/`totalPages`/`currentPage`) دارد؛ پاکت قبلی دست‌نخورده است.

### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "amount_rials": 1000000,
      "status": "pending",
      "note": null,
      "created_at": 1717000000,
      "processed_at": null
    }
  ]
}
```

**Settlement Status Values:**
| Value | Description |
|-------|-------------|
| `pending` | Awaiting admin review |
| `approved` | Approved, payment sent |
| `rejected` | Rejected by admin |

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |

---

## GET `/api/v1/settlement/{uuid}`

Get a single settlement.

**Permission:** `AUTH` — must be the owner or `ROLE_ADMIN`

### Response `200`
Settlement object with full details including `bank_account`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |

---

## POST `/api/v1/settlement/{uuid}/approve`

Approve a settlement request. Marks it as paid and debits the wallet.

**Permission:** `ROLE_ADMIN`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Settlement UUID |

### Request Body (`application/json`)
```json
{
  "note": "پرداخت شد — شناسه پیگیری: 123456"
}
```

| Field | Type | Required |
|-------|------|----------|
| `note` | string | ❌ |

### Response `200`
Updated settlement object with `status: "approved"`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
| `ERR_VALIDATION_001` | 422 | Already processed |

---

## POST `/api/v1/settlement/{uuid}/reject`

Reject a settlement request. Returns the amount back to wallet.

**Permission:** `ROLE_ADMIN`

### Request Body
```json
{
  "note": "حساب بانکی نادرست است"
}
```

| Field | Type | Required |
|-------|------|----------|
| `note` | string | ✅ | Rejection reason (required) |

### Response `200`
Updated settlement object with `status: "rejected"`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Settlement not found |
| `ERR_VALIDATION_002` | 422 | Missing note |

---

## FinancialBreakdown (لاگ مالی)

علاوه بر تسویه‌حساب دستی، کیف‌پول نماینده به‌صورت خودکار از طریق `CommissionService` هنگام پرداخت موفقِ نوبت/اشتراک شارژ می‌شود (`WalletTransaction` credit). هر واریز یک ردیف `FinancialBreakdown` ثبت می‌کند که تفکیک کامل تراکنش (ناخالص، هزینه پیامک، مالیات، خالص، درصد و سهم پورسانت، **سهم منشی**، سهم سیستم) را نگه می‌دارد. ثبت idempotent است (بر اساس `payment_id`). گزارش‌ها از طریق `GET /api/v1/admin/financial-breakdowns` و `GET /api/v1/admin/financial-summary` در دسترس‌اند — جزئیات در `docs/api/admin.md`.

### ترتیب تقسیم و سهم منشی

```
۱) هزینهٔ پنل پیامک        ← از ناخالص کم می‌شود
۲) مالیات                  ← استخراجی از باقی‌مانده: tax = amount × p/(100+p)
۳) سهم‌ها، همه از «خالصِ پس از مالیات»:
     پورسانت نماینده = netAfterTax × commission_percent / 100
     سهم هر منشی     = netAfterTax × online_share_percent / 100
   سهم سیستم = ناخالص − پیامک − مالیات − پورسانت − مجموع سهم منشی‌ها
```

- سهم منشی **مستقل از نماینده** است: نوبتِ بدون نمایندهٔ منطبق هم اگر منشیِ سهم‌بر داشته باشد، تفکیک مالی می‌سازد.
- `financial_breakdowns.secretary_share_rials` مجموع سهم منشی‌های همان پرداخت است؛ تفکیک هر منشی در جدول `secretary_earnings` (با `share_percent` و `relation_uuid`) ذخیره می‌شود و گزارش پنل منشی از همان خوانده می‌شود ([secretary.md](secretary.md)).
- اگر مجموع درصدها (پورسانت + سهم منشی‌ها) از ۱۰۰ بگذرد، به نسبت کلیپ و یک هشدار با `payment_uuid` لاگ می‌شود تا سهم سیستم منفی نشود.

---

## رسید پرداخت و ثبت پرداخت نهایی (ROLE_ADMIN)

> منطق کیف‌پول: مبلغ هنگام **ثبت درخواست** از کیف‌پول نماینده کسر (reserve/debit) می‌شود؛ **رد** آن را برمی‌گرداند (credit). تأیید و پرداختِ نهایی کیف‌پول را دوباره دست نمی‌زنند (جلوگیری از double-debit).

### POST `/file/upload/clinic_pro/settlement/receipt`

آپلود تصویر رسید پرداخت. بدنه = محتوای خام فایل؛ هدر `Content-Disposition: filename="..."`. **Permission:** `ROLE_ADMIN`

**Response `200`:**
```json
{ "success": true, "data": { "uuid": "...", "url": "/uploads/settlements/receipts/2026-06/...", "filename": "...", "filemime": "image/jpeg" } }
```

### POST `/api/v1/settlement/{uuid}/receipt`

ذخیره‌ی رسید پرداخت **بدون** نهایی‌سازی. فقط روی تسویه‌ی **approved**؛ وضعیت تغییر نمی‌کند (همچنان `approved`). پس از این مرحله دکمه‌ی «تکمیل» در پنل فعال می‌شود. **Permission:** `ROLE_ADMIN`

**Request Body:**
```json
{ "receipt": "/uploads/settlements/receipts/2026-06/..." }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `receipt` | string | ✅ | URL رسیدِ آپلودشده |

**Response `200`:** آبجکت تسویه (با `receipt` ذخیره‌شده، `status: "approved"`).

**Errors:**
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | درخواست یافت نشد |
| `ERR_VALIDATION_001` | 422 | تسویه approved نیست |
| `ERR_VALIDATION_002` | 422 | `receipt` خالی (`field: receipt`) |

### POST `/api/v1/settlement/{uuid}/paid`

**«تکمیل»** — ثبت پرداخت نهایی. فقط روی تسویه‌ی **approved**. وضعیت → `paid` و `receipt` ذخیره می‌شود. کیف‌پول تغییر نمی‌کند (مبلغ هنگام ثبت درخواست کسر شده). پس از تکمیل، آن مبلغ دیگر قابل درخواست تسویه نیست. **Permission:** `ROLE_ADMIN`

**Request Body:**
```json
{ "receipt": "/uploads/settlements/receipts/2026-06/..." }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `receipt` | string | ✅ | URL رسیدِ آپلودشده |

**Response `200`:** آبجکت تسویه (شامل `receipt` و `status: "paid"`).

**Errors:**
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | درخواست یافت نشد |
| `ERR_VALIDATION_001` | 422 | تسویه approved نیست |
| `ERR_VALIDATION_002` | 422 | `receipt` خالی (`field: receipt`) |
