- M2 GET /insurance/{id}: was unguarded; now owner-or-admin (403 otherwise) —
stops reading another doctor's negotiated price by id enumeration.
- M3 GET /clinic-pro/doctor-address/{id}: add the same owner/admin check the
sibling PATCH/DELETE already had.
- M4 POST/PATCH /service-item: staff_uuid must belong to the caller's tenant
(entity_type/entity_id) → 422; stops binding another tenant's staff.
- M5 appointment-settings list endpoints (date-override/holidays/
available-locations): add the per-doctor ownership check the sibling
single-record endpoints already enforce.
Regressions (6 negative cases fail without the fixes):
DoctorInsuranceOwnershipTest, DoctorAddressOwnershipTest,
ServiceItemStaffOwnershipTest, AppointmentSettingsListOwnershipTest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
218 lines
6.1 KiB
Markdown
218 lines
6.1 KiB
Markdown
# Clinic Services API
|
|
|
|
مدیریت بخشها و سرویسهای کلینیک/مطب.
|
|
|
|
**نیاز به پنل:** Basic یا بالاتر (`ERR_SUBSCRIPTION_REQUIRED` اگر نداشت)
|
|
|
|
---
|
|
|
|
## GET /api/v1/service-sections
|
|
|
|
لیست بخشهای سرویس entity جاری.
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY` + پنل Basic+
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"entity_type": "clinic",
|
|
"entity_id": 5,
|
|
"name": "آزمایشگاه",
|
|
"active": true,
|
|
"created_at": 1718000000,
|
|
"updated_at": 1718000000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## POST /api/v1/service-section
|
|
|
|
ایجاد بخش جدید.
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY` + پنل Basic+
|
|
|
|
**Request Body:**
|
|
```json
|
|
{ "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
|
|
|
|
```json
|
|
{ "name": "رادیولوژی دیجیتال", "active": true }
|
|
```
|
|
|
|
---
|
|
|
|
## DELETE /api/v1/service-section/{uuid}
|
|
|
|
حذف بخش (cascade — همه ServiceItem های آن حذف میشوند).
|
|
|
|
**Permission:** owner
|
|
|
|
---
|
|
|
|
## GET /api/v1/service-items/{sectionUuid}
|
|
|
|
لیست سرویسهای یک بخش.
|
|
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "...",
|
|
"section_uuid": "...",
|
|
"staff_uuid": "...",
|
|
"staff_name": "علی محمدی",
|
|
"name": "رادیوگرافی مستقیم",
|
|
"price_rials": 500000,
|
|
"active": true,
|
|
"created_at": 1718000000,
|
|
"updated_at": 1718000000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## POST /api/v1/service-item
|
|
|
|
ایجاد سرویس جدید.
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY` + پنل Basic+
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"section_uuid": "...",
|
|
"name": "رادیوگرافی مستقیم",
|
|
"price_rials": 500000,
|
|
"staff_uuid": "...",
|
|
"insurance_covered": true,
|
|
"insurance_price_rials": 200000
|
|
}
|
|
```
|
|
|
|
| فیلد | نوع | الزامی |
|
|
|------|-----|--------|
|
|
| section_uuid | UUID | ✅ |
|
|
| name | string | ✅ |
|
|
| price_rials | integer | ❌ (پیشفرض 0) |
|
|
| staff_uuid | UUID | ❌ |
|
|
| insurance_covered | boolean | ❌ (پیشفرض false) — آیا خدمت شامل بیمه میشود |
|
|
| insurance_price_rials | integer\|null | ❌ — سهم/قیمت بیمار با بیمه |
|
|
|
|
**Response 201:** ServiceItem object (شامل `insurance_covered` و `insurance_price_rials`)
|
|
|
|
> **قیمت واحد:** هنگام ساخت سرویس، یک تعرفه برای **سال جاری** با همان `price_rials` بهصورت خودکار ثبت میشود. قیمت سرویس = تعرفهی سال جاری است و همهجا (صورتحساب، مراجعه، مطالبات) از همین قیمت استفاده میشود.
|
|
|
|
---
|
|
|
|
## PATCH /api/v1/service-item/{uuid}
|
|
|
|
ویرایش سرویس.
|
|
|
|
```json
|
|
{
|
|
"name": "رادیوگرافی دیجیتال",
|
|
"price_rials": 600000,
|
|
"staff_uuid": null,
|
|
"active": false,
|
|
"insurance_covered": true,
|
|
"insurance_price_rials": 250000
|
|
}
|
|
```
|
|
|
|
> `staff_uuid` باید به پرسنل متعلق به همان tenant (`entity_type`/`entity_id` کاربر) اشاره کند؛ ربطدادن پرسنل tenant دیگر → `422 ERR_VALIDATION_001` (`field: staff_uuid`). همین قید روی `POST /service-item` نیز اعمال میشود.
|
|
|
|
---
|
|
|
|
## DELETE /api/v1/service-item/{uuid}
|
|
|
|
حذف سرویس.
|
|
|
|
اگر سرویس در پرونده بیماری استفاده شده باشد، خطا برمیگرداند:
|
|
|
|
```json
|
|
{
|
|
"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` (مالک خدمت)
|
|
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{ "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 | سال نامعتبر |
|