- 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.
5.8 KiB
دیتابیس — تسک ۱۳
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 |