- 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.3 KiB
دیتابیس — تسک ۱۲
course_protocols
| ستون | نوع | توضیح |
|---|---|---|
id |
INT PK AI | |
uuid |
VARCHAR(36) UNIQUE | |
entity_type / entity_id |
VARCHAR(10) / INT NOT NULL | |
service_item_id |
INT NOT NULL | FK ON DELETE CASCADE |
session_count |
SMALLINT NOT NULL | |
min_days |
SMALLINT NOT NULL | |
ideal_days |
SMALLINT NOT NULL | |
max_days |
SMALLINT NOT NULL | |
prefer_same_resource |
TINYINT(1) NOT NULL DEFAULT 1 | |
active |
TINYINT(1) NOT NULL DEFAULT 1 | |
created_at/updated_at |
INT NOT NULL |
UNIQUE KEY uniq_protocol_service (service_item_id) -- یک پروتکل فعال per سرویس
KEY idx_protocols_tenant (entity_type, entity_id, active)
قید اپلیکیشنی: min_days <= ideal_days <= max_days و session_count >= 2
(دورهٔ یکجلسهای همان نوبت تکی است).
course_protocol_steps
CREATE TABLE course_protocol_steps (
id INT PRIMARY KEY AUTO_INCREMENT,
protocol_id INT NOT NULL,
session_number SMALLINT NOT NULL,
params JSON NULL, -- {"energy": 12} — اسکالر
override_duration_minutes SMALLINT NULL,
UNIQUE KEY uniq_step (protocol_id, session_number),
CONSTRAINT fk_step_protocol FOREIGN KEY (protocol_id) REFERENCES course_protocols(id) ON DELETE CASCADE
);
فرزند aggregate با ریشهٔ CourseProtocol.
treatment_courses
| ستون | نوع | توضیح |
|---|---|---|
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 RESTRICT |
service_item_id |
INT NOT NULL | FK ON DELETE RESTRICT |
protocol_id |
INT NOT NULL | FK ON DELETE RESTRICT |
session_count |
SMALLINT NOT NULL | snapshot |
min_days |
SMALLINT NOT NULL | snapshot |
ideal_days |
SMALLINT NOT NULL | snapshot |
max_days |
SMALLINT NOT NULL | snapshot |
patient_package_id |
INT NULL | FK → patient_packages.id ON DELETE SET NULL |
preferred_resource_id |
INT NULL | FK → clinic_resources.id ON DELETE SET NULL |
status |
VARCHAR(12) NOT NULL DEFAULT 'active' | active|completed|abandoned |
abandon_reason |
VARCHAR(255) NULL | |
started_at |
INT NOT NULL | |
completed_at |
INT NULL | |
created_at/updated_at |
INT NOT NULL |
KEY idx_courses_tenant (entity_type, entity_id, status, started_at)
KEY idx_courses_patient (patient_record_id, status)
UNIQUE KEY uniq_active_course (patient_record_id, service_item_id, status)
⚠️ uniq_active_course با MariaDB روی مقدار status کار نمیکند به شکلی که فقط
active را یکتا کند (چند ردیف completed مجازند). راه درست: قید اپلیکیشنی در
CourseStarter + کلید یکتای جزئی که MariaDB ندارد.
جایگزین: یک ستون active_course_key VARCHAR(64) NULL UNIQUE با همان الگوی
Appointment.active_slot_key:
$this->activeCourseKey = $this->status === self::STATUS_ACTIVE
? sprintf('%d:%d', $this->patient->getId(), $this->service->getId())
: null;
الگوی اثباتشدهٔ همین کدبیس — استفادهاش کن، دوباره اختراع نکن.
course_sessions
| ستون | نوع | توضیح |
|---|---|---|
id |
INT PK AI | |
uuid |
VARCHAR(36) UNIQUE | |
entity_type / entity_id |
VARCHAR(10) / INT NOT NULL | |
course_id |
INT NOT NULL | FK ON DELETE CASCADE |
session_number |
SMALLINT NOT NULL | |
params |
JSON NULL | snapshot از course_protocol_steps |
appointment_id |
INT NULL UNIQUE | FK ON DELETE SET NULL |
status |
VARCHAR(12) NOT NULL DEFAULT 'planned' | planned|booked|completed|skipped |
completed_at |
INT NULL | |
created_at/updated_at |
INT NOT NULL |
UNIQUE KEY uniq_course_session (course_id, session_number)
UNIQUE KEY uniq_session_appointment (appointment_id)
KEY idx_sessions_tenant (entity_type, entity_id, status)
KEY idx_sessions_course (course_id, session_number)
uniq_session_appointment: یک نوبت به بیش از یک جلسهٔ دوره وصل نمیشود.
تغییر appointments
ALTER TABLE appointments
ADD COLUMN course_session_id INT NULL,
ADD CONSTRAINT fk_appointments_course_session
FOREIGN KEY (course_session_id) REFERENCES course_sessions(id) ON DELETE SET NULL,
ADD KEY idx_appointments_course_session (course_session_id);
دو طرفه است (course_sessions.appointment_id هم وجود دارد) — عمدی: لیست نوبتهای پنل
باید بدون JOIN بفهمد نوبت جزو دوره است، و صفحهٔ دوره باید بدون JOIN نوبت را پیدا کند.
هر دو در CourseSessionLinker همزمان ست میشوند؛ هیچ جای دیگری ننویسد.
Migration
ddev exec php bin/console doctrine:migrations:diff --no-interaction
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
بدون backfill.
طبقهبندی tenant
| جدول | وضعیت |
|---|---|
course_protocols, treatment_courses, course_sessions |
جفت tenant |
course_protocol_steps |
AGGREGATE_CHILDREN → ریشه CourseProtocol |