Files
clinicpro/docs/new_feture/taskes/task-13-cancellation-waitlist/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

5.8 KiB
Raw Blame History

دیتابیس — تسک ۱۳

cancellation_policies

ستون نوع توضیح
id INT PK AI
uuid VARCHAR(36) UNIQUE
entity_type / entity_id VARCHAR(10) / INT NOT NULL
service_item_id INT NULL NULL = پیش‌فرض محیط — FK ON DELETE CASCADE
free_window_hours SMALLINT NOT NULL DEFAULT 24
penalty_mode VARCHAR(10) NOT NULL DEFAULT 'none' none|percent|fixed
penalty_value INT NOT NULL DEFAULT 0 درصد ۰..۱۰۰ یا ریال
deposit_refundable TINYINT(1) NOT NULL DEFAULT 0 پس از پنجرهٔ رایگان
credit_refundable TINYINT(1) NOT NULL DEFAULT 1 اعتبار پکیج
no_show_threshold SMALLINT NOT NULL DEFAULT 3
risk_tag_uuid VARCHAR(36) NULL ارجاع به tenant_tags.uuid — بدون FK، الگوی موجود پروژه
active TINYINT(1) NOT NULL DEFAULT 1
created_at/updated_at INT NOT NULL
UNIQUE KEY uniq_cancel_policy_scope (entity_type, entity_id, service_item_id)
KEY idx_cancel_policies_tenant (entity_type, entity_id, active)

risk_tag_uuid بدون FK — همان الگوی DiscountRule.target_tag_uuid موجود.

no_show_records

CREATE TABLE no_show_records (
  id INT PRIMARY KEY AUTO_INCREMENT,
  uuid VARCHAR(36) NOT NULL UNIQUE,
  entity_type VARCHAR(10) NOT NULL,
  entity_id INT NOT NULL,
  patient_record_id INT NOT NULL,
  appointment_id INT NOT NULL,
  recorded_at INT NOT NULL,
  recorded_by INT NULL,
  UNIQUE KEY uniq_no_show_appointment (appointment_id),      -- یک بار per نوبت
  KEY idx_no_show_patient (patient_record_id, recorded_at),  -- کوئری شمارش ۱۲ ماه
  KEY idx_no_show_tenant  (entity_type, entity_id, recorded_at),
  CONSTRAINT fk_ns_patient FOREIGN KEY (patient_record_id) REFERENCES patient_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_ns_appt    FOREIGN KEY (appointment_id)    REFERENCES appointments(id)    ON DELETE CASCADE
);

جدول جدا و نه یک ستون شمارنده روی بیمار — همان استدلال دفتر اعتبار تسک ۱۱: شمارنده، «چه زمانی و کدام نوبت» را از دست می‌دهد و پنجرهٔ ۱۲ ماهه غیرقابل محاسبه می‌شود.

uniq_no_show_appointment: تغییر وضعیت به no_show ممکن است دوبار اتفاق بیفتد (idempotency)؛ رکورد دوم ثبت نشود.

waitlist_entries

ستون نوع توضیح
id INT PK AI
uuid VARCHAR(36) UNIQUE
entity_type / entity_id VARCHAR(10) / INT NOT NULL
patient_record_id INT NOT NULL FK ON DELETE CASCADE
service_item_id INT NOT NULL FK ON DELETE CASCADE
branch_id INT NULL FK ON DELETE CASCADE
desired_from INT NOT NULL
desired_to INT NOT NULL
preferred_day_parts JSON NULL ["morning","evening"]
priority SMALLINT NOT NULL DEFAULT 0
status VARCHAR(12) NOT NULL DEFAULT 'waiting' waiting|notified|converted|expired
notified_at INT NULL آخرین اطلاع
notify_count SMALLINT NOT NULL DEFAULT 0 سقف برای جلوگیری از اسپم
converted_appointment_id INT NULL FK ON DELETE SET NULL
created_at/updated_at INT NOT NULL
KEY idx_waitlist_match  (service_item_id, branch_id, status, desired_from, desired_to)
KEY idx_waitlist_tenant (entity_type, entity_id, status, created_at)
KEY idx_waitlist_patient (patient_record_id, status)

idx_waitlist_match کوئری داغ است: «چه کسانی منتظر این سرویس در این بازه‌اند؟»

SELECT * FROM waitlist_entries
WHERE service_item_id = ? AND (branch_id = ? OR branch_id IS NULL)
  AND status = 'waiting'
  AND desired_from <= :freedEnd AND desired_to >= :freedStart
ORDER BY priority DESC, created_at ASC
LIMIT 10

preferred_day_parts در PHP فیلتر می‌شود (JSON قابل ایندکس مطمئن نیست و نتیجه ≤ ۱۰ ردیف است).

هیچ تغییری در appointments

وضعیت‌های cancelled_by_user, cancelled_by_doctor, no_show از قبل هستند. deposit_amount_rials هم.

جریمه در دفتر مالی موجود

جدول جدید ندارد. WalletTransaction موجود استفاده می‌شود:

new WalletTransaction(
    user: $appt->getUser(),
    amountRials: -$penalty,
    kind: 'cancellation_penalty',      // ← مقدار جدید در enum موجود
    reference: $appt->getUuid(),
);
$tx->setRecordedEntity($appt->getEntityType(), $appt->getEntityId());   // per-محیط، طبق tenancy.md

setRecordedEntity اجباری است، وگرنه جریمه در دفتر همهٔ محیط‌ها دیده می‌شود (docs/architecture/tenancy.md، بخش کیف پول).

Migration

ddev exec php bin/console doctrine:migrations:diff --no-interaction
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php bin/console app:cancellation:seed-default-policy --force

seed-default-policy برای هر محیط یک سیاست پیش‌فرض محافظه‌کار می‌سازد: free_window_hours=24, penalty_mode=none, deposit_refundable=true, credit_refundable=true.

پیش‌فرض بدون جریمه عمدی است: فعال شدن ناگهانی جریمه روی بیماران موجود، شکایت است. کلینیک خودش باید فعالش کند.

پاکسازی

ddev exec php bin/console app:waitlist:expire   # روزانه

ورودی‌هایی که desired_to گذشته → status='expired'.

طبقه‌بندی tenant

جدول وضعیت
cancellation_policies, no_show_records, waitlist_entries جفت tenant