🗄 База данных
Общая информация
- СУБД: PostgreSQL (локально
postgres:alpine3.23, продакшн — managed PostgreSQL)
- Управление схемой: Flyway (программный запуск)
- Hibernate DDL: Отключён (
ddl-auto=none)
- Расширения:
pgcrypto (bcrypt-хеширование паролей)
- Мультитенантность: Каждый тенант = отдельная БД
ER-диаграмма
Описание таблиц
Справочники высшего уровня
departments — Кафедры
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID кафедры |
name |
VARCHAR(255) |
Название кафедры |
code |
BIGINT UNIQUE |
Код кафедры |
specialties — Специальности
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID специальности |
name |
VARCHAR(255) |
Название специальности |
specialty_code |
VARCHAR(255) UNIQUE |
Код ФГОС (напр. 10.03.01) |
specialty_profiles — Профили обучения
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID профиля |
specialty_id |
BIGINT FK → specialties |
Специальность, внутри которой существует профиль |
name |
VARCHAR(255) |
Название профиля, уникально внутри специальности |
description |
TEXT |
Описание профиля |
Пользователи
users — Пользователи системы
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID пользователя |
username |
VARCHAR(50) UNIQUE |
Логин |
password |
VARCHAR(255) |
bcrypt-хеш пароля |
role |
VARCHAR(20) |
ADMIN, TEACHER, STUDENT |
full_name |
VARCHAR(255) |
ФИО |
job_title |
VARCHAR(255) |
Должность |
department_id |
BIGINT FK → departments |
Кафедра |
status |
VARCHAR(20) |
ACTIVE или ARCHIVED; архивный пользователь не может войти |
active_from |
DATE |
Дата начала действия записи |
active_to |
DATE |
Дата окончания действия записи |
archived_at |
TIMESTAMP |
Когда пользователь архивирован |
archive_reason |
TEXT |
Причина архивирования |
created_at |
TIMESTAMP |
Дата создания |
updated_at |
TIMESTAMP |
Дата обновления (авто-триггер) |
Триггер: update_users_updated_at автоматически обновляет updated_at при любом UPDATE.
Учебный процесс
education_forms — Формы обучения
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(100) UNIQUE |
Название (Бакалавриат, Магистратура, Специалитет) |
description |
TEXT |
Описание |
Эта таблица используется и группами (student_groups.education_form_id), и календарными учебными графиками (academic_calendars.study_form_id). Отдельного справочника форм обучения для календарей нет.
student_groups — Учебные группы
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(100) |
Название группы (напр. ИВТ-21-1), не уникальное |
group_size |
BIGINT |
Количество студентов |
education_form_id |
BIGINT FK → education_forms |
Форма обучения |
department_id |
BIGINT FK → departments |
Кафедра |
specialty_id |
BIGINT FK → specialties |
Специальность |
specialty_profile_id |
BIGINT FK → specialty_profiles |
Профиль обучения группы |
year_start_study |
BIGINT |
Год начала обучения, используется для вычисления текущего курса |
subgroups — Подгруппы
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
group_id |
BIGINT FK → student_groups (CASCADE) |
Родительская группа |
name |
VARCHAR(100) |
Название подгруппы |
student_capacity |
INT |
Количество студентов |
Уникальность задаётся парой (group_id, name): в разных группах могут быть подгруппы с одинаковым названием. Подгруппы применяются только для лабораторных занятий.
subjects — Дисциплины
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(200) UNIQUE |
Название |
code |
VARCHAR(20) |
Код предмета |
department_id |
BIGINT FK → departments |
Кафедра |
description |
TEXT |
Описание |
Аудиторный фонд
classrooms — Аудитории
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(50) UNIQUE |
Название (напр. 101 Ленинская) |
capacity |
INT CHECK(> 0) |
Вместимость |
building |
VARCHAR(50) |
Корпус |
floor |
INT |
Этаж |
is_available |
BOOLEAN |
Доступна для назначения пар |
status |
VARCHAR(20) |
ACTIVE или ARCHIVED; архивные аудитории не выбираются в новых назначениях |
active_from |
DATE |
Дата начала действия записи |
active_to |
DATE |
Дата окончания действия записи |
archived_at |
TIMESTAMP |
Когда аудитория выведена из эксплуатации |
archive_reason |
TEXT |
Причина архивирования |
description |
TEXT |
Описание |
equipments — Оборудование
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(50) UNIQUE |
Название |
description |
TEXT |
Описание |
inventory_number |
VARCHAR(50) |
Инвентарный номер |
classroom_equipments — Привязка оборудования к аудиториям
| Колонка |
Тип |
Описание |
classroom_id |
BIGINT PK, FK → classrooms (CASCADE) |
Аудитория |
equipment_id |
BIGINT PK, FK → equipments (CASCADE) |
Оборудование |
quantity |
INT CHECK(> 0) |
Количество единиц |
notes |
TEXT |
Примечания |
Расписание
lesson_types — Типы занятий (справочник)
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
name |
VARCHAR(50) UNIQUE |
Название типа |
color_code |
VARCHAR(7) |
HEX-цвет для UI (напр. #FF6B6B) |
duration_minutes |
INT |
Длительность (по умолчанию 90) |
Связи «Преподаватель ↔ Дисциплина»
teacher_subjects — Квалификация преподавателей
| Колонка |
Тип |
Описание |
user_id |
BIGINT PK, FK → users (CASCADE) |
Преподаватель |
subject_id |
BIGINT PK, FK → subjects (CASCADE) |
Дисциплина |
qualification_level |
VARCHAR(50) |
Уровень квалификации |
experience_years |
INT |
Стаж |
teacher_lesson_types — Типы занятий преподавателя
| Колонка |
Тип |
Описание |
user_id |
BIGINT PK, FK → users (CASCADE) |
Преподаватель |
subject_id |
BIGINT PK, FK → subjects (CASCADE) |
Дисциплина |
lesson_type_id |
BIGINT PK, FK → lesson_types (CASCADE) |
Тип занятия |
teacher_department_assignments — История кафедр преподавателя
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID исторической записи |
teacher_id |
BIGINT FK → users |
Преподаватель |
department_id |
BIGINT FK → departments |
Кафедра |
valid_from |
DATE |
Дата начала принадлежности |
valid_to |
DATE |
Дата окончания принадлежности, NULL для текущей кафедры |
is_primary |
BOOLEAN |
Основная кафедра преподавателя |
comment |
TEXT |
Комментарий к переводу |
created_at |
TIMESTAMP |
Дата создания записи |
created_by |
BIGINT FK → users |
Кто оформил перевод |
Индекс uq_teacher_department_open_primary гарантирует не больше одной открытой основной кафедры у преподавателя.
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID комментария |
subject_id |
BIGINT FK → subjects |
Дисциплина |
author_id |
BIGINT FK → users |
Автор комментария |
comment |
TEXT |
Текст комментария |
created_at |
TIMESTAMP |
Дата создания |
time_slot_scopes — Сетки времени
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
code |
VARCHAR(50) UNIQUE |
Системный код сетки |
name |
VARCHAR(120) |
Название в интерфейсе |
apply_mode |
VARCHAR(20) |
DEFAULT, WEEKDAY, MANUAL |
day_of_week |
INT NULL |
День недели для автоматической сетки |
system_scope |
BOOLEAN |
Защищает базовую и субботнюю сетки от удаления |
display_order |
INT |
Порядок в списках |
Seed создаёт Базовая сетка (DEFAULT) и Субботняя сетка (WEEKDAY, day_of_week = 6). Дополнительные сетки создаются как MANUAL и применяются только через ручные назначения дат.
time_slots — Временные слоты занятий
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
time_slot_scope_id |
BIGINT FK → time_slot_scopes |
Сетка времени |
order_number |
INT |
Номер пары в дне |
start_time |
TIME |
Время начала |
end_time |
TIME |
Время окончания |
duration_minutes |
INT |
Длительность в минутах |
Уникальность задаётся индексом (time_slot_scope_id, order_number): в одной сетке может быть только один слот с номером пары.
time_slot_date_assignments — Ручные назначения сеток времени
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
assignment_date |
DATE UNIQUE |
Дата ручного применения |
time_slot_scope_id |
BIGINT FK → time_slot_scopes |
Ручная сетка времени |
academic_years — Учебные годы
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
title |
VARCHAR(20) UNIQUE |
Название, например 2025-2026 |
start_date |
DATE |
Дата начала |
end_date |
DATE |
Дата окончания |
semesters — Семестры
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
academic_year_id |
BIGINT FK → academic_years |
Учебный год |
semester_type |
VARCHAR(20) |
autumn или spring |
start_date |
DATE |
Дата начала, от неё считается неделя 1 |
end_date |
DATE |
Дата окончания |
academic_calendar_activity_types — Коды активностей графика
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
code |
VARCHAR(10) UNIQUE |
Код из Excel-графика: Т, Э, К, У, П, Пд, Н, Г, Д, ПА, С, *, = |
name |
VARCHAR(100) |
Расшифровка кода |
allow_schedule |
BOOLEAN |
Разрешает генерацию обычных пар |
color_code |
VARCHAR(7) |
HEX-цвет для редактора |
display_order |
INT |
Порядок вывода в UI |
description |
TEXT |
Дополнительное описание |
academic_calendars — Календарные учебные графики
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
title |
VARCHAR(255) |
Название графика |
academic_year_id |
BIGINT FK → academic_years |
Учебный год |
specialty_id |
BIGINT FK → specialties |
Специальность |
specialty_profile_id |
BIGINT FK → specialty_profiles |
Профиль обучения |
study_form_id |
BIGINT FK → education_forms |
Форма обучения из общего справочника |
course_count |
INT CHECK(> 0) |
Количество курсов в сетке |
created_at |
TIMESTAMP |
Дата создания |
updated_at |
TIMESTAMP |
Дата обновления |
academic_calendar_days — Дневная сетка календарного графика
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
calendar_id |
BIGINT FK → academic_calendars (CASCADE) |
Календарный график |
course_number |
INT |
Номер курса |
date |
DATE |
Дата учебного года |
week_number |
INT |
Номер недели от начала учебного года |
day_of_week |
INT CHECK(1–7) |
День недели ISO |
activity_type_id |
BIGINT FK → academic_calendar_activity_types |
Код активности |
student_group_calendar_assignments — Назначения графиков группам
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
group_id |
BIGINT FK → student_groups (CASCADE) |
Учебная группа |
academic_year_id |
BIGINT FK → academic_years (CASCADE) |
Учебный год |
calendar_id |
BIGINT FK → academic_calendars (CASCADE) |
Назначенный график |
schedule_rules — Правила расписания
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
subject_id |
BIGINT FK → subjects |
Дисциплина |
semester_id |
BIGINT FK → semesters |
Семестр |
lecture_academic_hours |
INT |
Лимит академических часов лекций |
laboratory_academic_hours |
INT |
Лимит академических часов лабораторных работ |
practice_academic_hours |
INT |
Лимит академических часов практик |
lecture_start_week |
INT |
Неделя семестра, с которой начинаются лекции |
laboratory_start_week |
INT |
Неделя семестра, с которой начинаются лабораторные |
practice_start_week |
INT |
Неделя семестра, с которой начинаются практики |
status |
VARCHAR(20) |
ACTIVE или ARCHIVED |
valid_from |
DATE |
Начало действия версии правила |
valid_to |
DATE |
Окончание действия версии правила |
version_group_id |
BIGINT |
Группа версий одного правила |
change_reason |
TEXT |
Причина изменения |
schedule_rule_groups — Группы правила
| Колонка |
Тип |
Описание |
schedule_rule_id |
BIGINT PK, FK → schedule_rules |
Правило |
group_id |
BIGINT PK, FK → student_groups |
Группа |
schedule_rule_slots — Слоты правила
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID |
schedule_rule_id |
BIGINT FK → schedule_rules |
Правило |
day_of_week |
INT CHECK(1–7) |
День недели: 1 — понедельник |
parity |
VARCHAR(10) |
BOTH, EVEN, ODD |
time_slot_id |
BIGINT FK → time_slots |
Базовый временной слот |
teacher_id |
BIGINT FK → users |
Преподаватель |
classroom_id |
BIGINT FK → classrooms |
Аудитория |
lesson_type_id |
BIGINT FK → lesson_types |
Тип занятия |
lesson_format |
VARCHAR(30) |
Очно или Онлайн |
time_locked |
BOOLEAN |
Время закреплено учебным отделом |
classroom_locked |
BOOLEAN |
Аудитория закреплена учебным отделом |
teacher_locked |
BOOLEAN |
Преподаватель закреплён учебным отделом |
locked_by |
BIGINT FK → users |
Кто выполнил закрепление |
locked_at |
TIMESTAMP |
Когда выполнено закрепление |
lock_comment |
TEXT |
Комментарий к закреплению |
schedule_rule_slot_subgroups — Подгруппы лабораторного слота
| Колонка |
Тип |
Описание |
schedule_rule_slot_id |
BIGINT PK, FK → schedule_rule_slots (CASCADE) |
Слот правила |
subgroup_id |
BIGINT PK, FK → subgroups (CASCADE) |
Подгруппа |
Связь заполняется только для лабораторных слотов. Для потоковой лабораторной можно выбрать разные подгруппы разных групп в одном слоте. Лекции и практики не делятся на подгруппы и не имеют записей в этой таблице. Триггер validate_schedule_rule_slot_subgroups проверяет тип занятия, принадлежность подгруппы к группам правила и запрет на две подгруппы одной группы в одном слоте; для быстрых выборок есть индекс idx_schedule_rule_slot_subgroups_subgroup.
schedule_overrides — Точечные изменения пар
| Колонка |
Тип |
Описание |
id |
BIGSERIAL PK |
ID изменения |
base_rule_slot_id |
BIGINT FK → schedule_rule_slots |
Базовый слот правила |
lesson_date |
DATE |
Дата конкретной пары |
action |
VARCHAR(20) |
MOVE, CANCEL, REPLACE |
new_time_slot_id |
BIGINT FK → time_slots |
Новый временной слот |
new_classroom_id |
BIGINT FK → classrooms |
Новая аудитория |
new_teacher_id |
BIGINT FK → users |
Новый преподаватель |
new_lesson_format |
VARCHAR(30) |
Новый формат занятия |
comment |
TEXT |
Причина изменения |
created_by |
BIGINT FK → users |
Автор изменения |
created_at |
TIMESTAMP |
Дата создания |
Ограничение uq_schedule_overrides_slot_date не позволяет создать две разные правки для одной и той же пары.
Flyway миграции
Правила работы
- Все миграции находятся в
backend/src/main/resources/db/migration/
- Формат имени:
V{номер}__{описание}.sql (напр. V1__init.sql, V2__add_departments.sql)
- ЗАПРЕЩЕНО изменять уже закоммиченные файлы миграций — это сломает контрольные суммы Flyway
- Flyway запускается программно при первом обращении к БД тенанта (
TenantConfigWatcher.initDatabaseForTenant())
- Настройка
baselineOnMigrate=true — если в БД уже есть данные, Flyway начнёт с baseline
Текущая ветка календарного учебного графика является осознанным исключением: V1__init.sql переписан как новая базовая схема, а применение предполагает полный сброс БД без переноса старых данных.
Текущие миграции
| Файл |
Описание |
V1__init.sql |
Инициализация: справочники, роли, lifecycle-поля, история кафедр преподавателей, комментарии дисциплин, календарные учебные графики, динамическое расписание, версии/закрепления правил, точечные изменения расписания, тестовые правила, триггеры, комментарии |
Накатывание на существующих тенантов
Для применения новой базовой схемы к уже существующим тенантам нужен полный сброс БД. Перезапуск backend без сброса не изменит уже применённую V1__init.sql.
Полный сброс БД (локально)