# چک‌لیست — تسک ۰۷ (رزرو موقت و ثبت نهایی چندمنبعی) **وضعیت کلی:** ✅ بک‌اند، تضمین دیتابیسی، مستندات و جریان رزرو (چند مورد 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=` روی همان صفحهٔ رزرو؛ رزرو جدید اول گرفته می‌شود، پس شکستش نوبت قدیم را دست‌نخورده می‌گذارد و پیام سرور همین را می‌گوید | | ۴.۴ | مسدودسازی موردی منبع از صفحهٔ منابع | ✅ | `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 .` | ✅ | | | ۷.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ✅ | |