# 🗄 База данных ## Общая информация - **СУБД:** PostgreSQL (локально `postgres:alpine3.23`, продакшн — managed PostgreSQL) - **Управление схемой:** Flyway (программный запуск) - **Hibernate DDL:** Отключён (`ddl-auto=none`) - **Расширения:** `pgcrypto` (bcrypt-хеширование паролей) - **Мультитенантность:** Каждый тенант = отдельная БД --- ## ER-диаграмма ```mermaid erDiagram departments { BIGSERIAL id PK VARCHAR name BIGINT code UK } specialties { BIGSERIAL id PK VARCHAR name VARCHAR specialty_code } users { BIGSERIAL id PK VARCHAR username UK VARCHAR password VARCHAR role VARCHAR full_name VARCHAR job_title BIGINT department_id FK TIMESTAMP created_at TIMESTAMP updated_at } education_forms { BIGSERIAL id PK VARCHAR name UK TEXT description TIMESTAMP created_at } student_groups { BIGSERIAL id PK VARCHAR name UK BIGINT group_size BIGINT education_form_id FK BIGINT department_id FK BIGINT specialty_code FK BIGINT year_start_study TIMESTAMP created_at } subgroups { BIGSERIAL id PK BIGINT group_id FK VARCHAR name INT student_capacity } subjects { BIGSERIAL id PK VARCHAR name UK VARCHAR code BIGINT department_id FK TEXT description TIMESTAMP created_at } lesson_types { BIGSERIAL id PK VARCHAR name UK VARCHAR color_code INT duration_minutes } equipments { BIGSERIAL id PK VARCHAR name UK TEXT description VARCHAR inventory_number } classrooms { BIGSERIAL id PK VARCHAR name UK INT capacity VARCHAR building INT floor BOOLEAN is_available TEXT description TIMESTAMP created_at } classroom_equipments { BIGINT classroom_id FK,PK BIGINT equipment_id FK,PK INT quantity TEXT notes } teacher_subjects { BIGINT user_id FK,PK BIGINT subject_id FK,PK VARCHAR qualification_level INT experience_years } teacher_lesson_types { BIGINT user_id FK,PK BIGINT subject_id FK,PK BIGINT lesson_type_id FK,PK } time_slots { BIGSERIAL id PK INT order_number UK TIME start_time TIME end_time INT duration_minutes } academic_years { BIGSERIAL id PK VARCHAR title UK DATE start_date DATE end_date } semesters { BIGSERIAL id PK BIGINT academic_year_id FK VARCHAR semester_type DATE start_date DATE end_date } holidays { BIGSERIAL id PK DATE date BIGINT academic_year_id FK VARCHAR description } academic_calendar_matrix { BIGSERIAL id PK BIGINT semester_id FK INT course_number BIGINT specialty_id FK INT week_number VARCHAR activity_type } schedule_rules { BIGSERIAL id PK BIGINT subject_id FK BIGINT semester_id FK DATE active_from_date INT total_academic_hours } schedule_rule_groups { BIGINT schedule_rule_id FK,PK BIGINT group_id FK,PK } schedule_rule_slots { BIGSERIAL id PK BIGINT schedule_rule_id FK INT day_of_week VARCHAR parity BIGINT time_slot_id FK BIGINT subgroup_id FK BIGINT teacher_id FK BIGINT classroom_id FK BIGINT lesson_type_id FK VARCHAR lesson_format } departments ||--o{ users : "department_id" departments ||--o{ student_groups : "department_id" departments ||--o{ subjects : "department_id" education_forms ||--o{ student_groups : "education_form_id" specialties ||--o{ student_groups : "specialty_code" student_groups ||--o{ subgroups : "group_id" student_groups ||--o{ schedule_rule_groups : "group_id" users ||--o{ teacher_subjects : "user_id" users ||--o{ teacher_lesson_types : "user_id" subjects ||--o{ teacher_subjects : "subject_id" subjects ||--o{ teacher_lesson_types : "subject_id" subjects ||--o{ schedule_rules : "subject_id" lesson_types ||--o{ teacher_lesson_types : "lesson_type_id" lesson_types ||--o{ schedule_rule_slots : "lesson_type_id" classrooms ||--o{ schedule_rule_slots : "classroom_id" classrooms ||--o{ classroom_equipments : "classroom_id" equipments ||--o{ classroom_equipments : "equipment_id" academic_years ||--o{ semesters : "academic_year_id" academic_years ||--o{ holidays : "academic_year_id" semesters ||--o{ academic_calendar_matrix : "semester_id" semesters ||--o{ schedule_rules : "semester_id" specialties ||--o{ academic_calendar_matrix : "specialty_id" schedule_rules ||--o{ schedule_rule_groups : "schedule_rule_id" schedule_rules ||--o{ schedule_rule_slots : "schedule_rule_id" time_slots ||--o{ schedule_rule_slots : "time_slot_id" subgroups ||--o{ schedule_rule_slots : "subgroup_id" users ||--o{ schedule_rule_slots : "teacher_id" ``` --- ## Описание таблиц ### Справочники высшего уровня #### `departments` — Кафедры | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID кафедры | | `name` | VARCHAR(255) | Название кафедры | | `code` | BIGINT UNIQUE | Код кафедры | #### `specialties` — Специальности | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID специальности | | `name` | VARCHAR(255) | Название специальности | | `specialty_code` | VARCHAR(255) | Код ФГОС (напр. `10.03.01`) | ### Пользователи #### `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 | Кафедра | | `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` — Учебные группы | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `name` | VARCHAR(100) UNIQUE | Название группы (напр. `ИВТ-21-1`) | | `group_size` | BIGINT | Количество студентов | | `education_form_id` | BIGINT FK → education_forms | Форма обучения | | `department_id` | BIGINT FK → departments | Кафедра | | `specialty_code` | BIGINT FK → specialties | Специальность | | `year_start_study` | BIGINT | Год начала обучения, используется для вычисления текущего курса | #### `subgroups` — Подгруппы | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `group_id` | BIGINT FK → student_groups (CASCADE) | Родительская группа | | `name` | VARCHAR(100) | Название подгруппы | | `student_capacity` | INT | Количество студентов | #### `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 | Доступна для назначения пар | | `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) | Тип занятия | #### `time_slots` — Временные слоты занятий | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `order_number` | INT UNIQUE | Номер пары в дне | | `start_time` | TIME | Время начала | | `end_time` | TIME | Время окончания | | `duration_minutes` | INT | Длительность в минутах | #### `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 | Дата окончания | #### `holidays` — Праздники и исключения | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `date` | DATE | Неучебная дата | | `academic_year_id` | BIGINT FK → academic_years | Учебный год | | `description` | VARCHAR(255) | Описание | #### `academic_calendar_matrix` — Матрица учебного графика | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `semester_id` | BIGINT FK → semesters | Семестр | | `course_number` | INT | Курс | | `specialty_id` | BIGINT FK → specialties | Специальность | | `week_number` | INT | Номер недели семестра | | `activity_type` | VARCHAR(20) | `THEORY`, `EXAM`, `VACATION`, `PRACTICE` | #### `schedule_rules` — Правила расписания | Колонка | Тип | Описание | |---------|-----|----------| | `id` | BIGSERIAL PK | ID | | `subject_id` | BIGINT FK → subjects | Дисциплина | | `semester_id` | BIGINT FK → semesters | Семестр | | `active_from_date` | DATE | Дата начала действия правила | | `total_academic_hours` | INT | Лимит академических часов | #### `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 | Временной слот | | `subgroup_id` | BIGINT FK → subgroups, NULL | Подгруппа | | `teacher_id` | BIGINT FK → users | Преподаватель | | `classroom_id` | BIGINT FK → classrooms | Аудитория | | `lesson_type_id` | BIGINT FK → lesson_types | Тип занятия | | `lesson_format` | VARCHAR(30) | `Очно` или `Онлайн` | --- ## Flyway миграции ### Правила работы 1. Все миграции находятся в `backend/src/main/resources/db/migration/` 2. Формат имени: `V{номер}__{описание}.sql` (напр. `V1__init.sql`, `V2__add_departments.sql`) 3. **ЗАПРЕЩЕНО** изменять уже закоммиченные файлы миграций — это сломает контрольные суммы Flyway 4. Flyway запускается **программно** при первом обращении к БД тенанта (`TenantConfigWatcher.initDatabaseForTenant()`) 5. Настройка `baselineOnMigrate=true` — если в БД уже есть данные, Flyway начнёт с baseline ### Текущие миграции | Файл | Описание | |------|----------| | `V1__init.sql` | Инициализация: справочники, динамическое расписание, тестовые правила, триггеры, комментарии | ### Накатывание на существующих тенантов Для применения новой миграции к уже существующим тенантам необходимо перезапустить backend: ```bash # Kubernetes kubectl rollout restart deployment backend -n magistr # Docker Compose (локально) docker compose restart backend ``` ### Полный сброс БД (локально) ```bash docker compose down -v # Удаляет volumes (данные) docker compose up -d # Пересоздаёт БД с нуля ```