Files
clinicpro/docs/api/clinic-services.md
T
hamedandClaude Opus 4.8 9e4ee11831 feat(services): match Figma خدمات page in settings shell + multi-staff
Render the clinic services page (sections → services) inside the settings
sub-navigation shell (SettingsLayout, "خدمات" active) to match the Figma
settings design. Restyle section cards to show the service count and a
status toggle with edit/delete actions, and service cards with labelled
price/duration and personnel chips.

A service can now have multiple personnel: add an additive many-to-many
ServiceItem↔ClinicStaff (staffMembers, EAGER) while keeping the legacy
single `staff` column mirrored for backward compatibility. Endpoints accept
`staff_uuids[]` (falling back to the legacy single `staff_uuid`) and return
`staff_members[]`; the section list now reports `items_count`.

Backfill-safe: pre-migration rows fall back to the single staff in toArray.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:28:51 +03:30

7.6 KiB

Clinic Services API

مدیریت بخش‌ها و سرویس‌های کلینیک/مطب.

نیاز به پنل: Basic یا بالاتر (ERR_SUBSCRIPTION_REQUIRED اگر نداشت)


GET /api/v1/service-sections

لیست بخش‌های سرویس entity جاری.

Permission: IS_AUTHENTICATED_FULLY + پنل Basic+

Response 200:

{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "entity_type": "clinic",
      "entity_id": 5,
      "name": "آزمایشگاه",
      "active": true,
      "items_count": 10,
      "created_at": 1718000000,
      "updated_at": 1718000000
    }
  ]
}

items_count تعداد سرویس‌های همان بخش است (فقط در این endpoint لیستی برگردانده می‌شود).


POST /api/v1/service-section

ایجاد بخش جدید.

Permission: IS_AUTHENTICATED_FULLY + پنل Basic+

Request Body:

{ "name": "رادیولوژی" }

Response 201: ServiceSection object

Errors:

Code HTTP توضیح
ERR_SUBSCRIPTION_REQUIRED 403 نیاز به پنل Basic+
ERR_VALIDATION_001 422 name خالی است

PATCH /api/v1/service-section/{uuid}

ویرایش بخش.

Permission: owner یا ROLE_ADMIN

{ "name": "رادیولوژی دیجیتال", "active": true }

DELETE /api/v1/service-section/{uuid}

حذف بخش (cascade — همه ServiceItem های آن حذف می‌شوند).

Permission: owner


GET /api/v1/service-items/{sectionUuid}

لیست سرویس‌های یک بخش.

Response 200:

{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "section_uuid": "...",
      "staff_uuid": "...",
      "staff_name": "علی محمدی",
      "staff": { "uuid": "...", "full_name": "علی محمدی" },
      "staff_members": [
        { "uuid": "...", "full_name": "علی محمدی" },
        { "uuid": "...", "full_name": "سحر رحمانی" }
      ],
      "name": "رادیوگرافی مستقیم",
      "price_rials": 500000,
      "active": true,
      "insurance_covered": false,
      "insurance_price_rials": null,
      "duration_minutes": 50,
      "created_at": 1718000000,
      "updated_at": 1718000000
    }
  ]
}

POST /api/v1/service-item

ایجاد سرویس جدید.

Permission: IS_AUTHENTICATED_FULLY + پنل Basic+

Request Body:

{
  "section_uuid": "...",
  "name": "رادیوگرافی مستقیم",
  "price_rials": 500000,
  "staff_uuid": "...",
  "insurance_covered": true,
  "insurance_price_rials": 200000,
  "duration_minutes": 50
}
فیلد نوع الزامی
section_uuid UUID
name string
price_rials integer (پیش‌فرض 0) — «قیمت پایه»
staff_uuids UUID[] — پرسنل مسئول (چند نفر). ترجیح داده می‌شود
staff_uuid UUID — legacy تک‌پرسنل (اگر staff_uuids نباشد استفاده می‌شود)
insurance_covered boolean (پیش‌فرض false) — آیا خدمت شامل بیمه می‌شود
insurance_price_rials integer|null — سهم/قیمت بیمار با بیمه
duration_minutes integer|null — «زمان متوسط» انجام خدمت به دقیقه (""/null = بدون مقدار)

Response 201: ServiceItem object (شامل insurance_covered و insurance_price_rials)

