Rebuild the patient records (پروندهها) area, phase A of the Figma redesign:
- BE: add a clinic-scoped `record_number` and a TenantTag `tags` M2M to
PatientRecord (migration + EAGER-hydrated collection). POST /patient and
PATCH /patient/{uuid} now accept `record_number` and tenant-scoped `tags`
(foreign tag → 422); demographic fields (gender, date_of_birth,
referral_source, description) continue to live on UserProfile via PATCH.
- FE: new PatientsListPage (table + card views, search, pagination, tags
column, "تشکیل پرونده") at /admin/patients, and PatientRecordFormPage
(create/edit) that POSTs the record then PATCHes the demographics. Point
the sidebar "پرونده" entry to the new list.
Phases B–E (tabbed patient file, service stepper, invoice, payments/wallet,
call-center) follow. Backend covered by PHPUnit, FE by Vitest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
486 lines
20 KiB
Markdown
486 lines
20 KiB
Markdown
# Patient Records & Sessions API
|
||
|
||
## Overview
|
||
|
||
Patient records track patients per entity (doctor or clinic). Each record holds multiple sessions (visits). Access requires an active subscription with the `patient_records` feature.
|
||
|
||
**Base path:** `/api/v1`
|
||
**Auth:** Bearer JWT (doctor, clinic, or secretary with `appointments.view` permission required)
|
||
|
||
---
|
||
|
||
## Endpoints
|
||
|
||
### List Patients
|
||
|
||
```
|
||
GET /api/v1/patients
|
||
```
|
||
|
||
Returns a paginated list of patient records belonging to the authenticated entity.
|
||
|
||
**Query params:**
|
||
|
||
| Param | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `page` | int | 1 | Page number |
|
||
| `limit` | int | 20 | Items per page (10–50) |
|
||
| `search` | string | — | Search by patient name or phone |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user": { "uuid": "...", "fullName": "علی رضایی", "phone": "09123456789" },
|
||
"created_by_type": "doctor",
|
||
"created_by_id": 5,
|
||
"created_at": 1718375000
|
||
}
|
||
],
|
||
"meta": {
|
||
"totalRecords": 42,
|
||
"totalPages": 3,
|
||
"currentPage": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No active plan with `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Create Patient Record
|
||
|
||
```
|
||
POST /api/v1/patient
|
||
```
|
||
|
||
Creates a patient record for a user under the current entity. If the record already exists, returns the existing record (idempotent).
|
||
|
||
سه حالت پشتیبانی میشود:
|
||
1. **کاربر ثبتنامکرده با uuid:** `user_uuid` ارسال شود.
|
||
2. **کاربر ثبتنامکرده با موبایل:** `mobile` ارسال شود (کاربر موجود پیدا میشود).
|
||
3. **بیمار جدید بدون ثبتنام:** `mobile` + `name` ارسال شود؛ اگر کاربری با آن موبایل نباشد، `User` جدید (نقش `ROLE_USER`، بدون رمز عبور) ساخته میشود سپس پرونده.
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"user_uuid": "string (اختیاری)",
|
||
"mobile": "09xxxxxxxxx (اختیاری — برای جستجو یا ساخت بیمار جدید)",
|
||
"name": "string (الزامی فقط هنگام ساخت بیمار جدید)",
|
||
"national_code": "string (اختیاری، ۱۰ رقم)",
|
||
"record_number": "string (اختیاری) — شماره پرونده، مخصوص رکورد",
|
||
"tags": ["uuid برچسبهای TenantTag (اختیاری) — باید متعلق به همین tenant باشند"]
|
||
}
|
||
```
|
||
|
||
- اگر `user_uuid` و `mobile` هر دو خالی باشند → خطا.
|
||
- `record_number` و `tags` روی خودِ رکورد ذخیره میشوند (نه پروفایل کاربر). سایر مشخصات دموگرافیک (`gender`, `date_of_birth`, `referral_source`, `description`, بیمهها) روی `UserProfile` هستند و از طریق `PATCH /patient/{uuid}` ست میشوند. پاسخ همیشه `record_number` و `tags: [{uuid,name,color}]` را برمیگرداند.
|
||
- برچسب متعلق به tenant دیگر → `422 ERR_VALIDATION_001` (`field: tags`).
|
||
- `national_code` فقط وقتی روی کاربر ست میشود که کاربر کد ملی نداشته باشد.
|
||
- موبایل تکراری duplicate نمیسازد؛ همان کاربر استفاده میشود.
|
||
- **یکتایی کد ملی:** اگر `national_code` ارسالی قبلاً به پروفایل کاربر دیگری تعلق داشته باشد → `409` با کد `ERR_PROFILE_001` (`field: national_code`). پیام خطا شامل شماره موبایلِ ماسکشدهی صاحب کد است (مثلاً «این کد ملی قبلاً با شماره 0912****56 ثبت شده است»). یک کد ملی = یک بیمار در کل سیستم (همراستا با قید یکتای `profiles.national_code`).
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user": { "uuid": "...", "fullName": "...", "phone": "..." },
|
||
"created_by_type": "doctor",
|
||
"created_by_id": 5,
|
||
"created_at": 1718375000
|
||
}
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_VALIDATION_001` | 422 | `user_uuid`/`mobile` خالی، یا موبایل/کد ملی نامعتبر، یا نام برای بیمار جدید خالی |
|
||
| `ERR_PROFILE_001` | 409 | کد ملی قبلاً برای پروفایل کاربر دیگری ثبت شده (`field: national_code`) |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Get Patient Record
|
||
|
||
```
|
||
GET /api/v1/patient/{uuid}
|
||
```
|
||
|
||
Returns a single patient record, enriched with the patient's full profile (`profile`) درونخطی از `UserProfile`. اگر پروفایل وجود نداشت، فیلدها `null` برمیگردند (نه خطا). نام بیمهها از روی id resolve میشوند.
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"uuid": "...",
|
||
"entity_type": "doctor",
|
||
"entity_id": 5,
|
||
"user_uuid": "...",
|
||
"user_name": "...",
|
||
"user_mobile": "0912...",
|
||
"user_national_code": "...",
|
||
"created_at": 1718375000,
|
||
"profile": {
|
||
"full_name": "محمد محمدی",
|
||
"name": "محمد",
|
||
"family": "محمدی",
|
||
"fathers_name": "رضا",
|
||
"national_code": "0012345678",
|
||
"gender": "male",
|
||
"date_of_birth": 700000000,
|
||
"blood_type": "O+",
|
||
"marital_status": "single",
|
||
"education": "کارشناسی",
|
||
"field_of_study": "نرمافزار",
|
||
"job": "...",
|
||
"address": "...",
|
||
"province_id": 8,
|
||
"city_id": 42,
|
||
"postal_code": "8913746351",
|
||
"referral_source": "اینستاگرام",
|
||
"description": "...",
|
||
"home_phone": "...",
|
||
"work_phone": "...",
|
||
"mobile": "0912...",
|
||
"basic_insurance_id": 3,
|
||
"basic_insurance_name": "تأمین اجتماعی",
|
||
"supplementary_insurance_id": 9,
|
||
"supplementary_insurance_name": "دانا"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> `date_of_birth` یک Unix timestamp است؛ سمت کلاینت با `formatDate()` شمسی نمایش داده میشود. `profile` برای هر دو `entity_type` (doctor/clinic) یکسان است.
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Update Patient Basic Info
|
||
|
||
```
|
||
PATCH /api/v1/patient/{uuid}
|
||
```
|
||
|
||
اطلاعات پایهی بیمار را بهروزرسانی میکند. برای هر سه نقشِ صاحبِ پرونده در دسترس است: **پزشک، کلینیک، و منشیِ فعالِ همان مطب/کلینیک** (دسترسی از طریق همان `resolveEntity` + `assertPatientGate` مثل بقیهی endpointهای بیمار کنترل میشود؛ منشی باید `db_uuid` فعال داشته باشد).
|
||
|
||
بهروزرسانی **partial** است — فقط کلیدهای ارسالشده اعمال میشوند. مقدار `""`/`null` برای فیلدهای پروفایل یعنی «پاککردن». `name` روی `User.realName` و بقیهی فیلدها روی `UserProfile` مینشینند (در صورت نبود پروفایل، ساخته میشود).
|
||
|
||
> **شماره موبایل قابل ویرایش است** — موبایل همان شناسهی ورود کاربر است، پس ارسال `mobile` علاوه بر شمارهی تماس، **نامکاربری ورود کاربر را نیز تغییر میدهد**. باید `^09\d{9}$` و در سطح کاربران یکتا باشد.
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"name": "محمد",
|
||
"family": "محمدی",
|
||
"fathers_name": "رضا",
|
||
"mobile": "09131234567",
|
||
"national_code": "0012345678",
|
||
"gender": "male",
|
||
"blood_type": "O+",
|
||
"marital_status": "single",
|
||
"education": "کارشناسی",
|
||
"field_of_study": "نرمافزار",
|
||
"job": "مهندس",
|
||
"home_phone": "03511111111",
|
||
"work_phone": "03512222222",
|
||
"address": "...",
|
||
"province_id": 8,
|
||
"city_id": 42,
|
||
"postal_code": "8913746351",
|
||
"referral_source": "اینستاگرام",
|
||
"description": "...",
|
||
"basic_insurance_id": 3,
|
||
"supplementary_insurance_id": 9
|
||
}
|
||
```
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `name` | string | اگر ارسال شود و خالی نباشد → `User.realName`. رشتهی خالی نادیده گرفته میشود. |
|
||
| `family` | string\|null | `UserProfile.family` |
|
||
| `fathers_name` | string\|null | `UserProfile.fathersName` (نام پدر) |
|
||
| `mobile` | string\|null | اگر خالی نباشد و با موبایل فعلی فرق کند باید `^09\d{9}$` و یکتا باشد؛ روی `User.mobileNumber` ست میشود و **شناسهی ورود** را عوض میکند |
|
||
| `national_code` | string\|null | اگر خالی نباشد باید ۱۰ رقم و در سطح بیمار یکتا باشد؛ روی `User.nationalCode` و `UserProfile.nationalCode` ست میشود |
|
||
| `gender` | `male`\|`female`\|null | |
|
||
| `blood_type` | string\|null | |
|
||
| `marital_status` | string\|null | |
|
||
| `education` | string\|null | مقطع تحصیلی (`UserProfile.education`) |
|
||
| `field_of_study` | string\|null | رشتهی تحصیلی (`UserProfile.fieldOfStudy`) |
|
||
| `job` | string\|null | |
|
||
| `home_phone`, `work_phone` | string\|null | |
|
||
| `address` | string\|null | |
|
||
| `province_id`, `city_id` | int\|null | id استان/شهر (category؛ `null` = حذف) |
|
||
| `postal_code` | string\|null | کد پستی |
|
||
| `referral_source` | string\|null | نحوهی آشنایی |
|
||
| `description` | string\|null | توضیحات |
|
||
| `basic_insurance_id`, `supplementary_insurance_id` | int\|null | id بیمه؛ `null` = حذف |
|
||
|
||
**Response 200:** مثل `GET /api/v1/patient/{uuid}` (رکورد + `profile` تازه).
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_VALIDATION_001` | 422 | کد ملی باید ۱۰ رقم باشد (`field: national_code`) یا موبایل نامعتبر است (`field: mobile`) |
|
||
| `ERR_PROFILE_NATIONAL_CODE_TAKEN` | 409 | کد ملی متعلق به بیمار دیگری است (`field: national_code`) |
|
||
| `ERR_PROFILE_002` | 409 | موبایل متعلق به کاربر دیگری است (`field: mobile`) |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### List Patient Sessions
|
||
|
||
```
|
||
GET /api/v1/patient/{uuid}/sessions
|
||
```
|
||
|
||
Returns paginated sessions for a patient record.
|
||
|
||
**Query params:** `page`, `limit` (same as list)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "...",
|
||
"record_uuid": "...",
|
||
"appointment_uuid": null,
|
||
"insurance_base_id": null,
|
||
"insurance_supplementary_id": null,
|
||
"visit_price_rials": 200000,
|
||
"base_insurance_discount_percent": "10.00",
|
||
"supplementary_discount_percent": "5.00",
|
||
"services_total_rials": 50000,
|
||
"final_price_rials": 230000,
|
||
"payment_method": "cash",
|
||
"is_paid": true,
|
||
"services": [
|
||
{
|
||
"uuid": "...",
|
||
"service_item_uuid": "...",
|
||
"service_name": "کندلا ۲۰۲۱",
|
||
"staff_uuid": "...",
|
||
"staff_name": "ژیلا فتحی",
|
||
"price_rials": 50000,
|
||
"quantity": 1,
|
||
"line_total_rials": 50000,
|
||
"created_at": 1718375000
|
||
}
|
||
],
|
||
"invoice_uuid": "...",
|
||
"invoice_status": "finalized",
|
||
"patient_debt_rials": 0,
|
||
"notes": "...",
|
||
"created_at": 1718375000,
|
||
"updated_at": 1718375000
|
||
}
|
||
],
|
||
"meta": { "totalRecords": 8, "totalPages": 1, "currentPage": 1 }
|
||
}
|
||
```
|
||
|
||
**فیلدهای غنیسازیشده (برای تب «سرویسها»):**
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `is_paid` | bool | `true` وقتی `payment_method !== "pending"` |
|
||
| `services` | array | سرویسهای ثبتشده در این session (نام، انجامدهنده/`staff`, قیمت، تعداد) |
|
||
| `invoice_uuid` | string\|null | uuid فاکتور مرتبط (اگر ساخته شده باشد؛ برای «مشاهده فاکتور») |
|
||
| `invoice_status` | string\|null | `draft`\|`finalized`\|`paid`\|`void` |
|
||
| `patient_debt_rials` | int | مانده بدهی سهم بیمار؛ `0` اگر تسویه شده، وگرنه سهم بیمارِ فاکتور یا `final_price_rials` |
|
||
|
||
> **ثبت پرداخت («تکمیل پرداخت»):** از همان `PATCH /api/v1/session/{uuid}` با بدنهی `{ "payment_method": "cash" }` استفاده میشود؛ پس از آن `is_paid=true` و `patient_debt_rials=0` میشود. مشاهدهی فاکتور از `GET /api/v1/billing/invoices/{invoice_uuid}` (این endpoint اکنون برای منشیِ فعال هم در دسترس است).
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### List Patient Appointments
|
||
|
||
```
|
||
GET /api/v1/patient/{uuid}/appointments
|
||
```
|
||
|
||
نوبتهای همین بیمار را برمیگرداند. برای جلوگیری از نشتِ اطلاعات بین ارائهدهندهها، فقط نوبتهایی نمایش داده میشوند که با پزشک(های) خودِ صاحب پرونده گرفته شدهاند:
|
||
|
||
- ارائهدهندهی **پزشک**: نوبتهای بیمار با همان پزشک.
|
||
- ارائهدهندهی **کلینیک** (و منشیِ فعالِ کلینیک): نوبتهای بیمار با پزشکانی که دعوت پذیرفتهشده (`accepted`) در آن کلینیک دارند.
|
||
|
||
مرتبشده بر اساس `starts_at` نزولی. خروجی آرایهی ساده است (بدون صفحهبندی).
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"uuid": "…",
|
||
"starts_at": 1754000000,
|
||
"ends_at": 1754001800,
|
||
"status": "confirmed",
|
||
"doctor_name": "دکتر ژیلا فتحی",
|
||
"service_name": null,
|
||
"price_rials": null,
|
||
"created_at": 1754000000
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`status` یکی از: `pending`، `confirmed`، `completed`، `cancelled_by_doctor`، `cancelled_by_user`، `no_show`، `expired`. فیلدهای `service_name`/`price_rials` فعلاً همیشه `null` هستند (نوبت خدمت/قیمت مستقل ندارد).
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned by caller |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Create Session
|
||
|
||
```
|
||
POST /api/v1/patient/{uuid}/session
|
||
```
|
||
|
||
Creates a new visit session for a patient record.
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"visit_price_rials": 200000,
|
||
"base_insurance_discount_percent": 10,
|
||
"supplementary_discount_percent": 5,
|
||
"insurance_base_id": null,
|
||
"insurance_supplementary_id": null,
|
||
"payment_method": "cash",
|
||
"notes": "...",
|
||
"services": [
|
||
{
|
||
"service_item_uuid": "...",
|
||
"staff_uuid": null,
|
||
"quantity": 2
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Field notes:**
|
||
|
||
- `payment_method`: `cash` | `card` | `insurance` | `online` | `pending`
|
||
- `services`: array of service items to attach; `price_rials` snapshot از ServiceItem؛ `quantity` (پیشفرض ۱) → `line_total_rials = price_rials × quantity`. هر `SessionService` در پاسخ `quantity` و `line_total_rials` دارد.
|
||
- `final_price_rials` (سهم بیمار) به این صورت محاسبه میشود:
|
||
- **ویزیت:** `round(visit_price × (1 - base%) × (1 - supp%))` با درصدهای انتخابشده در فرم.
|
||
- **هر خدمت:** سهم بیمار با قاعدهی پوشش همان بیمهگر برای همان خدمت (`TenantServiceCoverage` از طریق `BillingCalculator`) محاسبه میشود؛ یعنی فقط خدمتی که بیمهی انتخابشده آن را پوشش میدهد تخفیف میگیرد (درصد/فرانشیز/سقف؛ مقدار نبودِ override از قرارداد ارث میبرد). خدمتِ بدون پوشش، کامل بر عهدهی بیمار است.
|
||
- `final_price_rials = سهم بیمار ویزیت + Σ(سهم بیمار هر خدمت)` و `services_total_rials = Σ(price × quantity)` (قیمت کامل خدمات، بدون بیمه).
|
||
- این محاسبه دقیقاً همان منطقِ صورتحساب/مطالبات است؛ پیشنمایش پنل هم همین قاعده را سمت کلاینت آینه میکند.
|
||
|
||
**اتصال خودکار مطالبهی بیمه:** اگر session دارای `insurance_base_id` یا `insurance_supplementary_id` باشد، پس از ثبت بهصورت خودکار صورتحساب ساخته و نهایی میشود و مطالبه(های) بیمه در وضعیت `pending` ایجاد میگردد (پایه/مکمل، فقط برای سهم بیمه > ۰). این مطالبات در صفحهی [مطالبات بیمه](billing.md) قابل پیگیری و ارسالاند. خطا در این مرحله ثبت session را خراب نمیکند (لاگ میشود). برای هر صورتحساب فقط یکبار مطالبه ساخته میشود.
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ...session object... }
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_PATIENT_NOT_FOUND` | 404 | Record not found or not owned |
|
||
| `ERR_SUBSCRIPTION_REQUIRED` | 403 | No `patient_records` feature |
|
||
|
||
---
|
||
|
||
### Update Session
|
||
|
||
```
|
||
PATCH /api/v1/session/{uuid}
|
||
```
|
||
|
||
Updates mutable fields on a session.
|
||
|
||
**Request body (all optional):**
|
||
|
||
```json
|
||
{
|
||
"notes": "...",
|
||
"payment_method": "card"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ...session object... }
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
| Code | HTTP | Description |
|
||
|------|------|-------------|
|
||
| `ERR_SESSION_NOT_FOUND` | 404 | Session not found or not owned |
|
||
|
||
---
|
||
|
||
## Auto-Creation on Appointment Confirm
|
||
|
||
When an appointment's status changes to `confirmed` via `PATCH /api/v1/appointment/{uuid}/status`, the system automatically:
|
||
|
||
1. Creates a `PatientRecord` for the appointment's user (if not already existing) under the doctor entity
|
||
2. Creates a blank `PatientSession` linked to the appointment
|
||
3. **اگر نوبت با آدرس کلینیک ثبت شده باشد** (`appointment.address_id` → `DoctorAddress.clinic_id`)، همان دو مرحله برای آن **کلینیک** (`entity_type='clinic'`) هم تکرار میشود. اگر آدرس نوبت کلینیک نداشت ولی دکتر فقط عضو **یک** کلینیک بود، به همان کلینیک اضافه میشود.
|
||
|
||
هر شاخه (doctor / clinic) مستقل و فقط در صورت فعالبودن ویژگی `patient_records` برای همان entity اجرا میشود. duplicate با `findByEntityAndUser` جلوگیری میشود.
|
||
|
||
**انتساب پزشک:** هر `PatientSession` در پاسخ، `doctor_uuid` و `doctor_name` را از روی نوبتِ متناظر برمیگرداند؛ پس در پروندهی کلینیک مشخص است هر مراجعه برای کدام پزشک بوده است.
|
||
|
||
**آدرس نوبت:** هنگام رزرو، `address_id` خودکار از `location_id` همان session برنامهی هفتگی ست میشود (در همهی مسیرهای رزرو). ثبت `location_id` برای هر شیفت فعال در برنامهی هفتگی الزامی است (`POST/PATCH /api/v1/appointment-settings/weekly-schedule`)؛ در غیر این صورت `422`.
|