# چک‌لیست — تسک ۰۷ (رزرو موقت و ثبت نهایی چندمنبعی)

**وضعیت کلی:** ✅ بک‌اند، تضمین دیتابیسی، مستندات و جریان رزرو (چند مورد UI ⏳ با مقصد) · **آخرین بازبینی:** —

قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)

---

## ۰. خط سرخ

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ✅ | |
| ۰.۲ | `active_slot_key` و `refreshActiveSlotKey()` دست‌نخورده و **فعال** | ✅ | دو تور ایمنی موازی |
| ۰.۳ | `slot_start`/`slot_end` باقی ماندند | ✅ | چهار مصرف‌کننده رویشان کوئری می‌زنند |
| ۰.۴ | `is_reserve` دست‌نخورده — رزرو هیچ ردیف اشغالی نمی‌سازد | ✅ | |
| ۰.۵ | `POST /api/v1/appointment` قدیمی بیت‌به‌بیت کار می‌کند | ✅ | `LegacyBookingUnchangedTest` |
| ۰.۶ | `PAYMENT_TTL` و رفتار انقضای موجود حفظ شد | ✅ | |

## ۱. تضمین همزمانی — قلب تسک

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `resource_occupancy_slot` با `UNIQUE(resource_id, bucket, unit_index)` | ✅ | ⭐ کل تضمین اینجاست |
| ۱.۲ | `BUCKET_SECONDS = 300` ثابت + کامنت هشدار تغییرش | ✅ | |
| ۱.۳ | سطل‌ها با `intdiv($end - 1, 300)` — نه بدون `-1` | ✅ | ⭐ وگرنه نوبت مجاور رد می‌شود |
| ۱.۴ | `unit_index` با **INSERT پشت‌سرهم**، نه `SELECT` قبلش | ✅ | ⭐ پنجرهٔ رقابت |
| ۱.۵ | ردیف‌ها مرتب بر `(resource_id, bucket, unit_index)` درج می‌شوند | ✅ | ⭐ جلوگیری از deadlock |
| ۱.۶ | `SlotTakenException` موجود بازاستفاده شد | ✅ | |
| ۱.۷ | محدودیت گرانولاریتی ۵ دقیقه در مستندات صریح | ✅ | |

## ۲. بک‌اند

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `ResourceOccupancy` · `AppointmentSegment` | ✅ | |
| ۲.۲ | `OccupancyWriter` — **تنها** نویسندهٔ `resource_occupancy` | ✅ | |
| ۲.۳ | `HoldService` · `BookingService` · `RescheduleService` | ✅ | |
| ۲.۴ | **یک ردیف per (بخش × منبع)** — نه per نوبت | ✅ | ⭐ آزادسازی ظرفیت |
| ۲.۵ | برنامه در `hold` **دوباره ساخته می‌شود**؛ `assignment` کلاینت فقط اعتبارسنجی می‌شود | ✅ | ⭐ سه نشتی ثبت‌شده از همین شکل بودند |
| ۲.۶ | منبع باید **کاندید همان نیازمندی** باشد، نه فقط هم‌محیط | ✅ | |
| ۲.۷ | `confirm` هفت مرحله در **یک** تراکنش | ✅ | |
| ۲.۸ | `confirm` idempotent — دوباره روی همان hold خطا نمی‌دهد | ✅ | |
| ۲.۹ | رویداد **بعد از** commit (`DispatchAfterCurrentBusStamp`) با uuid در payload | ✅ | ⭐ تسک ۱۲ رویش حساب می‌کند |
| ۲.۱۰ | `reschedule`: اول hold جدید، بعد آزادسازی قدیم | ✅ | ⭐ ترتیب |
| ۲.۱۱ | لغو = `status='released'` + **حذف فیزیکی** ردیف‌های سطل | ✅ | |
| ۲.۱۲ | `setup/cleanup` در بازهٔ اشغال، نه در `appointment_segments` | ✅ | |
| ۲.۱۳ | `STATUS_RESCHEDULED` + گذارهای مجاز | ✅ | |
| ۲.۱۴ | `ExpireAppointmentsHandler` موجود توسعه یافت | ✅ | `AppointmentExpiryService::expireHolds()` — همان زمان‌بند موجود |
| ۲.۱۵ | قلاب‌های تسک ۰۸ و ۰۹ در `confirm` (مراحل ۳ و ۶) | ✅ | |
| ۲.۱۶ | چهار endpoint | ✅ | |
| ۲.۱۷ | دو کد خطا در `ErrorCodes.php` با پیام فارسی | ✅ | `ERR_SLOT_TAKEN` · `ERR_HOLD_EXPIRED` |

## ۳. دیتابیس

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `resource_occupancy` (BIGINT id) با چهار ایندکس | ✅ | |
| ۳.۲ | `resource_occupancy_slot` با UNIQUE | ✅ | |
| ۳.۳ | `appointment_segments` با snapshot `name`/`segment_type` | ✅ | قانون پنجم |
| ۳.۴ | سه ستون تهی‌پذیر روی `appointments` | ✅ | `branch_id` · `plan_total_minutes` · `patient_facing_minutes` |
| ۳.۵ | ترتیب ستون ایندکس‌ها **دستی** در migration | ✅ | |
| ۳.۶ | `app:occupancy:backfill --force` — idempotent، نوبت‌های بی‌منبع را گزارش می‌کند | ✅ | ⭐ بدون آن رزرو جدید روی نوبت قدیم می‌نشیند |
| ۳.۷ | `app:occupancy:prune --older-than=90d` | ✅ | |
| ۳.۸ | `resource_occupancy_slot` در `AGGREGATE_CHILDREN` + هرگز کوئری مستقیم | ✅ | |
| ۳.۹ | `TenantSchemaCoverageTest` سبز | ✅ | |

