задачи 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

@@ -286,10 +286,37 @@ erDiagram
BIGINT calendar_id FK
}
schedule_versions {
BIGSERIAL id PK
BIGINT semester_id FK
INT version_number
VARCHAR name
VARCHAR status
BIGINT based_on_version_id FK
BIGINT restored_from_version_id FK
TEXT change_reason
BIGINT created_by FK
TIMESTAMPTZ created_at
BIGINT published_by FK
TIMESTAMPTZ published_at
TIMESTAMPTZ archived_at
}
schedule_version_history {
BIGSERIAL id PK
BIGINT version_id FK
VARCHAR action
BIGINT actor_id FK
TEXT reason
TIMESTAMPTZ created_at
}
schedule_rules {
BIGSERIAL id PK
BIGINT subject_id FK
BIGINT semester_id FK
BIGINT schedule_version_id FK
BIGINT version_group_id
VARCHAR status
DATE valid_from
DATE valid_to
@@ -442,6 +469,12 @@ erDiagram
academic_years ||--o{ semesters : "academic_year_id"
academic_years ||--o{ academic_calendars : "academic_year_id"
academic_years ||--o{ student_group_calendar_assignments : "academic_year_id"
semesters ||--o{ schedule_versions : "semester_id"
schedule_versions ||--o{ schedule_rules : "schedule_version_id"
schedule_versions ||--o{ schedule_version_history : "version_id"
schedule_versions o|--o{ schedule_versions : "based_on/restored_from"
users ||--o{ schedule_versions : "created_by/published_by"
users ||--o{ schedule_version_history : "actor_id"
semesters ||--o{ schedule_rules : "semester_id"
specialties ||--o{ academic_calendars : "specialty_id"
specialty_profiles ||--o{ academic_calendars : "specialty_profile_id"
@@ -896,12 +929,50 @@ V1 создаёт GiST exclusion constraint `ex_academic_years_no_overlap` дл
триггеры защищают назначение при изменении группы, графика и границ учебного года; блокировки
ссылочных строк закрывают конкурентные записи между несколькими backend-pod.
#### `schedule_versions` — Версии расписания семестра
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | BIGSERIAL PK | ID версии |
| `semester_id` | BIGINT FK → semesters (CASCADE) | Семестр |
| `version_number` | INT CHECK(> 0) | Последовательный номер внутри семестра |
| `name` | VARCHAR(160) | Пользовательское название |
| `status` | VARCHAR(20) | `DRAFT`, `PUBLISHED` или `ARCHIVED` |
| `based_on_version_id` | BIGINT FK → schedule_versions | Версия-основа черновика |
| `restored_from_version_id` | BIGINT FK → schedule_versions | Публикация, которую заменили при восстановлении |
| `change_reason` | TEXT | Причина последней публикации или восстановления |
| `created_by` | BIGINT FK → users | Автор черновика |
| `created_at` | TIMESTAMPTZ | Время создания |
| `published_by` | BIGINT FK → users | Автор публикации |
| `published_at` | TIMESTAMPTZ | Время последней публикации |
| `archived_at` | TIMESTAMPTZ | Время архивирования |
Пара `semester_id + version_number` уникальна. Частичный индекс
`uq_schedule_versions_published_semester` запрещает более одной строки `PUBLISHED` на
семестр. Начальная загрузка V1 создаёт опубликованную версию 1 для каждого семестра и
привязывает к ней существующие seed-правила.
#### `schedule_version_history` — Аудит версий расписания
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | BIGSERIAL PK | ID события |
| `version_id` | BIGINT FK → schedule_versions (CASCADE) | Версия расписания |
| `action` | VARCHAR(30) | `CREATED`, `PUBLISHED`, `ARCHIVED` или `RESTORED` |
| `actor_id` | BIGINT FK → users | Автор действия; `NULL` для системной инициализации |
| `reason` | TEXT | Причина или описание события, до 2000 символов |
| `created_at` | TIMESTAMPTZ | Время события |
Журнал добавляется при каждом переходе версии и выводится в обратной хронологии. Он не
заменяет данные версии, а сохраняет отдельные факты аудита.
#### `schedule_rules` — Правила расписания
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | BIGSERIAL PK | ID |
| `subject_id` | BIGINT FK → subjects | Дисциплина |
| `semester_id` | BIGINT FK → semesters | Семестр |
| `schedule_version_id` | BIGINT FK → schedule_versions (CASCADE) | Версия расписания |
| `lecture_academic_hours` | INT | Лимит академических часов лекций |
| `laboratory_academic_hours` | INT | Лимит академических часов лабораторных работ |
| `practice_academic_hours` | INT | Лимит академических часов практик |
@@ -914,7 +985,7 @@ V1 создаёт GiST exclusion constraint `ex_academic_years_no_overlap` дл
| `version_group_id` | BIGINT | Группа версий одного правила |
| `change_reason` | TEXT | Причина изменения |
`ScheduleRule` использует собственные поля жизненного цикла `status`, `valid_from` и `valid_to`: архивированное правило или правило вне периода действия не участвует в генерации расписания. В отличие от справочников на `LifecycleEntity`, таблица не содержит `active_from`/`active_to`, поэтому состояние правила проверяется по `valid_*`.
`ScheduleRule` использует собственные поля жизненного цикла `status`, `valid_from` и `valid_to`: архивированное правило или правило вне периода действия не участвует в генерации расписания. В отличие от справочников на `LifecycleEntity`, таблица не содержит `active_from`/`active_to`, поэтому состояние правила проверяется по `valid_*`. Публичная генерация дополнительно требует статус `PUBLISHED` у связанной версии. Уникальный индекс по `schedule_version_id + version_group_id` не позволяет дважды скопировать одну логическую линию правила в одну версию.
Базовая схема V1 требует, чтобы лимиты лекций, лабораторных и практик были кратны двум.
Неотрицательность каждого лимита и положительная сумма уже закреплены ограничениями V1;
@@ -1111,19 +1182,19 @@ CHECK фиксирует допустимую форму каждого типа
| Файл | Описание |
|------|----------|
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики с интервальным хранением активностей и нумерацией недель `понедельник–воскресенье`, динамическое расписание, точечные изменения с переносом даты, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
| `V2__teacher_absences_and_replacement_wizard.sql` | Реестр отсутствий преподавателей, статусы согласования и журнал применённых/отклонённых решений со ссылками на обычные `schedule_overrides` |
| `V3__teacher_preferences_and_change_requests.sql` | Пожелания преподавателей на семестр, строгая и мягкая доступность, заявки на изменение занятия и неизменяемая история решений |
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики, динамическое расписание, версии/черновики и аудит публикаций, точечные изменения, отсутствия и журнал замен, пожелания преподавателей, заявки на изменение занятий и их история, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
### Этап разработки
Исторические разработческие миграции V2–V7 по прямому решению владельца проекта были
объединены в baseline `V1`. После фиксации baseline нумерация начата заново: `V2`
добавляет отсутствия и мастер замены, а `V3` — пожелания преподавателей и заявки на
изменение занятий, не изменяя контрольную сумму `V1`.
Исторические разработческие миграции V2–V7, а затем повторно созданные V2 с отсутствиями
и мастером замены и V3 с пожеланиями и заявками преподавателей по прямому решению владельца
проекта объединены в baseline `V1`. В каталоге миграций остаётся один файл
`V1__init.sql`.
Интервальное хранение активностей и правильная нумерация недель календарного графика входят
непосредственно в V1.
Перед применением этой редакции требуется полностью пустая tenant-схема.
Перед применением этой редакции требуется полностью пустая tenant-схема: для базы, где
предыдущая V1 уже записана в `flyway_schema_history`, изменённая контрольная сумма вызовет
ошибку проверки.
### Полный сброс БД (локально)