- 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.
6.2 KiB
دیتابیس — تسک ۰۷
resource_occupancy — مهمترین جدول سیستم
| ستون | نوع | توضیح |
|---|---|---|
id |
BIGINT PK AI | BIGINT چون پرحجمترین جدول میشود |
uuid |
VARCHAR(36) UNIQUE | |
entity_type / entity_id |
VARCHAR(10) / INT NOT NULL | |
resource_id |
INT NOT NULL | FK → clinic_resources.id ON DELETE RESTRICT |
appointment_id |
INT NULL | FK → appointments.id ON DELETE CASCADE؛ NULL = مسدودسازی دستی |
appointment_segment_id |
INT NULL | FK ON DELETE CASCADE |
start_at |
INT NOT NULL | شامل setup_minutes منبع |
end_at |
INT NOT NULL | شامل cleanup_minutes منبع |
units |
SMALLINT NOT NULL DEFAULT 1 | |
occupancy_kind |
VARCHAR(10) NOT NULL | exclusive|shared|passive |
status |
VARCHAR(10) NOT NULL | hold|booked|released |
expires_at |
INT NULL | فقط برای hold |
created_at |
INT NOT NULL |
KEY idx_occ_resource_range (resource_id, start_at, end_at, status) -- کوئری داغ تسک ۰۶
KEY idx_occ_tenant_range (entity_type, entity_id, start_at) -- لیستهای پنل
KEY idx_occ_appointment (appointment_id)
KEY idx_occ_expiry (status, expires_at) -- cron انقضا
resource_occupancy_slot — تضمین یکتایی
CREATE TABLE resource_occupancy_slot (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
resource_id INT NOT NULL,
bucket INT NOT NULL, -- floor(timestamp / 300)
unit_index SMALLINT NOT NULL, -- 0 .. capacity-1
occupancy_id BIGINT NOT NULL,
UNIQUE KEY uniq_occ_slot (resource_id, bucket, unit_index), -- ← کل تضمین اینجاست
KEY idx_occ_slot_occupancy (occupancy_id),
CONSTRAINT fk_occ_slot_occupancy FOREIGN KEY (occupancy_id)
REFERENCES resource_occupancy(id) ON DELETE CASCADE
) ENGINE=InnoDB;
BUCKET_SECONDS = 300 در OccupancyWriter::BUCKET_SECONDS ثابت است. تغییرش بعد از
تولید داده، migration کامل میخواهد — در کد کامنت هشدار بگذار.
سطلهای یک بازه:
// [start, end) نیمباز → سطل آخر شامل نمیشود اگر دقیقاً روی مرز باشد
$first = intdiv($start, self::BUCKET_SECONDS);
$last = intdiv($end - 1, self::BUCKET_SECONDS);
بدون -1 نوبت ۱۰:۰۰-۱۰:۳۰ و نوبت ۱۰:۳۰-۱۱:۰۰ سطل مشترک میگیرند و دومی بیدلیل رد میشود.
appointment_segments
| ستون | نوع | توضیح |
|---|---|---|
id |
INT PK AI | |
uuid |
VARCHAR(36) UNIQUE | |
entity_type / entity_id |
VARCHAR(10) / INT NOT NULL | |
appointment_id |
INT NOT NULL | FK ON DELETE CASCADE |
sequence |
SMALLINT NOT NULL | |
name |
VARCHAR(150) NOT NULL | snapshot نام بخش در لحظهٔ ثبت |
segment_type |
VARCHAR(40) NOT NULL | |
start_at |
INT NOT NULL | مطلق |
end_at |
INT NOT NULL | |
patient_present |
TINYINT(1) NOT NULL DEFAULT 1 | |
created_at |
INT NOT NULL |
KEY idx_appt_seg_appointment (appointment_id, sequence)
KEY idx_appt_seg_tenant (entity_type, entity_id, start_at)
name و segment_type عمداً کپی میشوند نه FK: قانون پنجم مستند — «هر چیزی که ثبت شد
باید همانطور بماند». اگر کلینیک فردا الگو را عوض کند، نوبت دیروز نباید تغییر معنا دهد.
تغییر appointments
ALTER TABLE appointments
ADD COLUMN branch_id INT NULL,
ADD COLUMN plan_total_minutes SMALLINT NULL,
ADD COLUMN patient_facing_minutes SMALLINT NULL,
ADD CONSTRAINT fk_appointments_branch FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE SET NULL,
ADD KEY idx_appointments_branch (branch_id, slot_start);
slot_start / slot_end میمانند و در حالت resource برابر شروع اولین بخش و پایان
آخرین بخشاند. دلیل: AppointmentRepository، لیستهای پنل، سایت عمومی و اپ دسکتاپ همه
روی این دو ستون کوئری میزنند. برداشتنشان یعنی بازنویسی همهجا.
وضعیت جدید:
public const STATUS_RESCHEDULED = 'rescheduled';
// ALLOWED_TRANSITIONS: confirmed → rescheduled ; pending → rescheduled
Migration
ddev exec php bin/console doctrine:migrations:diff --no-interaction
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
⚠️ دو ایندکس را دستی در migration بنویس — doctrine:migrations:diff ترتیب ستونهای
ایندکس ترکیبی را گاهی متفاوت تولید میکند و ترتیب اینجا حیاتی است
(resource_id اول در idx_occ_resource_range).
backfill نوبتهای موجود
ddev exec php bin/console app:occupancy:backfill --force
برای هر نوبت pending/confirmed آینده در حالت slot/service:
- یک
appointment_segmentواحد بساز (کل بازه) - یک
resource_occupancyروی منبعtype=doctorهمان پزشک - ردیفهای
resource_occupancy_slotمتناظر
اگر منبع type=doctor وجود ندارد (backfill تسک ۰۲ اجرا نشده)، آن نوبت رد شود و در
خروجی گزارش شود — نه خطا.
بدون این backfill، اولین رزرو در حالت جدید ممکن است روی نوبت قدیمی بنشیند.
نگهداشت
ddev exec php bin/console app:occupancy:prune --older-than=90d --force
ردیفهای resource_occupancy_slot مربوط به بازههای گذشته را حذف میکند.
resource_occupancy میماند (آدیت و گزارش بهرهوری تسک ۱۴).
طبقهبندی tenant
| جدول | وضعیت |
|---|---|
resource_occupancy |
جفت tenant |
appointment_segments |
جفت tenant (uuid ممکن است از request بیاید) |
resource_occupancy_slot |
AGGREGATE_CHILDREN → ریشه ResourceOccupancy — هرگز مستقیم کوئری نشود |