Files
clinicpro/docs/api/representation.md
T
hamed 9603b702c1 feat: implement domain guard for commission calculation and enhance representation dashboard
- Added domain guard in CommissionService to ensure commission is calculated only when the appointment is booked under the same representation as the doctor.
- Updated RepresentationController to filter statistics by representation, ensuring accurate data is shown for each representative.
- Introduced new endpoints for the representation dashboard to provide summary statistics, doctor performance, and financial reports.
- Created new pages for RepresentationFinance and RepresentationSettlement to display financial data and allow for settlement requests.
- Added migration to include booking_representation_id in appointments for tracking the representative under which the appointment was booked.
2026-06-24 16:14:41 +03:30

17 KiB
Raw Blame History

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)

{
  "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_id integer City ID (FK to categories where bundle=city)
commission_percent float Commission rate (0100)
bank_account object Bank details for settlements

Response 201

{
  "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

{
  "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)

{
  "full_name": "علی احمدی جدید",
  "city_id": 50,
  "commission_percent": 12,
  "bank_account": { ... },
  "active": true
}

All fields optional.

Response 200

Updated representation object.

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

DELETE /api/v1/representation/{uuid}

Delete a representation.

Permission: ROLE_ADMIN

Response 200

{ "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.

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 112

Response 200

{
  "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

{
  "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
    }
  }
}

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

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

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

GET /api/v1/representation/me

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

Response 200

{
  "success": true,
  "data": {
    "data": {
      "uuid": "...",
      "full_name": "حامد حسینی",
      "mobile_number": "09120671756",
      "city_id": 132,
      "commission_percent": "10.00",
      "bank_account": null,
      "active": true,
      "created_at": 1718000000
    }
  }
}

double-nested: مقدار با data.data استخراج می‌شود.

Errors

Code HTTP Description
ERR_NOT_FOUND_001 404 کاربر جاری نماینده نیست

POST /api/v1/representation/doctor

افزودن پزشک توسط نماینده. representation_id پزشک به‌صورت خودکار روی نماینده‌ی کاربر جاری ست می‌شود.

Request Body

{ "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
specialties integer[]

Response 201

{ "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 دیده شود.

Request Body

{ "owner_mobile": "0935...", "name": "کلینیک ...", "telephone": "...", "address": "..." }
Field Type Required
owner_mobile string
name string
telephone / address / info string

Response 200

{ "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

{
  "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

{
  "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

{
  "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

{ "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

{
  "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:

{
  "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 = موجودی کیف‌پول (getWalletBalancesettled_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):

{
  "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):

{
  "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) همان نماینده محاسبه می‌شود.