# Representation (Agent) API

> **Prefix:** `/api/v1/representation`

Representations are sales agents who earn commission on appointments booked through their referral.

---

## POST `/api/v1/representation`

Create a new representation.

**Permission:** `ROLE_ADMIN`

### Request Body (`application/json`)
```json
{
  "full_name": "علی احمدی",
  "mobile_number": "09123456789",
  "city_id": 42,
  "commission_percent": 10,
  "bank_account": {
    "iban": "IR...",
    "account_number": "1234567890",
    "bank_name": "بانک ملت",
    "owner_name": "علی احمدی"
  }
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `full_name` | string | ✅ | Agent full name |
| `mobile_number` | string | ✅ | Login mobile (creates a User account) |
| `city_ids` | integer[] | ❌ | شهرهای تحت پوشش (چند-شهری). `city_id` تکی هم برای BC پذیرفته می‌شود |
| `domain` | string | ❌ | دامنه اختصاصی نماینده (نرمال می‌شود: بدون scheme/www). یکتا؛ نباید با دامنه شهرها تداخل کند. **admin-only** |
| `is_global` | boolean | ❌ | نماینده سراسری — سایتِ دامنه‌اش فقط پزشکان/کلینیک‌های خودش را نشان می‌دهد. **admin-only** |
| `commission_percent` | float | ❌ | Commission rate (0–100) |
| `bank_account` | object | ❌ | Bank details for settlements |

**پاسخ‌ها اکنون شامل:** `city_ids: int[]`، `cities: [{id, name}]`، `domain`، `is_global` (علاوه بر `city_id` قدیمی = اولین شهر).

### Response `201`
```json
{
  "success": true,
  "data": {
    "uuid": "rep-uuid-...",
    "full_name": "علی احمدی",
    "mobile_number": "09123456789",
    "city_id": 42,
    "commission_percent": 10,
    "active": true,
    "bank_account": { ... },
    "created_at": 1717000000
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_CONFLICT_001` | 409 | Mobile number already in use |
| `ERR_VALIDATION_001` | 422 | `mobile_number` فرمت معتبر موبایل ایران (`^09\d{9}$`) ندارد (`field: mobile_number`) |
| `ERR_VALIDATION_002` | 422 | `mobile_number` یا `full_name` خالی |

---

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

Get representation detail.

**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`

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

### Response `200`
```json
{
  "success": true,
  "data": {
    "uuid": "...",
    "full_name": "علی احمدی",
    "mobile_number": "09123456789",
    "city_id": 42,
    "city_name": "تهران",
    "commission_percent": 10,
    "active": true,
    "bank_account": { ... },
    "created_at": 1717000000
  }
}
```

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

---

## PATCH `/api/v1/representation/{uuid}`

Update representation.

**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`

### Request Body (`application/json`)
```json
{
  "full_name": "علی احمدی جدید",
  "city_id": 50,
  "commission_percent": 12,
  "bank_account": { ... },
  "active": true
}
```

All fields optional. `city_ids: int[]` جایگزین `city_id` است (تکی هم پذیرفته می‌شود).

**Privileged fields:** `commission_percent`، `active`، `domain` و `is_global` **admin-only** هستند — نماینده روی رکورد خودش فقط `full_name`، `city_ids`، `bank_account` را می‌تواند تغییر دهد. `commission_percent` باید در بازه `0–100` باشد. خطاهای `domain`: نامعتبر → 422، تکراری یا برخورد با دامنه شهر → 409.

### Response `200`
Updated representation object.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not owner or admin |
| `ERR_AUTH_006` | 403 | Non-admin tried to change `commission_percent` or `active` |
| `ERR_VALIDATION_001` | 422 | `commission_percent` خارج از بازه ۰ تا ۱۰۰ (`field: commission_percent`) |
| `ERR_NOT_FOUND_001` | 404 | Representation not found |

---

## DELETE `/api/v1/representation/{uuid}`

Delete a representation.

**Permission:** `ROLE_ADMIN`

### Response `200`
```json
{ "success": true, "data": { "message": "نماینده حذف شد" } }
```

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

---

## GET `/api/v1/representation/{uuid}/dashboard/monthly`

Get monthly earnings dashboard for a representation.

`total_appointments` فقط نوبت‌های **آنلاین** را می‌شمارد — یعنی نوبت‌هایی که از سایتِ همین نماینده رزرو شده‌اند (`appointments.booking_representation_id` برابر همین نماینده). نوبتی که منشی در پنل ثبت می‌کند از سایت نیامده و در آمار نماینده نمی‌آید.

**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`

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

### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `year` | integer | ✅ | e.g. `2024` |
| `month` | integer | ✅ | 1–12 |

### Response `200`
```json
{
  "success": true,
  "data": {
    "period": { "year": 2024, "month": 6 },
    "stats": {
      "total_appointments": 15,
      "total_revenue_rials": 7500000,
      "commission_rials": 750000,
      "daily": [
        { "date": "2024-06-01", "appointments": 2, "revenue": 1000000, "commission": 100000 }
      ]
    }
  }
}
```

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

---

## GET `/api/v1/representation/{uuid}/dashboard/yearly`

Get yearly earnings dashboard for a representation.

**Permission:** `AUTH` — must be the representation's user or `ROLE_ADMIN`

### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `year` | integer | ✅ | e.g. `2024` |

### Response `200`
```json
{
  "success": true,
  "data": {
    "period": { "year": 2024 },
    "months": [
      { "month": 1, "appointments": 10, "revenue_rials": 5000000, "commission_rials": 500000 },
      { "month": 2, "appointments": 8, "revenue_rials": 4000000, "commission_rials": 400000 }
    ],
    "totals": {
      "appointments": 97,
      "revenue_rials": 48500000,
      "commission_rials": 4850000
    }
  }
}
```

---

## GET `/api/v1/site-context`

**عمومی (بدون auth).** نگاشت یک دامنه به زمینه‌ی سایت — مصرف‌کننده: سایت عمومی nobat724 برای دامنه‌های خارج از `data/city.json` (دامنه اختصاصی نمایندگان سراسری).

### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `domain` | string | ✅ | host یا URL کامل؛ نرمال می‌شود (scheme/www/پورت حذف) |

### Response `200`
```json
{
  "success": true,
  "data": {
    "type": "representation",
    "city": null,
    "representation": { "uuid": "...", "full_name": "نماینده الف", "is_global": true }
  }
}
```
`type`: `city` (دامنه یکی از شهرها) | `representation` (دامنه اختصاصی نماینده فعال) | `unknown`. برای `city`، آبجکت `city: {id, name}` پر می‌شود.

---

## قانون کمیسیون دامنه‌محور

کمیسیون (نوبت **و** اشتراک) فقط وقتی ثبت می‌شود که **هر دو** شرط برقرار باشد:
1. دامنه‌ی مبدأ خرید (`payment.frontend_address`) به یک نماینده‌ی فعال برسد.
2. پزشک/کلینیکِ موضوع خرید، `representation_id` همان نماینده را داشته باشد.

دامنه به نماینده به این ترتیب می‌رسد:

- نماینده‌ای که همان دامنه را در `representations.domain` ثبت کرده (نماینده‌ی سراسری).
- وگرنه اگر دامنه، دامنه‌ی یک شهر باشد (`cities.domain`)، نماینده‌ی فعالِ همان شهر از `representation_cities`.

اگر دو نماینده‌ی فعال یک شهر را پوشش دهند، نماینده‌ای انتخاب نمی‌شود: انتساب پول مبهم است و باید در داده صریح شود.

در غیر این صورت هیچ کمیسیونی برای هیچ نماینده‌ای ثبت نمی‌شود (پرداخت بدون `frontend_address` هم کمیسیون ندارد). درصد: نوبت = `commission_percent` نماینده؛ اشتراک = تنظیم سراسری `upgrade_commission_percent`. نگاشت دامنه فقط از طریق `DomainContextResolver` انجام می‌شود.

**زمان ثبت:** کمیسیون نوبت در لحظه‌ی **پرداخت موفق** ثبت می‌شود، نه در لحظه‌ی تأیید نوبت. نوبتِ `pending` هم کمیسیون دارد؛ تأیید کارِ پزشک/منشی است و ممکن است هرگز انجام نشود. ثبت idempotent است و مسیر تأیید دوباره چیزی نمی‌سازد.

---

## پنل نماینده (ROLE_REPRESENTATION)

> **شمارش نوبت:** `dashboard/summary` و `doctors/performance` هم فقط نوبت‌های آنلاینِ همین نماینده را می‌شمارند (`appointments.booking_representation_id`). نوبتی که منشی در پنل ثبت می‌کند شمرده نمی‌شود. ستون درآمدِ هر پزشک هم فقط سهم همین نماینده است، نه سهم نمایندگان قبلیِ آن پزشک.
>
> **بازسازی گذشته:** نوبت‌های آنلاینی که پیش از نگاشت دامنه‌ی شهری پرداخت شده‌اند نه `booking_representation_id` دارند و نه ردیف `FinancialBreakdown`. دستور زیر هر دو را از روی `payments.frontend_address` می‌سازد؛ بدون `--force` فقط گزارش می‌دهد و تاریخ ردیف مالی روی لحظه‌ی پرداخت می‌نشیند، نه لحظه‌ی اجرا:
>
> ```
> php bin/console app:representation:backfill-online-commission [--force]
> ```


این endpointها برای کاربرِ دارای نقش `ROLE_REPRESENTATION` در پنل ادمین (`/admin`) هستند. مالکیت همیشه از کاربر جاری (`#[CurrentUser]` + `findByUser`) تعیین می‌شود؛ هیچ uuid/id ورودی برای تعیین مالکیت پذیرفته نمی‌شود.

> **Permission (همه‌ی این بخش):** `ROLE_REPRESENTATION`

### GET `/api/v1/representation/me`

پروفایل نماینده‌ی کاربر جاری.

#### Response `200`
```json
{
  "success": true,
  "data": {
    "data": {
      "uuid": "...",
      "full_name": "حامد حسینی",
      "mobile_number": "09120671756",
      "city_id": 132,
      "commission_percent": "10.00",
      "bank_account": [
        { "id": "iban-uuid-1", "iban": "IR000000000000000000000000", "bank_name": "بانک ملت", "owner_name": "حامد حسینی", "verified": true, "created_at": 1718000000 }
      ],
      "active": true,
      "created_at": 1718000000,
      "national_code": "0012345678",
      "national_code_verified": true
    }
  }
}
```
> double-nested: مقدار با `data.data` استخراج می‌شود. `bank_account` آرایه‌ای از ۰ تا ۲ شبا است (`null` اگر هیچ شبایی ثبت نشده). `national_code`/`national_code_verified` از کاربرِ نماینده می‌آید.

#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

---

### POST `/api/v1/representation/verify-national-code`

تأیید کد ملی نماینده با استعلام **شاهکار** (`s.api.ir` → ShahkarLite): تطبیق کد ملی با موبایلِ کاربر جاری. در صورت موفقیت، `national_code` ذخیره و `national_code_verified=true` می‌شود.

#### Request Body
```json
{ "national_code": "0012345678" }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `national_code` | string | ✅ | کد ملی ۱۰ رقمی |

#### Response `200`
آبجکت پروفایل نماینده (مثل `me`، با `national_code_verified: true`).

#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | کد ملی ۱۰ رقم نیست (`field: national_code`) |
| `ERR_IDENTITY_001` | 422 | کد ملی متعلق به این موبایل نیست (`field: national_code`) |
| `ERR_EXTERNAL_001` | 502 | خطا در استعلام |
| `ERR_EXTERNAL_002` | 503 | سرویس استعلام پیکربندی نشده |

---

### POST `/api/v1/representation/iban`

افزودن یک شماره شبا. ابتدا با **IbanMatch** (`s.api.ir`) بررسی می‌شود شبا متعلق به کد ملیِ تأییدشده‌ی نماینده باشد. حداکثر ۲ شبا.

#### Request Body
```json
{ "iban": "IR000000000000000000000000", "birth_date": "1370/01/01" }
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `iban` | string | ✅ | شماره شبا (با/بدون `IR` و فاصله؛ نرمال‌سازی می‌شود) |
| `birth_date` | string | ✅ | تاریخ تولد شمسی `Y/m/d` (نمونه `1370/01/01`). فقط برای استعلام IbanMatch؛ **ذخیره نمی‌شود.** |

#### Response `200`
آبجکت پروفایل نماینده با `bank_account` به‌روزشده.

#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_IDENTITY_004` | 409 | کد ملی هنوز تأیید نشده |
| `ERR_IDENTITY_003` | 409 | سقف ۲ شبا پر است |
| `ERR_VALIDATION_001` | 422 | شبا نامعتبر (`field: iban`) یا تاریخ تولد نامعتبر (`field: birth_date`) |
| `ERR_IDENTITY_002` | 422 | شبا متعلق به نماینده نیست (`field: iban`) |
| `ERR_EXTERNAL_001` | 502 | خطا در استعلام |
| `ERR_EXTERNAL_002` | 503 | سرویس استعلام پیکربندی نشده |

---

### DELETE `/api/v1/representation/iban/{id}`

حذف یک شماره شبا با `id` آن (از `bank_account[].id`).

#### Response `200`
آبجکت پروفایل نماینده با `bank_account` به‌روزشده.

---

### POST `/api/v1/representation/doctor`

افزودن پزشک توسط نماینده. `representation_id` پزشک به‌صورت خودکار روی نماینده‌ی کاربر جاری ست می‌شود. پس از ثبت موفق، یک پیامک خوش‌آمد (تگ `welcome`) به‌صورت async به موبایل پزشک ارسال می‌شود.

#### Request Body
```json
{ "mobile": "0935...", "name": "...", "gender": "man", "degree": "...", "medical_system_code": "...", "specialties": [1,2] }
```
| Field | Type | Required |
|-------|------|----------|
| `mobile` | string | ✅ |
| `name` | string | ✅ |
| `gender` / `degree` / `medical_system_code` / `info` | string | ❌ |
| `activity_time` | integer (Unix ts، تاریخ شروع فعالیت) | ❌ |
| `specialties` | integer[] | ❌ — هر شناسهٔ فرزند با تمام والدهایش تا ریشهٔ درخت گسترش می‌یابد |

#### Response `201`
```json
{ "success": true, "data": { "uuid": "..." } }
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 422 | موبایل یا نام خالی |
| `ERR_VALIDATION_001` | 422 | `mobile` فرمت معتبر موبایل ایران ندارد (`field: mobile`) |
| `ERR_CONFLICT_001` | 409 | این کاربر قبلاً پزشک است |

---

### POST `/api/v1/representation/clinic`

افزودن کلینیک توسط نماینده. `representation_id` کلینیک خودکار روی نماینده‌ی کاربر جاری ست می‌شود (مثل createDoctor) تا در لیست‌های scoped دیده شود. پس از ثبت موفق، یک پیامک خوش‌آمد (تگ `welcome`) به‌صورت async به موبایل مالک کلینیک ارسال می‌شود.

#### Request Body
```json
{ "owner_mobile": "0935...", "name": "کلینیک ...", "telephone": "...", "address": "..." }
```
| Field | Type | Required |
|-------|------|----------|
| `owner_mobile` | string | ✅ |
| `name` | string | ✅ |
| `telephone` / `address` / `info` | string | ❌ |

#### Response `200`
```json
{ "success": true, "data": { "uuid": "...", "name": "...", "is_active": true } }
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 422 | موبایل یا نام خالی |
| `ERR_VALIDATION_001` | 422 | `owner_mobile` فرمت معتبر موبایل ایران ندارد (`field: owner_mobile`) |

---

### GET `/api/v1/representation/appointments`

نوبت‌های همه‌ی پزشکانی که `representation_id` آن‌ها = نماینده‌ی کاربر جاری است (paginated، با شکل آیتمِ یکسان با `/api/v1/admin/appointments`).

#### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | integer | ❌ | پیش‌فرض 1 |
| `limit` | integer | ❌ | پیش‌فرض 15، حداکثر 500 |
| `status` | string | ❌ | فیلتر وضعیت |
| `date` | string (YYYY-MM-DD) | ❌ | فیلتر تاریخِ نوبت |
| `search` | string | ❌ | جستجو در موبایل/نام بیمار یا نام پزشک |

#### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "patient_name": "...",
      "patient_mobile": "0912...",
      "doctor_uuid": "...",
      "doctor_name": "...",
      "slot_start": 1718000000,
      "slot_end": 1718001800,
      "appointment_date": "2025-06-15",
      "appointment_time": "10:00",
      "end_time": "10:30",
      "status": "confirmed",
      "created_at": 1717900000
    }
  ],
  "meta": { "totalRecords": 12, "totalPages": 1, "currentPage": 1 }
}
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