قیمت واحد: هنگام ساخت سرویس، یک تعرفه برای سال جاری با همان price_rials به‌صورت خودکار ثبت می‌شود. قیمت سرویس = تعرفه‌ی سال جاری است و همه‌جا (صورتحساب، مراجعه، مطالبات) از همین قیمت استفاده می‌شود.


PATCH /api/v1/service-item/{uuid}

ویرایش سرویس.

{
  "name": "رادیوگرافی دیجیتال",
  "price_rials": 600000,
  "staff_uuid": null,
  "active": false,
  "insurance_covered": true,
  "insurance_price_rials": 250000,
  "duration_minutes": 30
}

فیلد duration_minutes (زمان متوسط، دقیقه) در پاسخِ toArray و در ساخت/ویرایش پشتیبانی می‌شود؛ ""/null آن را پاک می‌کند.

پرسنل چندنفره: یک سرویس می‌تواند چند پرسنل داشته باشد. staff_uuids (آرایه) ترجیح داده می‌شود؛ در نبود آن، staff_uuid تک‌نفره به‌صورت backward-compatible پذیرفته می‌شود. پاسخ همیشه staff_members[] (کامل) و staff/staff_uuid/staff_name (نفر اول، برای سازگاری) را برمی‌گرداند. هر پرسنل باید متعلق به همان tenant (entity_type/entity_id) باشد؛ در غیر این صورت → 422 ERR_VALIDATION_001 (field: staff_uuids). همین قید روی POST /service-item نیز اعمال می‌شود.


DELETE /api/v1/service-item/{uuid}

حذف سرویس.

اگر سرویس در پرونده بیماری استفاده شده باشد، خطا برمی‌گرداند:

{
  "success": false,
  "errors": [{ "code": "ERR_SERVICE_ITEM_IN_USE", "message": "این سرویس در پرونده بیمار ثبت شده است" }]
}

Errors:

Code HTTP توضیح
ERR_SUBSCRIPTION_REQUIRED 403 نیاز به پنل Basic+
ERR_SERVICE_NOT_FOUND 404 سرویس یافت نشد
ERR_SERVICE_ITEM_IN_USE 409 سرویس در پرونده بیمار استفاده شده

تعرفه‌ی نسخه‌دار سالانه (Tariff) — فاز ۳ سیستم صورتحساب

هر خدمت می‌تواند برای هر سال شمسی یک تعرفه داشته باشد. اگر تعرفه‌ی سالی ثبت نشود، به price_rials خود خدمت fallback می‌شود (TariffService::resolvePrice). سال جاری شمسی سمت سرور با IntlDateFormatter (تقویم persian) محاسبه می‌شود.

GET /api/v1/service-items/{uuid}/tariffs

لیست تعرفه‌های یک خدمت + قیمت پیش‌فرض + سال جاری.

Permission: IS_AUTHENTICATED_FULLY (مالک خدمت)

{
  "success": true,
  "data": {
    "current_year": 1405,
    "default_price_rials": 500000,
    "data": [
      { "uuid": "…", "service_item_id": 12, "year": 1405, "price_rials": 600000, "is_active": true },
      { "uuid": "…", "service_item_id": 12, "year": 1404, "price_rials": 500000, "is_active": true }
    ]
  }
}

PUT /api/v1/service-items/{uuid}/tariffs/{year}

ثبت/به‌روزرسانی تعرفه‌ی یک سال (upsert). year بین ۱۳۹۰ تا ۱۵۰۰.

Body:

{ "price_rials": 600000 }

Response 200: { success, data: { …tariff } }

اگر year برابر سال جاری باشد، ServiceItem.price_rials هم با همین مقدار همگام می‌شود (قیمت واحد). تعرفه‌ی سال‌های دیگر فقط برای محاسبه‌ی صورتحساب همان سال (TariffService::resolvePrice) به‌کار می‌رود و قیمت پایه‌ی سرویس را تغییر نمی‌دهد. هم‌چنین PATCH /service-item/{uuid} با تغییر price_rials، تعرفه‌ی سال جاری را upsert می‌کند.

Errors:

Code HTTP توضیح
ERR_SERVICE_NOT_FOUND 404 سرویس یافت نشد
ERR_VALIDATION_001 422 سال نامعتبر