ролевая моделю доступа + загруженность по кафедрам и преподавателям + просморт расписания

This commit is contained in:
Zuev
2026-05-19 23:10:56 +03:00
parent f010ffc467
commit 91729cff7d
93 changed files with 4264 additions and 699 deletions

View File

@@ -9,7 +9,13 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS departments (
id BIGSERIAL UNIQUE PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL,
code BIGINT UNIQUE NOT NULL
code BIGINT UNIQUE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO departments (name, code) VALUES
@@ -20,7 +26,13 @@ INSERT INTO departments (name, code) VALUES
CREATE TABLE IF NOT EXISTS specialties (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
specialty_code VARCHAR(255) UNIQUE NOT NULL
specialty_code VARCHAR(255) UNIQUE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO specialties (name, specialty_code) VALUES
@@ -35,6 +47,12 @@ CREATE TABLE IF NOT EXISTS specialty_profiles (
name VARCHAR(500) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT,
CONSTRAINT uq_specialty_profiles_name UNIQUE (specialty_id, name)
);
@@ -50,20 +68,62 @@ CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'STUDENT',
role VARCHAR(30) NOT NULL DEFAULT 'STUDENT',
full_name VARCHAR(255) NOT NULL,
job_title VARCHAR(255) NOT NULL,
department_id BIGINT NOT NULL REFERENCES departments(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
-- Админ по умолчанию: admin / admin (bcrypt через pgcrypto)
INSERT INTO users (username, password, role, full_name, job_title, department_id)
VALUES ('admin', crypt('admin', gen_salt('bf', 10)), 'ADMIN', 'Иванов Админ Иванович', 'Доцент', 1),
('Тестовый преподаватель', crypt('1234567890', gen_salt('bf', 10)), 'TEACHER', 'Петров Препод Петрович', 'Профессор', 2)
('Тестовый преподаватель', crypt('1234567890', gen_salt('bf', 10)), 'TEACHER', 'Петров Препод Петрович', 'Профессор', 2),
('учебный_отдел', crypt('1234567890', gen_salt('bf', 10)), 'EDUCATION_OFFICE', 'Оператор учебного отдела', 'Учебный отдел', 1),
('кафедраб', crypt('1234567890', gen_salt('bf', 10)), 'DEPARTMENT', 'Оператор кафедры ИБ', 'Кафедра', 1),
('просмотр_расписаний', crypt('1234567890', gen_salt('bf', 10)), 'SCHEDULE_VIEWER', 'Оператор просмотра расписаний', 'Просмотр расписаний', 1)
ON CONFLICT (username) DO NOTHING;
CREATE TABLE IF NOT EXISTS teacher_department_assignments (
id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id),
department_id BIGINT NOT NULL REFERENCES departments(id),
valid_from DATE NOT NULL,
valid_to DATE,
is_primary BOOLEAN NOT NULL DEFAULT TRUE,
comment TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT REFERENCES users(id),
CONSTRAINT chk_teacher_department_dates CHECK (valid_to IS NULL OR valid_to >= valid_from)
);
CREATE INDEX IF NOT EXISTS idx_teacher_department_teacher_dates
ON teacher_department_assignments(teacher_id, valid_from, valid_to);
CREATE INDEX IF NOT EXISTS idx_teacher_department_department_dates
ON teacher_department_assignments(department_id, valid_from, valid_to);
CREATE UNIQUE INDEX IF NOT EXISTS uq_teacher_department_open_primary
ON teacher_department_assignments(teacher_id)
WHERE is_primary = TRUE AND valid_to IS NULL;
INSERT INTO teacher_department_assignments (teacher_id, department_id, valid_from, is_primary, comment)
SELECT u.id, u.department_id, COALESCE(u.created_at::DATE, CURRENT_DATE), TRUE, 'Начальная кафедра из users.department_id'
FROM users u
WHERE u.role = 'TEACHER'
AND NOT EXISTS (
SELECT 1
FROM teacher_department_assignments existing
WHERE existing.teacher_id = u.id
);
-- ==========================================
-- Образовательные формы
-- ==========================================
@@ -71,7 +131,13 @@ CREATE TABLE IF NOT EXISTS education_forms (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO education_forms (name) VALUES
@@ -92,7 +158,13 @@ CREATE TABLE IF NOT EXISTS student_groups (
specialty_id BIGINT NOT NULL REFERENCES specialties(id),
specialty_profile_id BIGINT NOT NULL REFERENCES specialty_profiles(id),
year_start_study BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
-- Тестовая базовая группа для работы
@@ -116,6 +188,12 @@ CREATE TABLE IF NOT EXISTS subgroups (
group_id BIGINT NOT NULL REFERENCES student_groups(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
student_capacity INT,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT,
UNIQUE(group_id, name)
);
@@ -146,7 +224,13 @@ CREATE TABLE IF NOT EXISTS subjects (
code VARCHAR(20),
department_id BIGINT NOT NULL REFERENCES departments(id),
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO subjects (name, department_id) VALUES
@@ -157,6 +241,17 @@ INSERT INTO subjects (name, department_id) VALUES
('Английский язык', 1)
ON CONFLICT (name) DO NOTHING;
CREATE TABLE IF NOT EXISTS subject_comments (
id BIGSERIAL PRIMARY KEY,
subject_id BIGINT NOT NULL REFERENCES subjects(id),
author_id BIGINT REFERENCES users(id),
comment TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_subject_comments_subject
ON subject_comments(subject_id, created_at DESC);
-- Типы занятий
CREATE TABLE IF NOT EXISTS lesson_types (
id BIGSERIAL PRIMARY KEY,
@@ -176,7 +271,13 @@ CREATE TABLE IF NOT EXISTS equipments (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
inventory_number VARCHAR(50)
inventory_number VARCHAR(50),
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO equipments (name) VALUES
@@ -197,7 +298,13 @@ CREATE TABLE IF NOT EXISTS classrooms (
floor INT,
is_available BOOLEAN DEFAULT TRUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
active_from DATE,
active_to DATE,
archived_at TIMESTAMP,
archived_by BIGINT,
archive_reason TEXT
);
INSERT INTO classrooms (name, capacity, building, floor) VALUES
@@ -229,6 +336,17 @@ WHERE
OR (c.name = '303 Обычная' AND e.name IN ('Проектор'))
ON CONFLICT (classroom_id, equipment_id) DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_departments_status ON departments(status);
CREATE INDEX IF NOT EXISTS idx_specialties_status ON specialties(status);
CREATE INDEX IF NOT EXISTS idx_specialty_profiles_status ON specialty_profiles(status);
CREATE INDEX IF NOT EXISTS idx_users_status ON users(status);
CREATE INDEX IF NOT EXISTS idx_education_forms_status ON education_forms(status);
CREATE INDEX IF NOT EXISTS idx_student_groups_status ON student_groups(status);
CREATE INDEX IF NOT EXISTS idx_subgroups_status ON subgroups(status);
CREATE INDEX IF NOT EXISTS idx_subjects_status ON subjects(status);
CREATE INDEX IF NOT EXISTS idx_equipments_status ON equipments(status);
CREATE INDEX IF NOT EXISTS idx_classrooms_status ON classrooms(status);
-- ==========================================
-- Связи для преподавателей
-- ==========================================
@@ -483,6 +601,13 @@ CREATE TABLE IF NOT EXISTS schedule_rules (
lecture_start_week INT NOT NULL,
laboratory_start_week INT NOT NULL,
practice_start_week INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
valid_from DATE,
valid_to DATE,
version_group_id BIGINT,
change_reason TEXT,
created_by BIGINT REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_schedule_rules_type_hours_non_negative CHECK (
lecture_academic_hours >= 0
AND laboratory_academic_hours >= 0
@@ -495,11 +620,14 @@ CREATE TABLE IF NOT EXISTS schedule_rules (
lecture_start_week > 0
AND laboratory_start_week > 0
AND practice_start_week > 0
)
),
CONSTRAINT chk_schedule_rules_dates CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from)
);
CREATE INDEX IF NOT EXISTS idx_schedule_rules_semester ON schedule_rules(semester_id);
CREATE INDEX IF NOT EXISTS idx_schedule_rules_subject ON schedule_rules(subject_id);
CREATE INDEX IF NOT EXISTS idx_schedule_rules_status_dates
ON schedule_rules(status, valid_from, valid_to);
CREATE TABLE IF NOT EXISTS schedule_rule_groups (
schedule_rule_id BIGINT NOT NULL REFERENCES schedule_rules(id) ON DELETE CASCADE,
@@ -519,6 +647,12 @@ CREATE TABLE IF NOT EXISTS schedule_rule_slots (
classroom_id BIGINT NOT NULL REFERENCES classrooms(id),
lesson_type_id BIGINT NOT NULL REFERENCES lesson_types(id),
lesson_format VARCHAR(30) NOT NULL,
time_locked BOOLEAN NOT NULL DEFAULT FALSE,
classroom_locked BOOLEAN NOT NULL DEFAULT FALSE,
teacher_locked BOOLEAN NOT NULL DEFAULT FALSE,
locked_by BIGINT REFERENCES users(id),
locked_at TIMESTAMP,
lock_comment TEXT,
CONSTRAINT chk_schedule_rule_slots_day CHECK (day_of_week BETWEEN 1 AND 7),
CONSTRAINT chk_schedule_rule_slots_parity CHECK (parity IN ('BOTH', 'EVEN', 'ODD')),
CONSTRAINT chk_schedule_rule_slots_format CHECK (lesson_format IN ('Очно', 'Онлайн'))
@@ -537,6 +671,28 @@ CREATE TABLE IF NOT EXISTS schedule_rule_slot_subgroups (
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slot_subgroups_subgroup ON schedule_rule_slot_subgroups(subgroup_id);
CREATE TABLE IF NOT EXISTS schedule_overrides (
id BIGSERIAL PRIMARY KEY,
base_rule_slot_id BIGINT NOT NULL REFERENCES schedule_rule_slots(id),
lesson_date DATE NOT NULL,
action VARCHAR(20) NOT NULL,
new_time_slot_id BIGINT REFERENCES time_slots(id),
new_classroom_id BIGINT REFERENCES classrooms(id),
new_teacher_id BIGINT REFERENCES users(id),
new_lesson_format VARCHAR(30),
comment TEXT,
created_by BIGINT REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_schedule_overrides_action CHECK (action IN ('MOVE', 'CANCEL', 'REPLACE')),
CONSTRAINT uq_schedule_overrides_slot_date UNIQUE (base_rule_slot_id, lesson_date)
);
CREATE INDEX IF NOT EXISTS idx_schedule_overrides_date
ON schedule_overrides(lesson_date);
CREATE INDEX IF NOT EXISTS idx_schedule_overrides_new_teacher
ON schedule_overrides(new_teacher_id);
CREATE OR REPLACE FUNCTION validate_schedule_rule_slot_subgroups()
RETURNS TRIGGER AS $$
DECLARE
@@ -762,12 +918,17 @@ COMMENT ON TABLE schedule_rules IS 'Правила динамической ге
COMMENT ON TABLE schedule_rule_groups IS 'Привязка правил расписания к учебным группам';
COMMENT ON TABLE schedule_rule_slots IS 'Слоты проведения занятий внутри правила расписания';
COMMENT ON TABLE schedule_rule_slot_subgroups IS 'Подгруппы лабораторных слотов правила расписания';
COMMENT ON TABLE teacher_department_assignments IS 'История принадлежности преподавателей к кафедрам';
COMMENT ON TABLE subject_comments IS 'Комментарии кафедр и администраторов к дисциплинам';
COMMENT ON TABLE schedule_overrides IS 'Точечные изменения конкретных сгенерированных пар';
COMMENT ON COLUMN schedule_rules.lecture_academic_hours IS 'Лимит академических часов лекций';
COMMENT ON COLUMN schedule_rules.laboratory_academic_hours IS 'Лимит академических часов лабораторных работ';
COMMENT ON COLUMN schedule_rules.practice_academic_hours IS 'Лимит академических часов практик';
COMMENT ON COLUMN schedule_rules.lecture_start_week IS 'Неделя начала лекций в семестре';
COMMENT ON COLUMN schedule_rules.laboratory_start_week IS 'Неделя начала лабораторных работ в семестре';
COMMENT ON COLUMN schedule_rules.practice_start_week IS 'Неделя начала практик в семестре';
COMMENT ON COLUMN schedule_rules.valid_from IS 'Дата начала действия версии правила расписания';
COMMENT ON COLUMN schedule_rules.valid_to IS 'Дата окончания действия версии правила расписания';
COMMENT ON TABLE education_forms IS 'Формы обучения';
COMMENT ON TABLE subgroups IS 'Подгруппы';
@@ -782,6 +943,7 @@ COMMENT ON COLUMN users.id IS 'ID пользователя';
COMMENT ON COLUMN users.username IS 'Логин пользователя';
COMMENT ON COLUMN users.password IS 'Хэш пароля пользователя';
COMMENT ON COLUMN users.role IS 'Роль пользователя';
COMMENT ON COLUMN users.status IS 'Жизненный цикл пользователя: ACTIVE или ARCHIVED';
COMMENT ON COLUMN users.created_at IS 'Дата и время создания';
COMMENT ON COLUMN users.updated_at IS 'Дата и время последнего обновления';
COMMENT ON COLUMN users.full_name IS 'ФИО пользователя';
@@ -833,6 +995,7 @@ COMMENT ON COLUMN classrooms.floor IS 'Этаж';
COMMENT ON COLUMN classrooms.is_available IS 'Доступность аудитории';
COMMENT ON COLUMN classrooms.description IS 'Описание аудитории';
COMMENT ON COLUMN classrooms.created_at IS 'Дата и время создания';
COMMENT ON COLUMN classrooms.status IS 'Жизненный цикл аудитории: ACTIVE или ARCHIVED';
COMMENT ON COLUMN classroom_equipments.classroom_id IS 'ID аудитории';
COMMENT ON COLUMN classroom_equipments.equipment_id IS 'ID оборудования';