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:
+8
-7
@@ -95,17 +95,18 @@ POST /api/v1/auth/switch-context {"db_uuid": "<clinic_uuid>"}
|
|||||||
| ۰۵ برنامهٔ چندبخشی | سرویس اصلی هر محیط سه بخش دارد: بیحسی ۵د (اتاق انحصاری) → انتظار ۳۰د (اتاق **passive**) → لیزر ۲۰د (اتاق + دستگاه) | `GET /api/v1/service-item/{uuid}/segments` |
|
| ۰۵ برنامهٔ چندبخشی | سرویس اصلی هر محیط سه بخش دارد: بیحسی ۵د (اتاق انحصاری) → انتظار ۳۰د (اتاق **passive**) → لیزر ۲۰د (اتاق + دستگاه) | `GET /api/v1/service-item/{uuid}/segments` |
|
||||||
| ۰۶ موتور دسترسپذیری | همان برنامه با منابع واقعی جستجو میشود | `POST /api/v1/appointment-availability` |
|
| ۰۶ موتور دسترسپذیری | همان برنامه با منابع واقعی جستجو میشود | `POST /api/v1/appointment-availability` |
|
||||||
| ۰۷ نگهداشتن و ثبت | **۲ نوبت در هر محیط از مسیر واقعی** hold → confirm، با بخش و اشغال منبع | `GET /api/v1/appointment/{uuid}/segments` |
|
| ۰۷ نگهداشتن و ثبت | **۲ نوبت در هر محیط از مسیر واقعی** hold → confirm، با بخش و اشغال منبع | `GET /api/v1/appointment/{uuid}/segments` |
|
||||||
|
| **منبع↔سرویس** | رابطهٔ چندبهچند با مدت و قیمت اختصاصی: دو دستگاه یک سرویس را با اعداد متفاوت میدهند | `GET /api/v1/resource/{uuid}/services` |
|
||||||
|
| **دستهٔ مشترک** | دسته روی سرویس و منبع؛ یال «شامل بودن» بین دستهها | مودال «سرویسها» در صفحهٔ منابع |
|
||||||
| ۰۸ عکس قیمت | لیست قیمت فعال + `PriceSnapshot` روی نوبتهای واقعی با بیعانه | `GET /api/v1/price-lists` |
|
| ۰۸ عکس قیمت | لیست قیمت فعال + `PriceSnapshot` روی نوبتهای واقعی با بیعانه | `GET /api/v1/price-lists` |
|
||||||
| ۰۹ موتور سیاست | **۶ سیاست، یکی از هر دسته** (انتخاب، صلاحیت، منبع، زمان، فاصله، قیمت) | `GET /api/v1/policies` |
|
|
||||||
| ۱۱ پکیج و دفتر اعتبار | پکیج ۶ جلسهای + ۲ پکیج خریداریشده + ردیف مصرف در دفتر | `GET /api/v1/packages` |
|
> **تسکهای ۹ تا ۱۴ (سیاست، پکیج، دوره، لغو/انتظار، رویداد و گزارش) به تصمیم مالک محصول
|
||||||
| ۱۲ دورهٔ درمان | پروتکل ۶ جلسهای با پارامتر انرژی هر جلسه + دورهٔ فعال با ۶ جلسه | `GET /api/v1/treatment-course/{uuid}` |
|
> از محصول حذف شدهاند.** مدل فعلی فقط منبع، سرویس و گزینه است —
|
||||||
| ۱۳ لغو و لیست انتظار | سیاست عمومی + سیاست سختگیرانه روی یک سرویس، رکورد عدم حضور، ۲ نفر در لیست انتظار | `GET /api/v1/waitlist` · `/cancellation-policy` |
|
> [`docs/architecture/resource-first-model.md`](docs/architecture/resource-first-model.md).
|
||||||
| ۱۴ رویداد و بهرهوری | `HoldCreated` و `AppointmentBooked` در outbox + دقیقهٔ اشغال واقعی هر منبع | `GET /api/v1/reports/resource-utilization?branch_uuid=…` |
|
|
||||||
|
|
||||||
نوبتهای موتور جدید **از `new Appointment(...)` ساخته نمیشوند**؛ از
|
نوبتهای موتور جدید **از `new Appointment(...)` ساخته نمیشوند**؛ از
|
||||||
`AppointmentPlanBuilder` → `AvailabilityEngine` → `HoldService` → `BookingService` رد
|
`AppointmentPlanBuilder` → `AvailabilityEngine` → `HoldService` → `BookingService` رد
|
||||||
میشوند. برای همین اشغال منبع و رویداد دامنه واقعیاند: گزارش بهرهوری عدد نشان میدهد و
|
میشوند. برای همین اشغال منبع واقعی است و تقویم دستگاه واقعاً پر است — نه ردیفهایی که
|
||||||
تقویم دستگاه واقعاً پر است.
|
با INSERT ساخته شدهاند و هیچوقت از موتور رد نشدهاند.
|
||||||
|
|
||||||
## چه چیزی با این داده قابل تست است
|
## چه چیزی با این داده قابل تست است
|
||||||
|
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ Book an appointment slot.
|
|||||||
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
|
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
|
||||||
| `slot_end` | integer | ⚠️ | Slot end (Unix timestamp). **با `service_item_uuids` نادیده گرفته میشود** و سرور خودش حساب میکند (به مقدار کلاینت اعتماد نمیشود)؛ در آن حالت الزامی هم نیست. بدون سرویس، مقدار کلاینت حفظ میشود و الزامی است |
|
| `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` |
|
| `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 |
|
| `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_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 |
|
| `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 |
|
| `note` | string | ❌ | Patient note |
|
||||||
| `city_id` | integer | ❌ | شناسهی شهرِ دامنهی جاری (از `city.json` سایت). برای گاردِ پورسانت نماینده: اگر شهر نمایندهی فعال داشته باشد، `booking_representation_id` نوبت ست میشود. پورسانت فقط وقتی واریز میشود که این نماینده با نمایندهی پزشک یکی باشد. خالی/ناموجود ⇒ بدون پورسانت |
|
| `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` میآید — همان مؤلفهای که
|
> **مدت در حالت سرویسی:** مدت از `ServiceBookingCalculator` میآید — همان مؤلفهای که
|
||||||
> `GET /api/v1/appointment-service-slots` هم با آن اسلاتها را میسازد. یعنی `solo` و
|
> `GET /api/v1/appointment-service-slots` هم با آن اسلاتها را میسازد. یعنی `solo` و
|
||||||
> `additional` سرویسها لحاظ میشوند و نه جمعِ سادهٔ `duration_minutes`؛ وگرنه نوبتِ ثبتشده
|
> `additional` سرویسها لحاظ میشوند و نه جمعِ سادهٔ `duration_minutes`؛ وگرنه نوبتِ ثبتشده
|
||||||
|
|||||||
@@ -237,6 +237,78 @@
|
|||||||
`PATCH` همان فیلدهاست؛ `address_uuid` پذیرفته **نمیشود** (جفت محیط از آدرس مشتق شده
|
`PATCH` همان فیلدهاست؛ `address_uuid` پذیرفته **نمیشود** (جفت محیط از آدرس مشتق شده
|
||||||
و write-once است) و `type_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`
|
### `PUT /api/v1/resource/{uuid}/skills`
|
||||||
|
|
||||||
جایگزینی **کامل**: مهارتی که در بدنه نیست، برداشته میشود. `{"skills":[]}` همه را
|
جایگزینی **کامل**: مهارتی که در بدنه نیست، برداشته میشود. `{"skills":[]}` همه را
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# مدل منبعمحور: منبع، سرویس، گزینه
|
||||||
|
|
||||||
|
سیستم نوبتدهی روی **منبع** ساخته شده، نه روی پزشک. منبع هر چیزی است که زمانش رزرو
|
||||||
|
میشود: پزشک، دستگاه لیزر، یونیت دندانپزشکی، اتاق عمل، مربی. همین باعث میشود از مطب یک
|
||||||
|
پزشک تا مرکز لیزر و درمانگاه، همه با یک مدل داده کار کنند.
|
||||||
|
|
||||||
|
## سه موجودیت
|
||||||
|
|
||||||
|
| مفهوم | کجاست | نکته |
|
||||||
|
|---|---|---|
|
||||||
|
| **منبع** | `ClinicResource` | تقویم، استثنا، مهارت، ظرفیت و اشغال دارد. پزشک/پرسنل/اتاق با `ResourceLinker` به منبع وصل میشوند |
|
||||||
|
| **سرویس** | `ServiceItem` | مدت (solo/additional)، قیمت، دستهٔ کاتالوگ |
|
||||||
|
| **گزینهٔ سرویس** | باز هم `ServiceItem` | عضو یک `ItemGroup` با بازهٔ انتخاب (`select_min`/`select_max`) |
|
||||||
|
|
||||||
|
**چرا گزینه جدول جدا ندارد:** «لیزر» و «لیزر پا» هر دو یک چیزند — چیزی که قیمت و مدت
|
||||||
|
دارد و میشود رزروش کرد. جدول سوم یعنی `PriceListItem`، `Tariff`،
|
||||||
|
`appointment_service_items`، `SegmentTemplate` و کل مسیر `appointment-service-slots`
|
||||||
|
باید دو نوع ورودی بشناسند؛ یعنی دو منبع حقیقت برای یک مفهوم.
|
||||||
|
|
||||||
|
## رابطهٔ منبع↔سرویس
|
||||||
|
|
||||||
|
`ResourceServiceOffering` — چندبهچند با تنظیمات اختصاصی:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource_service_offerings(resource_id, service_item_id, duration_minutes, price_rials, active)
|
||||||
|
```
|
||||||
|
|
||||||
|
- ردیف با **آیتم والد** = «منبع + سرویس»؛ ردیف با **آیتم عضو گروه** = «منبع + گزینه».
|
||||||
|
یک جدول، هر دو سطحِ سند.
|
||||||
|
- `null` یعنی «ارث از سطح بالاتر»، **نه صفر**. صفرِ صریح یک مقدار واقعی است (سرویس رایگان).
|
||||||
|
- `active = false` یعنی «فعلاً این منبع این را نمیدهد» — ردیف میماند تا تنظیماتش با
|
||||||
|
خاموش/روشن کردن از دست نرود.
|
||||||
|
- فرزند aggregate منبع است: ستون محیط ندارد چون منبع خودش دارد، و سازنده اجبار میکند
|
||||||
|
منبع و سرویس در یک محیط باشند.
|
||||||
|
|
||||||
|
## زنجیرهٔ حل مدت و قیمت
|
||||||
|
|
||||||
|
`ResourceServiceResolver` — از خاص به عام:
|
||||||
|
|
||||||
|
```
|
||||||
|
۱. منبع + گزینه → ResourceServiceOffering(resource, option) source: resource_option
|
||||||
|
۲. منبع + سرویس → ResourceServiceOffering(resource, parent) source: resource_service
|
||||||
|
۳. شعبه + آیتم → ServiceBranchOverride(item, address) source: branch
|
||||||
|
۴. پیشفرض آیتم → ServiceItem source: service_default
|
||||||
|
```
|
||||||
|
|
||||||
|
دو قاعده که سکوتشان باگ میسازد:
|
||||||
|
|
||||||
|
- **مدت و قیمت جدا حل میشوند.** منبعی که فقط مدتش فرق دارد نباید قیمتش هم از همان سطح
|
||||||
|
بیاید، وگرنه اولین override تعرفهٔ شعبه را بیصدا میبلعد.
|
||||||
|
- **منبعِ هر مقدار برگردانده میشود** (`durationSource` / `priceSource`). بدون آن، پنل
|
||||||
|
نمیتواند کنار عدد بنویسد «از شعبه»، و «چرا این عدد؟» میشود جستوجو در چهار جدول.
|
||||||
|
|
||||||
|
## انتخاب منبع
|
||||||
|
|
||||||
|
`ClinicResourceRepository::findEligible($address, $type, $skillIds, $service)`:
|
||||||
|
|
||||||
|
1. شعبه + نوع + فعال
|
||||||
|
2. مهارتهای لازم (همه، نه یکی)
|
||||||
|
3. **رابطهٔ سرویس** — فقط اگر برای آن سرویس و **همان نوع منبع** ردیفی ثبت شده باشد
|
||||||
|
4. ترتیب: منابعی که دستهٔ سرویس را پوشش میدهند اول
|
||||||
|
|
||||||
|
بند ۳ دو قید دارد که هر کدام یک شکست واقعی را جلو گرفتهاند:
|
||||||
|
|
||||||
|
- **مشروط بودن:** محیطی که هنوز رابطهها را پر نکرده باید مثل قبل کار کند، وگرنه با
|
||||||
|
اولین deploy بیوقت میشود.
|
||||||
|
- **محدود به نوع:** «کدام منبع این سرویس را میدهد» دربارهٔ نقشِ انجامدهنده است. بدون این
|
||||||
|
قید، ثبت رابطه برای دستگاهها باعث میشد اتاقِ همان برنامه واجد شرایط نباشد و کل رزرو
|
||||||
|
بشکند.
|
||||||
|
|
||||||
|
## دستهبندی
|
||||||
|
|
||||||
|
دسته سراسریِ محیط است و **هم سرویسها هم منابع** از آن استفاده میکنند
|
||||||
|
(`ServiceItem::$catalogCategory` و `resource_catalog_categories`).
|
||||||
|
|
||||||
|
دو رابطهٔ متفاوت که نباید قاطی شوند:
|
||||||
|
|
||||||
|
| رابطه | جدول | معنا |
|
||||||
|
|---|---|---|
|
||||||
|
| سلسلهمراتب نمایشی | `CatalogCategory::$parent` | چیدمان منو؛ درخت، تکوالدی، `MAX_DEPTH = 4` |
|
||||||
|
| **شامل بودن** | `catalog_category_includes` | «تمام بدن شامل دست است»؛ گراف جهتدار بدون دور |
|
||||||
|
|
||||||
|
درخت برای «شامل بودن» کافی نیست: «دست» باید همزمان زیر «تمام بدن» و «اندام فوقانی»
|
||||||
|
باشد و درخت تکوالدی این را نمیتواند بگوید.
|
||||||
|
|
||||||
|
`CategoryClosureResolver` بستار گذرا را حساب میکند (تمام بدن → نیمتنه → پا ⇒ تمام بدن
|
||||||
|
شامل پا). همهٔ یالهای محیط با یک کوئری خوانده و در حافظه پیمایش میشوند؛ مجموعهٔ
|
||||||
|
بازدیدشده همزمان نتیجه و محافظ دور است، و `assertNoCycle` ساختِ حلقه را رد میکند.
|
||||||
|
|
||||||
|
**تعارض انتخاب:** انتخاب همزمان دو آیتم که دستهٔ یکی دیگری را در بر میگیرد ⇒ `422` با
|
||||||
|
کد `category_overlap`. این جای رابطهٔ دستی `incompatible_with` را برای حالت ناحیهای
|
||||||
|
میگیرد — یک بار روی دسته، نه بهازای هر جفت آیتم. آن رابطه برای ناسازگاریهایی که ربطی
|
||||||
|
به ناحیه ندارند سرِ جایش میماند.
|
||||||
|
|
||||||
|
## نوبت
|
||||||
|
|
||||||
|
نوبت هم منبع را نگه میدارد هم گزینه را، و عددهایش را snapshot میکند:
|
||||||
|
|
||||||
|
| ستون | چرا |
|
||||||
|
|---|---|
|
||||||
|
| `resource_id` | نوبت **برای** کدام منبع است. تکرار `resource_occupancy` نیست: آن میگوید چه چیزی و کِی اشغال شد (شامل اتاقِ یک بخش)، این میگوید بیمار چه چیزی را انتخاب کرد |
|
||||||
|
| `service_option_item_id` | بدون دانستن گزینه، بازتولید عددِ ذخیرهشده ممکن نیست |
|
||||||
|
| `service_total_minutes` | از خروجی resolver، نه از `ServiceItem` |
|
||||||
|
| `PriceSnapshot` | قیمتِ لحظهٔ رزرو؛ تغییر فردای تعرفه صورتحساب دیروز را تکان نمیدهد |
|
||||||
|
|
||||||
|
هر دو ستون تهیپذیرند: نوبتهای پیش از این مدل منبع ندارند و migration نباید بشکندشان.
|
||||||
|
|
||||||
|
## رزرو
|
||||||
|
|
||||||
|
`POST /api/v1/appointment` هم `doctor_uuid` میپذیرد هم `resource_uuid`:
|
||||||
|
|
||||||
|
- با منبعِ پزشک، پزشک از خودِ منبع استنتاج میشود.
|
||||||
|
- محل نوبت از **شعبهٔ منبع** میآید؛ فرستادن جداگانهٔ `clinic_uuid` فقط راهی برای ناسازگار
|
||||||
|
کردن این دو بود.
|
||||||
|
- منبع باید در همان محیط رزرو باشد. uuid از بدنهٔ درخواست میآید و `TenantFilter` پوششش
|
||||||
|
نمیدهد، پس بدون این بررسی بیمار میتوانست دستگاه کلینیک دیگری را روی نوبت این کلینیک
|
||||||
|
بنشاند.
|
||||||
|
- منبعی که سرویس انتخابشده را ارائه نمیدهد همانجا رد میشود، نه وقتی بیمار سرِ قرار
|
||||||
|
حاضر شده.
|
||||||
|
|
||||||
|
جزئیات قرارداد: [`docs/api/appointment.md`](../api/appointment.md) و
|
||||||
|
[`docs/api/resource.md`](../api/resource.md).
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۰۹ (موتور قوانین ششدستهای)
|
# چکلیست — تسک ۰۹ (موتور قوانین ششدستهای)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۱۰ (فرم ساخت قانون و محیط آزمایش)
|
# چکلیست — تسک ۱۰ (فرم ساخت قانون و محیط آزمایش)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۱۱ (پکیج و دفتر اعتبار جلسات)
|
# چکلیست — تسک ۱۱ (پکیج و دفتر اعتبار جلسات)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۱۲ (دوره درمان)
|
# چکلیست — تسک ۱۲ (دوره درمان)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)
|
# چکلیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# چکلیست — تسک ۱۴ (رویدادهای دامنه و گزارش بهرهوری)
|
# چکلیست — تسک ۱۴ (رویدادهای دامنه و گزارش بهرهوری)
|
||||||
|
|
||||||
|
> # ⛔ این تسک از محصول حذف شد
|
||||||
|
>
|
||||||
|
> **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز
|
||||||
|
> خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت
|
||||||
|
> «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
|
||||||
|
>
|
||||||
|
> ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر **تاریخچه**اند، نه کار
|
||||||
|
> جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
|
||||||
|
>
|
||||||
|
> مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md)
|
||||||
|
> و چکلیست [تسک ۱۵](../task-15-resource-first-model/checklist.md).
|
||||||
|
|
||||||
|
|
||||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||||
|
|
||||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||||
|
|||||||
@@ -135,21 +135,21 @@
|
|||||||
|
|
||||||
| # | مورد | وضعیت | یادداشت |
|
| # | مورد | وضعیت | یادداشت |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| ۹.۱ | `docs/api/appointment.md` — `resource_uuid` و پاسخ جدید | ⏳ | |
|
| ۹.۱ | `docs/api/appointment.md` — `resource_uuid` و پاسخ جدید | ✅ | `resource_uuid` در جدول فیلدها + پاسخ `resource`/`service_option` |
|
||||||
| ۹.۲ | `docs/api/appointment.md` — بخشهای حذفشده پاک شد | ⏳ | |
|
| ۹.۲ | `docs/api/appointment.md` — بخشهای حذفشده پاک شد | ✅ | `docs/api/appointment.md` هیچ ارجاعی به دامنههای حذفشده نداشت — بررسی شد |
|
||||||
| ۹.۳ | `docs/api/clinic.md` — endpoint سرویسهای منبع | ⏳ | |
|
| ۹.۳ | `docs/api/clinic.md` — endpoint سرویسهای منبع | ✅ | `docs/api/resource.md` — `GET/PUT resource/{uuid}/services` با JSON واقعی از اجرای واقعی |
|
||||||
| ۹.۴ | سند معماری مدل منبعمحور | ⏳ | |
|
| ۹.۴ | سند معماری مدل منبعمحور | ✅ | `docs/architecture/resource-first-model.md` |
|
||||||
| ۹.۵ | چکلیست تسکهای ۹ تا ۱۴ با وضعیت «حذفشده» | ⏳ | |
|
| ۹.۵ | چکلیست تسکهای ۹ تا ۱۴ با وضعیت «حذفشده» | ✅ | بنر ⛔ روی چکلیست تسکهای ۹ تا ۱۴ با تاریخ و کامیت مرجع |
|
||||||
| ۹.۶ | `TEST_USERS.md` بهروز شد | ⏳ | |
|
| ۹.۶ | `TEST_USERS.md` بهروز شد | ✅ | جدول موتور نوبتدهی بهروز شد؛ ردیفهای حذفشده رفتند و منبع↔سرویس و دستهٔ مشترک آمدند |
|
||||||
|
|
||||||
## ۱۰. تأیید نهایی
|
## ۱۰. تأیید نهایی
|
||||||
|
|
||||||
| # | مورد | وضعیت | یادداشت |
|
| # | مورد | وضعیت | یادداشت |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| ۱۰.۱ | `ddev exec php bin/phpunit` کامل سبز | ⏳ | |
|
| ۱۰.۱ | `ddev exec php bin/phpunit` کامل سبز | ✅ | `1304 tests, 3776 assertions` سبز |
|
||||||
| ۱۰.۲ | `--group=slot-mode-frozen` سبز | ⏳ | خط قرمز |
|
| ۱۰.۲ | `--group=slot-mode-frozen` سبز | ✅ | `OK (3 tests, 8 assertions)` — خط قرمز دستنخورده |
|
||||||
| ۱۰.۳ | `phpstan` روی baseline ۱۴ خطا | ⏳ | |
|
| ۱۰.۳ | `phpstan` روی baseline ۱۴ خطا | ✅ | ۱۴ خطا، همان baseline |
|
||||||
| ۱۰.۴ | `npx tsc --noEmit` بدون خطا | ⏳ | |
|
| ۱۰.۴ | `npx tsc --noEmit` بدون خطا | ✅ | بدون خطا |
|
||||||
| ۱۰.۵ | `npx vitest run assets/admin` سبز | ⏳ | |
|
| ۱۰.۵ | `npx vitest run assets/admin` سبز | ✅ | ۶۴۸ تست در ۹۸ فایل سبز |
|
||||||
| ۱۰.۶ | `app:seed-scenarios --reset -n` بدون خطا | ⏳ | |
|
| ۱۰.۶ | `app:seed-scenarios --reset -n` بدون خطا | ✅ | `--reset -n` بدون خطا؛ سیدر حالا رابطهٔ منبع↔سرویس هم میسازد |
|
||||||
| ۱۰.۷ | کامیت + `graphify update` + کامیت گراف | ⏳ | |
|
| ۱۰.۷ | کامیت + `graphify update` + کامیت گراف | ⏳ | |
|
||||||
|
|||||||
Reference in New Issue
Block a user