---

### GET `/api/v1/representation/doctors`

پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (فقط ردیف‌های `representation_id = نماینده‌ی کاربر جاری`). شکل آیتم یکسان با `GET /api/v1/admin/doctors` است.

> **Permission:** `ROLE_REPRESENTATION` — id نماینده از `#[CurrentUser]` تعیین می‌شود، نه از query (نماینده نمی‌تواند داده‌ی نماینده‌ی دیگر را ببیند).

#### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | integer | ❌ | پیش‌فرض 1 |
| `limit` | integer | ❌ | پیش‌فرض 15، حداکثر 100 |
| `search` | string | ❌ | جستجو در نام یا موبایل پزشک |

#### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...", "id": 12, "name": "...", "gender": "man", "degree": "...",
      "medical_code": "...", "mobile": "0912...", "email": null,
      "is_active": true, "rate": 3.5, "specialties": [],
      "profile_image": null, "created_at": "2026-06-18T..."
    }
  ],
  "meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
}
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

---

### GET `/api/v1/representation/doctors/stats`

آمار پزشکانِ ثبت‌شده توسط نماینده‌ی جاری (فقط `representation_id = نماینده‌ی کاربر جاری`). شکل پاسخ سازگار با `GET /api/v1/admin/doctors/stats` (بدون `top_specialty`). فرانت‌اند کارت‌های «کل پزشکان / فعال / غیرفعال / مرد / زن» را از این endpoint برای نقش نماینده پر می‌کند.

> **Permission:** `ROLE_REPRESENTATION` — id نماینده از `#[CurrentUser]`.

#### Response `200`
```json
{
  "success": true,
  "data": { "total": 12, "active": 9, "inactive": 3, "male": 7, "female": 5 }
}
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

---

### POST `/api/v1/representation/doctors/{uuid}/status`

فعال/غیرفعال کردن پزشکِ زیرمجموعه‌ی نماینده‌ی جاری (toggle `active_doctor_appointment`). فقط روی پزشکانی که `representation_id` آن‌ها برابر نماینده‌ی کاربر جاری است؛ در غیر این صورت 404.

> **Permission:** `ROLE_REPRESENTATION` — مالکیت از `#[CurrentUser]` چک می‌شود.

#### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string | uuid پزشک |

#### Response `200`
```json
{ "success": true, "data": { "is_active": false } }
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست، یا پزشک یافت نشد / متعلق به این نماینده نیست |

---

### GET `/api/v1/representation/clinics`

کلینیک‌های ثبت‌شده توسط نماینده‌ی جاری (فقط `representation_id = نماینده‌ی کاربر جاری`). شکل آیتم سازگار با `GET /api/v1/admin/clinics`.

> **Permission:** `ROLE_REPRESENTATION` — id نماینده از `#[CurrentUser]`.

#### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | integer | ❌ | پیش‌فرض 1 |
| `limit` | integer | ❌ | پیش‌فرض 15، حداکثر 100 |
| `search` | string | ❌ | جستجو در نام یا تلفن کلینیک |

#### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...", "id": 5, "name": "کلینیک ...", "telephone": "...",
      "logo": null, "clinic_logo": null, "is_active": true,
      "doctors_count": 0, "created_at": "2026-06-18T..."
    }
  ],
  "meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
}
```
#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

---

## داشبورد، عملکرد و مالیِ نماینده‌ی جاری

> همه‌ی این endpointها `#[IsGranted('ROLE_REPRESENTATION')]` و scope بر اساس `#[CurrentUser]` (نه uuid مسیر). درآمد همیشه از `FinancialBreakdown.representation_share_rials` (پورسانت واقعیِ ثبت‌شده) محاسبه می‌شود، نه مبلغ کل نوبت. بازه‌ها: امروز=`strtotime('today')`, هفته=۷ روز اخیر, ماه=۳۰ روز اخیر.

### GET `/api/v1/representation/dashboard/summary`

خلاصه‌ی آمار نوبت و درآمد نماینده‌ی جاری.

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "appointments": { "today": 0, "week": 3, "month": 12, "total": 40 },
    "income": {
      "today": 0, "week": 270000, "month": 909090, "total": 3000000,
      "settlable_rials": 2090910, "settled_rials": 500000, "pending_rials": 0
    }
  }
}
```
`settlable_rials` = موجودی کیف‌پول (`getWalletBalance`)؛ `settled_rials` = جمع Settlementهای `paid`؛ `pending_rials` = جمع `pending`+`approved`.

#### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | کاربر جاری نماینده نیست |

### GET `/api/v1/representation/doctors/performance`

عملکرد پزشکانِ نماینده‌ی جاری (paginated). **Query:** `page`, `limit`.

**Response `200` (paginated):**
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...", "name": "...",
      "appointments": { "today": 0, "week": 1, "month": 4, "total": 18 },
      "representation_income_rials": 363636,
      "subscription_status": "active"
    }
  ],
  "meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
}
```
`subscription_status`: `active` (اشتراک فعال دارد) یا `none`.

### GET `/api/v1/representation/finance/report`

گزارش مالی بازه‌ای از ردیف‌های `FinancialBreakdown` نماینده‌ی جاری (paginated). **Query:** `page`, `limit`, `from` (Unix ts), `to` (Unix ts).

**Response `200` (paginated):**
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...", "appointment_uuid": "...", "doctor_name": "...",
      "gross_rials": 2000000, "tax_rials": 45455, "sms_fee_rials": 1500000,
      "commission_percent": 20, "representation_share_rials": 90909,
      "created_at": "2026-06-24T..."
    }
  ],
  "meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
}
```

> **اصلاح `buildStats`** (در `GET /api/v1/representation/{uuid}/dashboard/monthly|yearly`): قبلاً آمار را به نماینده فیلتر نمی‌کرد (کلِ پلتفرم). اکنون `total_appointments` فقط نوبت‌های پزشکانِ همان نماینده، `commission_rials` از `FinancialBreakdown.representation_share_rials`، و `total_revenue_rials` از `FinancialBreakdown.gross_rials` (source=appointment) همان نماینده محاسبه می‌شود.
