Files
hamedandClaude Opus 5 ca2f9b8652 feat(admin): build the last two screens, and pin spacing with a test
The cancellation policy page showed only the tenant policy, so nothing said
which services opt out of it. Service policies do not blend with the tenant one
— a service that has its own follows it completely — and without the table an
operator cannot tell why one service's penalty differs. It lists them with a
link to each service.

The waitlist had the matches endpoint and no way to reach it. The list answers
"who is waiting"; the question asked when capacity frees up is "who is waiting
for this slot", so the page now takes a service and a date and answers that.
The note says plainly that cancelling notifies them anyway — this is for
looking before deciding, not a second notification path.

Spacing is enforced at hold time rather than during candidate generation, which
costs one slot being shown and then refused, and saves a patient-history query
per candidate. That trade had no test; now a booking five days after the last
one is refused and one thirty days later goes through.

Checklists across all sixteen tasks are final: no pending rows, and the
warnings that remain are recorded decisions — one resolver instead of six
engines, a closed list instead of a registry, sample size three instead of ten
— each with the reason it was taken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:32:58 +03:30

9.3 KiB
Raw Permalink Blame History

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

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

قواعد: _shared/definition-of-done.md · red-lines.md · 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 .
۷.۱۱ موارد به‌تعویق با دلیل و تسک مقصد