задачи 2 и 8

This commit is contained in:
Zuev
2026-08-11 18:18:08 +03:00
parent 519864b962
commit 18a97b293a
59 changed files with 6237 additions and 276 deletions

View File

@@ -2182,3 +2182,323 @@ ALTER TABLE teacher_department_assignments
COMMENT ON CONSTRAINT ex_teacher_primary_department_no_overlap
ON teacher_department_assignments
IS 'У преподавателя не могут пересекаться периоды двух основных кафедр';
-- ==========================================
-- Отсутствия преподавателей и мастер замены
-- ==========================================
CREATE TABLE teacher_absences (
id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
reason TEXT NOT NULL,
status VARCHAR(20) NOT NULL,
requested_by BIGINT REFERENCES users(id),
reviewed_by BIGINT REFERENCES users(id),
review_comment TEXT,
reviewed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_teacher_absences_period CHECK (end_date >= start_date),
CONSTRAINT chk_teacher_absences_reason CHECK (length(trim(reason)) BETWEEN 1 AND 2000),
CONSTRAINT chk_teacher_absences_status CHECK (
status IN ('PENDING', 'APPROVED', 'REJECTED', 'RESOLVED', 'CANCELLED')
)
);
CREATE INDEX idx_teacher_absences_teacher_period
ON teacher_absences(teacher_id, start_date, end_date);
CREATE INDEX idx_teacher_absences_status
ON teacher_absences(status);
CREATE TABLE teacher_absence_decisions (
id BIGSERIAL PRIMARY KEY,
absence_id BIGINT NOT NULL REFERENCES teacher_absences(id) ON DELETE CASCADE,
base_rule_slot_id BIGINT NOT NULL REFERENCES schedule_rule_slots(id),
lesson_date DATE NOT NULL,
resolution VARCHAR(30) NOT NULL,
decision_status VARCHAR(20) NOT NULL,
schedule_override_id BIGINT REFERENCES schedule_overrides(id) ON DELETE SET NULL,
comment TEXT,
decided_by BIGINT REFERENCES users(id),
decided_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_teacher_absence_decision_lesson UNIQUE (
absence_id,
base_rule_slot_id,
lesson_date
),
CONSTRAINT chk_teacher_absence_decision_resolution CHECK (
resolution IN ('REPLACE_TEACHER', 'MOVE_TIME', 'CHANGE_CLASSROOM', 'CANCEL', 'REJECT')
),
CONSTRAINT chk_teacher_absence_decision_status CHECK (
decision_status IN ('APPLIED', 'REJECTED')
)
);
CREATE INDEX idx_teacher_absence_decisions_absence
ON teacher_absence_decisions(absence_id, lesson_date);
COMMENT ON TABLE teacher_absences IS 'Временные отсутствия преподавателей с согласованием кафедрой';
COMMENT ON COLUMN teacher_absences.status IS 'PENDING, APPROVED, REJECTED, RESOLVED или CANCELLED';
COMMENT ON TABLE teacher_absence_decisions IS 'Журнал применённых и отклонённых решений мастера замены';
COMMENT ON COLUMN teacher_absence_decisions.resolution IS 'Выбранный способ разрешения затронутого занятия';
-- ==========================================
-- Пожелания и заявки преподавателей
-- ==========================================
CREATE TABLE teacher_preferences (
id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id),
semester_id BIGINT NOT NULL REFERENCES semesters(id),
preference_type VARCHAR(30) NOT NULL,
day_of_week SMALLINT,
time_slot_id BIGINT REFERENCES time_slots(id),
preference_date DATE,
comment TEXT,
status VARCHAR(20) NOT NULL,
requested_by BIGINT REFERENCES users(id),
reviewed_by BIGINT REFERENCES users(id),
review_comment TEXT,
reviewed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_teacher_preferences_type CHECK (
preference_type IN (
'HARD_UNAVAILABLE',
'SOFT_PREFERRED',
'SOFT_UNWANTED',
'CONSECUTIVE',
'NO_GAPS'
)
),
CONSTRAINT chk_teacher_preferences_status CHECK (
status IN ('PENDING', 'APPROVED', 'REJECTED', 'CANCELLED')
),
CONSTRAINT chk_teacher_preferences_day CHECK (
day_of_week IS NULL OR day_of_week BETWEEN 1 AND 7
),
CONSTRAINT chk_teacher_preferences_payload CHECK (
(
preference_type = 'HARD_UNAVAILABLE'
AND (
(preference_date IS NOT NULL AND day_of_week IS NULL AND time_slot_id IS NULL)
OR
(preference_date IS NULL AND day_of_week IS NOT NULL AND time_slot_id IS NOT NULL)
)
)
OR
(
preference_type IN ('SOFT_PREFERRED', 'SOFT_UNWANTED')
AND preference_date IS NULL
AND day_of_week IS NOT NULL
AND time_slot_id IS NOT NULL
)
OR
(
preference_type IN ('CONSECUTIVE', 'NO_GAPS')
AND preference_date IS NULL
AND day_of_week IS NULL
AND time_slot_id IS NULL
)
),
CONSTRAINT chk_teacher_preferences_comment CHECK (
comment IS NULL OR length(trim(comment)) BETWEEN 1 AND 2000
)
);
CREATE INDEX idx_teacher_preferences_teacher_semester
ON teacher_preferences(teacher_id, semester_id, status);
CREATE INDEX idx_teacher_preferences_slot
ON teacher_preferences(semester_id, day_of_week, time_slot_id)
WHERE status = 'APPROVED';
CREATE INDEX idx_teacher_preferences_date
ON teacher_preferences(teacher_id, preference_date)
WHERE status = 'APPROVED' AND preference_date IS NOT NULL;
CREATE TABLE teacher_change_requests (
id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id),
base_rule_slot_id BIGINT NOT NULL REFERENCES schedule_rule_slots(id),
lesson_date DATE NOT NULL,
request_type VARCHAR(30) NOT NULL,
target_lesson_date DATE,
requested_time_slot_id BIGINT REFERENCES time_slots(id),
requested_classroom_id BIGINT REFERENCES classrooms(id),
reason TEXT NOT NULL,
status VARCHAR(20) NOT NULL,
reviewed_by BIGINT REFERENCES users(id),
review_comment TEXT,
reviewed_at TIMESTAMPTZ,
applied_override_id BIGINT REFERENCES schedule_overrides(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_teacher_change_requests_type CHECK (
request_type IN ('MOVE', 'CHANGE_CLASSROOM', 'CANCEL')
),
CONSTRAINT chk_teacher_change_requests_status CHECK (
status IN ('PENDING', 'APPROVED', 'REJECTED', 'CANCELLED')
),
CONSTRAINT chk_teacher_change_requests_reason CHECK (
length(trim(reason)) BETWEEN 1 AND 2000
),
CONSTRAINT chk_teacher_change_requests_payload CHECK (
(
request_type = 'MOVE'
AND requested_time_slot_id IS NOT NULL
AND requested_classroom_id IS NULL
)
OR
(
request_type = 'CHANGE_CLASSROOM'
AND target_lesson_date IS NULL
AND requested_time_slot_id IS NULL
AND requested_classroom_id IS NOT NULL
)
OR
(
request_type = 'CANCEL'
AND target_lesson_date IS NULL
AND requested_time_slot_id IS NULL
AND requested_classroom_id IS NULL
)
)
);
CREATE INDEX idx_teacher_change_requests_teacher
ON teacher_change_requests(teacher_id, created_at DESC);
CREATE INDEX idx_teacher_change_requests_status
ON teacher_change_requests(status, created_at);
CREATE UNIQUE INDEX uq_teacher_change_requests_pending_lesson
ON teacher_change_requests(base_rule_slot_id, lesson_date)
WHERE status = 'PENDING';
CREATE TABLE teacher_change_request_history (
id BIGSERIAL PRIMARY KEY,
request_id BIGINT NOT NULL REFERENCES teacher_change_requests(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL,
comment TEXT,
actor_id BIGINT REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_teacher_change_request_history_status CHECK (
status IN ('PENDING', 'APPROVED', 'REJECTED', 'CANCELLED')
)
);
CREATE INDEX idx_teacher_change_request_history_request
ON teacher_change_request_history(request_id, created_at, id);
COMMENT ON TABLE teacher_preferences IS 'Согласуемые ограничения и пожелания преподавателей на семестр';
COMMENT ON COLUMN teacher_preferences.preference_type IS 'Строгая недоступность, мягкое пожелание или пожелание к компактности расписания';
COMMENT ON TABLE teacher_change_requests IS 'Заявки преподавателей на перенос, смену аудитории или отмену опубликованного занятия';
COMMENT ON TABLE teacher_change_request_history IS 'Неизменяемая история статусов и комментариев по заявке преподавателя';
-- ==========================================
-- Черновики, версии, публикация и аудит расписания
-- ==========================================
CREATE TABLE schedule_versions (
id BIGSERIAL PRIMARY KEY,
semester_id BIGINT NOT NULL REFERENCES semesters(id) ON DELETE CASCADE,
version_number INT NOT NULL,
name VARCHAR(160) NOT NULL,
status VARCHAR(20) NOT NULL,
based_on_version_id BIGINT REFERENCES schedule_versions(id) ON DELETE SET NULL,
restored_from_version_id BIGINT REFERENCES schedule_versions(id) ON DELETE SET NULL,
change_reason TEXT,
created_by BIGINT REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
published_by BIGINT REFERENCES users(id),
published_at TIMESTAMPTZ,
archived_at TIMESTAMPTZ,
CONSTRAINT uq_schedule_versions_number UNIQUE (semester_id, version_number),
CONSTRAINT chk_schedule_versions_number CHECK (version_number > 0),
CONSTRAINT chk_schedule_versions_name CHECK (length(trim(name)) BETWEEN 1 AND 160),
CONSTRAINT chk_schedule_versions_status CHECK (status IN ('DRAFT', 'PUBLISHED', 'ARCHIVED')),
CONSTRAINT chk_schedule_versions_publish_fields CHECK (
status <> 'PUBLISHED' OR published_at IS NOT NULL
)
);
CREATE UNIQUE INDEX uq_schedule_versions_published_semester
ON schedule_versions(semester_id)
WHERE status = 'PUBLISHED';
CREATE INDEX idx_schedule_versions_semester_history
ON schedule_versions(semester_id, version_number DESC);
INSERT INTO schedule_versions (
semester_id,
version_number,
name,
status,
created_at,
published_at
)
SELECT
semester.id,
1,
'Опубликованная версия 1',
'PUBLISHED',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM semesters semester;
ALTER TABLE schedule_rules
ADD COLUMN schedule_version_id BIGINT;
UPDATE schedule_rules rule
SET schedule_version_id = version.id,
version_group_id = COALESCE(rule.version_group_id, rule.id)
FROM schedule_versions version
WHERE version.semester_id = rule.semester_id
AND version.status = 'PUBLISHED';
ALTER TABLE schedule_rules
ALTER COLUMN schedule_version_id SET NOT NULL,
ADD CONSTRAINT fk_schedule_rules_version
FOREIGN KEY (schedule_version_id) REFERENCES schedule_versions(id) ON DELETE CASCADE;
CREATE INDEX idx_schedule_rules_version
ON schedule_rules(schedule_version_id, status);
CREATE UNIQUE INDEX uq_schedule_rules_version_group
ON schedule_rules(schedule_version_id, version_group_id)
WHERE version_group_id IS NOT NULL;
CREATE TABLE schedule_version_history (
id BIGSERIAL PRIMARY KEY,
version_id BIGINT NOT NULL REFERENCES schedule_versions(id) ON DELETE CASCADE,
action VARCHAR(30) NOT NULL,
actor_id BIGINT REFERENCES users(id),
reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_schedule_version_history_action CHECK (
action IN ('CREATED', 'PUBLISHED', 'ARCHIVED', 'RESTORED')
),
CONSTRAINT chk_schedule_version_history_reason CHECK (
reason IS NULL OR length(trim(reason)) BETWEEN 1 AND 2000
)
);
CREATE INDEX idx_schedule_version_history_version
ON schedule_version_history(version_id, created_at DESC, id DESC);
INSERT INTO schedule_version_history (version_id, action, reason, created_at)
SELECT id, 'CREATED', 'Начальная версия расписания', created_at
FROM schedule_versions;
INSERT INTO schedule_version_history (version_id, action, reason, created_at)
SELECT id, 'PUBLISHED', 'Начальная публикация расписания', published_at
FROM schedule_versions;
COMMENT ON TABLE schedule_versions IS 'Изолированные черновики и опубликованные версии правил расписания';
COMMENT ON COLUMN schedule_versions.status IS 'DRAFT, PUBLISHED или ARCHIVED';
COMMENT ON COLUMN schedule_versions.based_on_version_id IS 'Версия, из которой был скопирован черновик';
COMMENT ON COLUMN schedule_versions.restored_from_version_id IS 'Предыдущая опубликованная версия при восстановлении';
COMMENT ON COLUMN schedule_rules.schedule_version_id IS 'Версия расписания, которой принадлежит правило';
COMMENT ON COLUMN schedule_rules.version_group_id IS 'Стабильный идентификатор правила между версиями';
COMMENT ON TABLE schedule_version_history IS 'Неизменяемый журнал создания, публикации, архивации и восстановления версий';