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:
hamed
2026-08-01 23:05:33 +03:30
co-authored by Claude Opus 5
parent 26425bec31
commit 1972fdd20f
11 changed files with 300 additions and 19 deletions
+72
View File
@@ -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":[]}` همه را