## ۴. UI

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | تایمر شمارش معکوس hold | ✅ | ⭐ `HoldCountdown` — مبنا `expires_at` سرور است نه شمارندهٔ مرورگر؛ زیر یک دقیقه هشدار می‌شود. سه تست |
| ۴.۲ | خطای `409` با لیست جایگزین | ✅ | ⭐ انتخاب کهنه کنار گذاشته و جستجو دوباره اجرا می‌شود؛ جایگزین‌ها بلافاصله روی صفحه‌اند |
| ۴.۳ | خطای `reschedule` شامل «نوبت فعلی تغییری نکرد» | ✅ | ⭐ جابه‌جایی با `?rebook=<uuid>` روی همان صفحهٔ رزرو؛ رزرو جدید اول گرفته می‌شود، پس شکستش نوبت قدیم را دست‌نخورده می‌گذارد و پیام سرور همین را می‌گوید |
| ۴.۴ | مسدودسازی موردی منبع از صفحهٔ منابع | ✅ | `ResourceBlocksModal` + سه اندپوینت تازه؛ ۶ تست بک‌اند |
| ۴.۵ | تفکیک «مسدودسازی موردی» از «بلندمدت» در UI | ✅ | ⭐ تفاوتشان بالای همان مودال نوشته شده، نه فقط در مستندات |
| ۴.۶ | هیچ رنگ/شعاع hard-code | ✅ | |
| ۴.۷ | دارک‌مود و حالت فشرده | ✅ | اسکرین‌شات واقعی در دارک‌مود و حالت فشرده؛ ایرادی نماند |
| ۴.۸ | RTL و موبایل | ✅ | جدول وقت‌ها اسکرول افقی داخلی دارد |
| ۴.۹ | همهٔ رشته‌ها فارسی | ✅ | |
| ۴.۱۰ | بخش‌های نوبت در `AppointmentDetailPage` | ✅ | `GET /appointment/{uuid}/segments` + `AppointmentSegmentsCard`؛ بخشِ بدون حضور بیمار کم‌رنگ است و فهرست خالی یعنی نوبت اسلاتی (کارت رندر نمی‌شود) |
| ۴.۱۱ | جریان سه‌مرحله‌ای رزرو | ✅ | ⭐ `ResourceBookingPage`: جستجو → نگه‌داشتن → ثبت، با آزادسازی صریح |
| ۴.۱۲ | انتخاب پزشک پیش از ثبت | ✅ | ثبت نهایی بدون پزشک ممکن نیست؛ دکمه تا انتخاب نشدن غیرفعال است |

## ۵. تست

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `ConcurrentHoldTest` — **دو اتصال واقعی**، دقیقاً یکی موفق | ✅ | ⭐⭐ mock قبول نیست |
| ۵.۲ | `OccupancyWriterTest` — بازهٔ مماس، capacity، ترتیب INSERT | ✅ | |
| ۵.۳ | `HoldLifecycleTest` — hold/انقضا/آزادسازی زودهنگام | ✅ | |
| ۵.۴ | `BookingConfirmTest` — hold دیگری ۴۰۴، منقضی ۴۰۹، idempotent | ✅ | |
| ۵.۵ | `CapacityReleaseIntegrationTest` | ✅ | ⭐⭐ اپراتور در بازهٔ انتظار ردیف ندارد |
| ۵.۶ | `RescheduleTest` — شکست hold جدید → نوبت قدیم سالم | ✅ | |
| ۵.۷ | `OccupancyBackfillTest` — idempotent | ✅ | |
| ۵.۸ | `LegacyBookingUnchangedTest` | ✅ | ⭐ |
| ۵.۹ | `BookingTenantTest` موجود سبز | ✅ | |

## ۶. مستندات

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/appointment-booking.md` | ✅ | |
| ۶.۲ | گرانولاریتی ۵ دقیقه و محدودیتش | ✅ | |
| ۶.۳ | قرارداد `hold_uuid` و TTL | ✅ | |
| ۶.۴ | تفکیک مسدودسازی موردی/بلندمدت | ✅ | |
| ۶.۵ | `docs/architecture/booking-concurrency.md` — سطل زمانی + دلیل رد دو گزینهٔ دیگر | ✅ | ⭐ شش ماه بعد زیر سؤال می‌رود |

## ۷. بازبینی پایانی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ✅ | |
| ۷.۲ | `bin/phpunit` کامل سبز | ✅ | |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ✅ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ✅ | |
| ۷.۵ | `npx tsc --noEmit` و `yarn test` سبز | ✅ | |
| ۷.۶ | تست‌های tenant سبز | ✅ | |
| ۷.۷ | `docs/api/*` به‌روز | ✅ | |
| ۷.۸ | چک‌لیست UI کامل | ✅ | |
| ۷.۹ | دو کلاینت دیگر بررسی شدند | ✅ | `slot_start/slot_end` سالم است؟ |
| ۷.۱۰ | commit، سپس `graphify update .` | ✅ | |
| ۷.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ✅ | |
