Files
clinicpro/docs/new_feture/taskes/task-07-hold-and-book/database.md
T
hamed 021d0eb6b2 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.
2026-07-30 11:43:58 +03:30

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هرگز مستقیم کوئری نشود