# چک‌لیست — تسک ۰۷ (رزرو موقت و ثبت نهایی چندمنبعی) **وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** — قواعد: [_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` موجود توسعه یافت | ⏳ | آزادسازی + حذف سطل | | ۲.۱۵ | قلاب‌های تسک ۰۸ و ۰۹ در `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 در UI رزرو | ⏳ | | | ۴.۲ | خطای `409` با پیام «این ساعت همین لحظه رزرو شد» + **لیست جایگزین خودکار** | ⏳ | ⭐ مستند بند ۱۷ | | ۴.۳ | خطای `reschedule` شامل «نوبت فعلی تغییری نکرد» | ⏳ | ⭐ | | ۴.۴ | مسدودسازی موردی منبع از صفحهٔ منابع | ⏳ | | | ۴.۵ | تفکیک «مسدودسازی موردی» (occupancy) از «بلندمدت» (exception) در UI روشن است | ⏳ | دو راه یک کار گیج‌کننده است | | ۴.۶ | هیچ رنگ/شعاع hard-code | ⏳ | | | ۴.۷ | دارک‌مود و حالت فشرده | ⏳ | | | ۴.۸ | RTL و موبایل | ⏳ | | | ۴.۹ | همهٔ رشته‌ها فارسی | ⏳ | | | ۴.۱۰ | `AppointmentDetailPage` بخش بخش‌های نوبت (فقط حالت `resource`) | ⏳ | | ## ۵. تست | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۵.۱ | `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 .` | ⏳ | | | ۷.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |