Document the resource-first model and retire the deleted tasks' checklists
docs/architecture/resource-first-model.md describes the shape: the three entities, why an option is a ServiceItem rather than a fourth table, the four-level resolution chain, the two conditions on the eligibility filter and what each of them prevented, and why containment is a graph beside the display tree rather than the tree itself. docs/api/resource.md gains both offering endpoints with the response captured from a real call, including a row where the price comes from the branch and one where it comes from the resource — the two cases the *_source fields exist for. docs/api/appointment.md documents resource_uuid, the doctor inference, and the nullable resource/service_option in the response. The checklists for tasks 9 to 14 keep their rows but open with a banner saying the task was removed, when, by whose decision, and which commit to revert. They are history now; deleting them would erase the record of work that shipped and was then withdrawn. Verified end to end: 1304 tests, slot-mode-frozen green, phpstan at 14, tsc clean, 648 panel tests, and app:seed-scenarios --reset builds all three environments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -250,6 +250,7 @@ Book an appointment slot.
|
||||
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
|
||||
| `slot_end` | integer | ⚠️ | Slot end (Unix timestamp). **با `service_item_uuids` نادیده گرفته میشود** و سرور خودش حساب میکند (به مقدار کلاینت اعتماد نمیشود)؛ در آن حالت الزامی هم نیست. بدون سرویس، مقدار کلاینت حفظ میشود و الزامی است |
|
||||
| `service_item_uuids` | string[] | ❌ | یک یا چند UUID سرویس. سرویسها **ذخیره** میشوند (`service_items`)، اولین سرویس سرویسِ اصلی (`service_item`) است، و مدت/بافر روی نوبت ثبت میشود (`service_total_minutes` / `service_buffer_minutes`). UUID ناموجود، سرویسِ غیرbookable، سرویس بدون مدت، یا سرویسِ محیطی دیگر ⇒ `422` |
|
||||
| `resource_uuid` | string (UUID) | ⚠️ | منبعی که نوبت **برایش** گرفته میشود (دستگاه، اتاق، یا خودِ پزشک). اگر داده شود `doctor_uuid` اختیاری است و برای منبعِ پزشک از خودش استنتاج میشود؛ محل نوبت هم از شعبهٔ همان منبع میآید. منبع باید در همان محیط رزرو باشد و اگر سرویس انتخابشده را ارائه ندهد ⇒ `422` |
|
||||
| `for_self` | boolean | ❌ | `true` (default) = patient is the logged-in payer; `false` = booking for someone else |
|
||||
| `patient_name` | string | ⚠️ | Required when `for_self=false`; otherwise filled from the payer's profile |
|
||||
| `patient_mobile` | string | ⚠️ | Required when `for_self=false`; otherwise the payer's mobile |
|
||||
@@ -259,6 +260,13 @@ Book an appointment slot.
|
||||
| `note` | string | ❌ | Patient note |
|
||||
| `city_id` | integer | ❌ | شناسهی شهرِ دامنهی جاری (از `city.json` سایت). برای گاردِ پورسانت نماینده: اگر شهر نمایندهی فعال داشته باشد، `booking_representation_id` نوبت ست میشود. پورسانت فقط وقتی واریز میشود که این نماینده با نمایندهی پزشک یکی باشد. خالی/ناموجود ⇒ بدون پورسانت |
|
||||
|
||||
> **`doctor_uuid` یا `resource_uuid`:** دستکم یکی الزامی است؛ نبودِ هر دو ⇒ `422`. مسیر
|
||||
> قدیمیِ فقط-`doctor_uuid` دستنخورده است و سایت عمومی همان را میفرستد.
|
||||
>
|
||||
> **پاسخ:** علاوه بر فیلدهای قبلی، `resource` (`uuid`, `name`, `type`) و `service_option`
|
||||
> (`uuid`, `name`) برمیگردند. نوبتهای پیش از مدل منبعمحور هر دو را `null` دارند، پس
|
||||
> کلاینت باید با `null` کنار بیاید.
|
||||
>
|
||||
> **مدت در حالت سرویسی:** مدت از `ServiceBookingCalculator` میآید — همان مؤلفهای که
|
||||
> `GET /api/v1/appointment-service-slots` هم با آن اسلاتها را میسازد. یعنی `solo` و
|
||||
> `additional` سرویسها لحاظ میشوند و نه جمعِ سادهٔ `duration_minutes`؛ وگرنه نوبتِ ثبتشده
|
||||
|
||||
@@ -237,6 +237,78 @@
|
||||
`PATCH` همان فیلدهاست؛ `address_uuid` پذیرفته **نمیشود** (جفت محیط از آدرس مشتق شده
|
||||
و write-once است) و `type_uuid` قابل تغییر است.
|
||||
|
||||
### `GET /api/v1/resource/{uuid}/services`
|
||||
|
||||
سرویسهایی که این منبع ارائه میدهد، با **مقدار مؤثر** و اینکه هر عدد از کدام سطح آمده.
|
||||
|
||||
`duration_minutes`/`price_rials` مقدارِ ثبتشده روی همین رابطهاند و `null` یعنی «ارث از
|
||||
سطح بالاتر»، نه صفر. `effective_*` نتیجهٔ زنجیرهٔ حل است و `*_source` میگوید کدام سطح
|
||||
برنده شده — بدون آن، پنل نمیتواند کنار خانهٔ خالی بنویسد عدد از کجا میآید.
|
||||
|
||||
زنجیره از خاص به عام: `resource_option` → `resource_service` → `branch` → `service_default`.
|
||||
|
||||
خروجی واقعی (۲۰۰):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"service_uuid": "f49baba9-68e9-4d61-aa90-fc8c784607e0",
|
||||
"service_name": "لیزر CO2",
|
||||
"duration_minutes": null,
|
||||
"price_rials": null,
|
||||
"active": true,
|
||||
"effective_duration_minutes": 40,
|
||||
"effective_price_rials": 18000000,
|
||||
"duration_source": "service_default",
|
||||
"price_source": "branch"
|
||||
},
|
||||
{
|
||||
"service_uuid": "7f13ab0d-2f64-4e8c-8e12-154172b6620a",
|
||||
"service_name": "ویزیت عمومی",
|
||||
"duration_minutes": null,
|
||||
"price_rials": 1200000,
|
||||
"active": true,
|
||||
"effective_duration_minutes": 15,
|
||||
"effective_price_rials": 1200000,
|
||||
"duration_source": "service_default",
|
||||
"price_source": "resource_option"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**دسترسی:** `appointment_settings.view`. **۴۰۴:** منبع محیط دیگر.
|
||||
|
||||
### `PUT /api/v1/resource/{uuid}/services`
|
||||
|
||||
جایگزینی **کامل**، مثل مهارتها: سرویسی که در بدنه نیست از این منبع برداشته میشود و
|
||||
`{"services":[]}` همه را پاک میکند.
|
||||
|
||||
```json
|
||||
{
|
||||
"services": [
|
||||
{ "service_uuid": "f49baba9-…", "duration_minutes": 15, "price_rials": 9500000, "active": true },
|
||||
{ "service_uuid": "7f13ab0d-…", "duration_minutes": "", "price_rials": null }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| فیلد | نوع | الزامی | توضیح |
|
||||
|---|---|---|---|
|
||||
| `service_uuid` | string (UUID) | ✅ | سرویس یا گزینهٔ سرویس؛ هر دو `ServiceItem` اند |
|
||||
| `duration_minutes` | int \| null \| `""` | ❌ | مدت اختصاصی این منبع. `null` و رشتهٔ خالی یعنی **ارث**، نه صفر. مقدار ≤ ۰ ⇒ `422` |
|
||||
| `price_rials` | int \| null \| `""` | ❌ | همان قاعده؛ منفی ⇒ `422`. صفرِ صریح یعنی رایگان و ارث نمیگیرد |
|
||||
| `active` | boolean | ❌ | پیشفرض `true`. غیرفعال یعنی «فعلاً این را نمیدهد» ولی اعداد ذخیرهشده میمانند |
|
||||
|
||||
پاسخ ۲۰۰ همان فهرست `GET` است (با مقادیر تازه حلشده).
|
||||
|
||||
**۴۲۲:** `services` غایب یا غیرآرایه · `service_uuid` غایب یا ناموجود · سرویسِ محیط دیگر
|
||||
(«سرویس انتخابشده به این محل نوبتدهی تعلق ندارد») · مدت یا قیمت نامعتبر.
|
||||
|
||||
> **این رابطه روی انتخاب منبع اثر میگذارد.** وقتی برای یک سرویس دستکم یک ردیف ثبت شده
|
||||
> باشد، `appointment-availability` فقط منابعی از **همان نوع** را کاندید میکند که ردیف
|
||||
> فعال دارند. تا وقتی هیچ ردیفی نیست، هیچ فیلتری اعمال نمیشود — محیطی که هنوز
|
||||
> رابطهها را پر نکرده نباید یکشبه بیوقت شود.
|
||||
|
||||
### `PUT /api/v1/resource/{uuid}/skills`
|
||||
|
||||
جایگزینی **کامل**: مهارتی که در بدنه نیست، برداشته میشود. `{"skills":[]}` همه را
|
||||
|
||||
Reference in New Issue
Block a user