feat: implement cancellation policy, no-show tracking, and waitlist management
- Add implementation notes for cancellation and waitlist features. - Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting. - Establish architecture for domain events and outbox pattern to ensure reliable event publishing. - Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports. - Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# تسک ۰۷ — رزرو موقت و ثبت نهایی چندمنبعی
|
||||
|
||||
**فاز:** ۱ (هسته) · **وابستگی:** ۰۶ · **زمان:** ۱۶-۲۰ ساعت
|
||||
|
||||
---
|
||||
|
||||
## هدف
|
||||
|
||||
مستند بند ۱۱ و قانون سوم جمعبندی: «جلوگیری از رزرو تکراری کار دیتابیس است، نه کار کد».
|
||||
سه مرحلهٔ `جستجو → رزرو موقت → ثبت نهایی` روی **چند منبع** پیاده شود، با تضمین یکتایی
|
||||
در سطح دیتابیس.
|
||||
|
||||
## وضعیت فعلی — نقطهٔ قوت پروژه
|
||||
|
||||
```php
|
||||
// src/Appointment/Entity/Appointment.php
|
||||
public const PAYMENT_TTL = 900;
|
||||
#[ORM\Column(name: 'active_slot_key', length: 64, nullable: true, unique: true)]
|
||||
private ?string $activeSlotKey = null; // "{doctorId}:{slotStart}" یا NULL
|
||||
|
||||
private function refreshActiveSlotKey(): void {
|
||||
$this->activeSlotKey = !$this->isReserve && in_array($this->status, self::SLOT_OCCUPYING_STATUSES, true)
|
||||
? sprintf('%d:%d', $this->doctor->getId(), $this->slotStart)
|
||||
: null;
|
||||
}
|
||||
```
|
||||
|
||||
سه مرحله و تضمین دیتابیسی **از قبل درست پیاده شدهاند**. محدودیت: کلید فقط
|
||||
`doctor + slot_start` است و هیچ منبع دیگری را نمیپوشاند، و مدل «یک ردیف = یک بازهٔ پیوسته»
|
||||
است.
|
||||
|
||||
## دامنه
|
||||
|
||||
**هست:**
|
||||
- `resource_occupancy` — تنها مرجع حقیقت اشغال منابع
|
||||
- `appointment_segments` — بخشهای نوبت ثبتشده
|
||||
- `HoldService` — رزرو موقت چندمنبعی با TTL
|
||||
- `BookingService` — ثبت نهایی اتمی
|
||||
- تضمین یکتایی در MariaDB (بدون `EXCLUDE` — راهحل «سطل زمانی»)
|
||||
- انقضای خودکار hold ها (توسعهٔ `ExpireAppointmentsHandler` موجود)
|
||||
- وضعیت `rescheduled` و رویداد جابهجایی
|
||||
|
||||
**نیست:** قیمتگذاری تفکیکشده (تسک ۰۸)، سیاست لغو و جریمه (تسک ۱۳).
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح |
|
||||
|---|---|---|
|
||||
| POST | `/api/v1/appointment-hold` | رزرو موقت یک زمان + منابعش |
|
||||
| DELETE | `/api/v1/appointment-hold/{uuid}` | آزادسازی زودهنگام |
|
||||
| POST | `/api/v1/appointment-confirm` | ثبت نهایی از یک hold معتبر |
|
||||
| POST | `/api/v1/appointment/{uuid}/reschedule` | جابهجایی (hold جدید + آزادسازی قدیم، اتمی) |
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ موفق: `POST /appointment-hold` با زمان و `assignment` معتبر → `201` با
|
||||
`hold_uuid` و `expires_at`؛ ردیفهای `resource_occupancy` با `status='hold'` برای
|
||||
**هر بخش × هر منبع** ثبت میشوند.
|
||||
- ✅ موفق: بلافاصله بعد از hold، `POST /appointment-availability` همان زمان را **برنمیگرداند**.
|
||||
- ✅ موفق: `POST /appointment-confirm` → `200`، وضعیت `confirmed`، همهٔ ردیفهای اشغال
|
||||
`status='booked'` و `expires_at = NULL`.
|
||||
- ✅ موفق (**تست همزمانی — اصلیترین**): دو درخواست hold همزمان روی همان منبع و بازه →
|
||||
دقیقاً **یکی** `201` و دیگری `409` با `ERR_SLOT_TAKEN`. تست باید با تراکنش واقعی
|
||||
موازی اجرا شود، نه mock.
|
||||
- ✅ موفق: hold منقضیشده → `POST /appointment-confirm` با `409 ERR_HOLD_EXPIRED` و آن
|
||||
زمان دوباره در جستجو ظاهر میشود.
|
||||
- ✅ موفق: آزادسازی ظرفیت حفظ میشود — اپراتور در بخش انتظار ردیف اشغال **ندارد**.
|
||||
- ❌ خطا: `confirm` با hold متعلق به کاربر دیگر → `404`.
|
||||
- ❌ خطا: hold با منبعی که در `assignment` نیست ولی نیازمندی دارد → `422`.
|
||||
- ⚠️ مرزی: منبع با `capacity=3` → سه hold همزمان موفق، چهارمی `409`.
|
||||
- ⚠️ مرزی: لغو نوبت → همهٔ ردیفهای اشغالش آزاد (`status='released'`)، نه حذف فیزیکی.
|
||||
- ⚠️ مرزی: `reschedule` که hold جدیدش شکست بخورد → نوبت قدیمی **دستنخورده** بماند.
|
||||
- ⚠️ مرزی: نوبتهای حالت `slot`/`service` → `active_slot_key` قدیمی همچنان کار میکند و
|
||||
ردیف اشغال هم برایشان ساخته میشود (تور ایمنی دوگانه).
|
||||
|
||||
## خروجی
|
||||
|
||||
- `src/Appointment/Booking/`
|
||||
- توسعهٔ `Appointment` entity با `segments` و رابطهٔ اشغال
|
||||
- `docs/api/appointment-booking.md` + بهروزرسانی `docs/api/appointment.md`
|
||||
- تست همزمانی واقعی
|
||||
Reference in New Issue
Block a user