Files
magistr/SUPERVISOR_TASKS_IMPLEMENTATION_PLAN.md
2026-05-19 15:02:07 +03:00

542 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План реализации задач после созвона с научным руководителем
Дата: 2026-05-19
## 1. Короткий вывод
В проекте уже есть основа для большей части требований: справочники, пользователи, кафедры, дисциплины, аудитории, динамическое расписание на базе `schedule_rules`, календарный учебный график, временные сетки и экран загруженности аудиторий. Главные недостающие архитектурные слои:
- нормальная ролевая модель и серверная авторизация;
- темпоральность: история принадлежности, версионирование связей и запрет ретроактивного изменения прошлого расписания;
- мягкое удаление справочников вместо физического удаления;
- отдельные рабочие места для кафедры и учебного отдела;
- расширенные просмотры расписания и загруженности по преподавателям, аудиториям, кафедрам, парам и периодам.
Рекомендуемый порядок: сначала ввести темпоральный слой, жизненный цикл записей и роли, затем уже строить новые кабинеты и редакторы. Если начать с экранов, старые связи будут продолжать теряться при удалениях, переносах преподавателей и правках расписания.
## 2. Расшифровка задач руководителя в терминах системы
| Формулировка | Что реализуем |
|-------------|---------------|
| Загруженность по паре или преподавателю | Аналитика занятости по временным слотам, преподавателям, аудиториям, кафедрам, дисциплинам и периодам. |
| Темпоральные базы данных | Не отдельная БД, а темпоральная модель внутри текущей PostgreSQL БД каждого тенанта: периоды действия записей, история связей, версионирование расписания. |
| Сделать удаление аудиторий и т.д., чтобы нельзя было использовать в дальнейшем | Мягкое удаление/архивация справочников. Исторические записи остаются, новые назначения на архивные сущности запрещены. |
| Перенос преподавателей с одной кафедры в другую без потери связи с прошлыми записями | История принадлежности преподавателя к кафедрам через интервалы действия, а не простое перезаписывание `users.department_id`. |
| Уровни пользователей | Расширение ролей и прав: администратор, кафедра, учебный отдел, преподаватель, студент. Проверки должны быть на backend, не только в UI. |
| Администратор: создание аудиторий, специальностей, кафедр и т.д. | Довести справочники до полного CRUD + архив/восстановление + фильтр архивных записей. |
| Кафедра: смотреть предметы кафедры, грузить, связывать предметы, разделять, комментарии, расписания и загруженность | Новый кабинет кафедры: дисциплины кафедры, импорт, описание дисциплин, привязки преподавателей и типов занятий, комментарии, расписание и нагрузка кафедры. |
| Учебный отдел: закреплять время и аудитории, редактировать расписание, закреплять предметы и преподавателей в аудитории в определённое время | Новый кабинет учебного отдела: редактор расписания, фиксация времени/аудиторий/преподавателей, точечные переносы и отмены, проверка конфликтов. |
| Просмотр расписания разных аудиторий, преподавателей, кафедр | Универсальный просмотр расписания с фильтрами по аудитории, преподавателю, группе, кафедре, дисциплине и периоду. |
## 3. Текущее состояние проекта
### Уже есть
- Backend: Java 17, Spring Boot, JPA, Flyway, мультитенантность через отдельную БД на тенант.
- Frontend: Vanilla JS, отдельные интерфейсы `/admin/`, `/teacher/`, `/student/`.
- Роли сейчас ограничены `ADMIN`, `TEACHER`, `STUDENT`.
- Авторизация возвращает UUID-токен и роль, но полноценной серверной проверки прав по эндпоинтам пока нет.
- Динамическое расписание строится из:
- `schedule_rules`;
- `schedule_rule_groups`;
- `schedule_rule_slots`;
- `schedule_rule_slot_subgroups`;
- `time_slots`;
- `academic_years` / `semesters`;
- `academic_calendars`.
- `GET /api/schedule` сейчас принимает ровно один фильтр: `groupId` или `teacherId`.
- Экран загруженности аудиторий уже есть и использует реальные аудитории, временные слоты и сгенерированное расписание.
- У аудиторий есть `is_available`, но `DELETE /api/classrooms/{id}` физически удаляет запись.
- Преподаватель сейчас имеет одно поле `users.department_id`; история переводов не хранится.
- Дисциплины связаны с кафедрой через `subjects.department_id`; история смены кафедры дисциплины не хранится.
- Привязка преподавателей к дисциплинам есть через `teacher_subjects`, но это простая текущая связь без периода действия, комментариев и разбиения по ответственности.
### Основные проблемы текущей модели
- Физическое удаление справочников опасно для истории расписания и отчётов.
- Обновление текущих связей перезаписывает контекст прошлого.
- Динамическое расписание пересчитывается из текущих правил, поэтому изменение правила может изменить отображение прошлого периода.
- Ролевая модель не отражает учебный отдел и кафедру как самостоятельных пользователей.
- Просмотр расписания и загруженности завязан на частные сценарии, а не на единый фильтруемый слой.
## 4. Целевая архитектурная идея
Ввести три базовых понятия:
1. **Жизненный цикл записи**: активна, архивирована, недоступна для новых назначений.
2. **Период действия связи**: преподаватель относится к кафедре не "навсегда", а с даты по дату.
3. **Версия расписания**: прошлое расписание должно оставаться воспроизводимым, даже если текущие правила изменились.
После этого все новые экраны работают с одной логикой:
- активные справочники доступны для выбора;
- архивные записи видны в истории, но не выбираются для будущих назначений;
- отчёты за прошлый период используют связи, действовавшие на дату занятия;
- правки будущего расписания не ломают прошлые отчёты.
## 5. Этап 1. Роли и права доступа
### Новые роли
| Роль | Назначение |
|------|------------|
| `ADMIN` | Полный доступ внутри тенанта: справочники, пользователи, роли, настройки. |
| `EDUCATION_OFFICE` | Учебный отдел: расписание, аудитории, временные слоты, закрепления, конфликты. |
| `DEPARTMENT` | Кафедра: дисциплины своей кафедры, преподаватели, комментарии, нагрузка, просмотр расписания. |
| `TEACHER` | Преподаватель: личное расписание и, позже, заявки на изменения. |
| `STUDENT` | Просмотр расписания. |
`ADMIN` можно оставить как суперроль внутри одной БД тенанта. Управление тенантами через `/api/database` стоит разрешить только `ADMIN`.
### Backend
- Расширить `Role`.
- Обновить `AuthController.ROLE_REDIRECTS`:
- `ADMIN` -> `/admin/`;
- `EDUCATION_OFFICE` -> `/edu-office/`;
- `DEPARTMENT` -> `/department/`;
- `TEACHER` -> `/teacher/`;
- `STUDENT` -> `/student/`.
- Добавить серверную авторизацию:
- хранение активных сессий/токенов или проверяемый токен;
- фильтр/интерцептор, который получает пользователя по `Authorization`;
- аннотации или централизованные проверки прав в контроллерах.
- Не полагаться на `localStorage.role` как на источник прав.
### Frontend
- Добавить оболочки `/department/` и `/edu-office/`.
- В админке скрывать пункты меню по роли, но считать это только UX-ограничением.
- Все ошибки доступа показывать на русском: `Недостаточно прав для выполнения операции`.
## 6. Этап 2. Темпоральный слой и жизненный цикл справочников
### Базовые поля жизненного цикла
Для справочников, которые могут участвовать в расписании или отчётах, добавить:
- `status` (`ACTIVE`, `ARCHIVED`);
- `active_from`;
- `active_to`;
- `archived_at`;
- `archived_by`;
- `archive_reason`.
Минимальный набор для первого внедрения:
- `classrooms`;
- `equipments`;
- `departments`;
- `specialties`;
- `specialty_profiles`;
- `education_forms`;
- `student_groups`;
- `subgroups`;
- `subjects`;
- `users`.
Для сущностей, где достаточно простого вывода из эксплуатации, можно начать с `status`, `archived_at`, `archive_reason`, а `active_from/active_to` добавить там, где требуется отчётность по датам.
### Правила использования
- `GET` по умолчанию возвращает только активные записи.
- Для админских экранов добавить `includeArchived=true`.
- `DELETE` заменить на архивирование.
- Физическое удаление оставить только для записей, которые точно нигде не используются, либо убрать из публичных API.
- В формах будущего расписания нельзя выбирать архивные аудитории, дисциплины, группы, подгруппы, кафедры и пользователей.
- В историческом расписании архивные записи отображаются с пометкой, например `Аудитория 101 (архив)`.
### Аудитории
`is_available` и архивирование должны означать разные вещи:
- `is_available=false` — аудитория временно недоступна, но может вернуться в работу;
- `status=ARCHIVED` — аудитория выведена из эксплуатации и не может использоваться в новых назначениях.
UI-кнопку `Удалить` лучше заменить на `Вывести из эксплуатации`. Для администратора можно добавить отдельное действие `Восстановить`.
## 7. Этап 3. История переводов преподавателей между кафедрами
### Модель данных
Добавить таблицу:
```sql
teacher_department_assignments
```
Поля:
- `id`;
- `teacher_id`;
- `department_id`;
- `valid_from`;
- `valid_to`;
- `is_primary`;
- `comment`;
- `created_at`;
- `created_by`.
Текущий `users.department_id` можно временно оставить как денормализованное поле "текущая кафедра", но источником правды для отчётов должна стать таблица истории.
### Правила
- У преподавателя должна быть ровно одна основная кафедра на дату.
- При переводе преподавателя:
- закрывается старая запись `valid_to`;
- создаётся новая запись `valid_from`;
- старые расписания и отчёты используют кафедру, актуальную на дату занятия.
- Привязки `teacher_subjects` не удаляются автоматически при переводе, иначе потеряется история. Для будущих назначений можно показывать предупреждение, если дисциплина не относится к текущей кафедре преподавателя.
### API
- `GET /api/users/{id}/department-history`
- `POST /api/users/{id}/department-transfer`
- `GET /api/departments/{id}/teachers?date=YYYY-MM-DD`
- `GET /api/users/teachers?departmentId=&date=`
## 8. Этап 4. Версионирование расписания и точечные изменения
Текущая модель `schedule_rules` хороша для генерации будущего расписания, но недостаточна для неизменяемого прошлого и точечных переносов.
### Версии правил
Добавить к правилам и слотам период действия:
- `valid_from`;
- `valid_to`;
- `status`;
- `version_group_id` или `parent_rule_id`;
- `created_by`;
- `change_reason`.
При редактировании правила:
- если правка относится к будущему периоду, закрывать старую версию и создавать новую;
- если правка исправляет ошибку текущего будущего правила, разрешить обновление только до даты начала действия;
- прошлые периоды не пересчитывать по новым данным.
### Точечные изменения расписания
Добавить таблицу:
```sql
schedule_overrides
```
Назначение: перенос, отмена или замена конкретной сгенерированной пары.
Поля:
- `id`;
- `base_rule_slot_id`;
- `lesson_date`;
- `action` (`MOVE`, `CANCEL`, `REPLACE`);
- `new_time_slot_id`;
- `new_classroom_id`;
- `new_teacher_id`;
- `new_lesson_format`;
- `comment`;
- `created_by`;
- `created_at`.
Генератор расписания должен сначала строить базовые пары из правил, затем применять точечные изменения.
### Закрепление времени, аудитории, преподавателя
В `schedule_rule_slots` или отдельной таблице закреплений добавить признаки:
- `time_locked`;
- `classroom_locked`;
- `teacher_locked`;
- `locked_by`;
- `locked_at`;
- `lock_comment`.
Учебный отдел сможет фиксировать назначения, а автоматические или массовые операции не должны менять закреплённые поля без явного подтверждения.
## 9. Этап 5. Проверка конфликтов
Перед сохранением правила, версии или override проверять:
- преподаватель не занят в другом месте в тот же день/пару;
- аудитория не занята другой парой;
- вместимость аудитории не меньше суммы групп/подгрупп;
- преподаватель привязан к дисциплине и нужному типу занятия;
- выбранная аудитория активна и доступна на дату;
- группа/подгруппа активна на дату;
- кафедральные ограничения не нарушены, если дисциплина принадлежит другой кафедре.
Конфликты делить на:
- `ERROR` — сохранение запрещено;
- `WARNING` — можно сохранить с комментарием, если роль имеет право.
API:
- `POST /api/admin/schedule-rules/validate`
- `POST /api/edu-office/schedule/validate`
- `GET /api/schedule/conflicts?startDate=&endDate=`
## 10. Этап 6. Кабинет кафедры
### Функции
- Просмотр дисциплин своей кафедры.
- Создание и редактирование описания дисциплины.
- Импорт дисциплин из файла. Формат нужно согласовать отдельно: CSV/XLSX, обязательные колонки, правила обновления.
- Разделение данных дисциплины:
- карточка дисциплины: название, код, кафедра, описание, комментарии;
- преподаватели дисциплины;
- типы занятий, которые может вести каждый преподаватель;
- история изменений.
- Комментарии к дисциплине, привязке или расписанию.
- Просмотр расписания кафедры.
- Просмотр загруженности кафедры:
- по преподавателям;
- по дисциплинам;
- по неделям;
- по типам занятий.
### Backend
Новые или расширенные сущности:
- `subject_comments`;
- `subject_department_assignments` или темпоральные поля у `subjects`;
- версионированные `teacher_subjects`;
- `teacher_lesson_types` вывести в полноценный API, если таблица уже есть в схеме.
API:
- `GET /api/department/subjects`
- `POST /api/department/subjects/import`
- `GET /api/department/subjects/{id}`
- `PUT /api/department/subjects/{id}`
- `GET /api/department/subjects/{id}/teachers`
- `PUT /api/department/subjects/{id}/teachers`
- `GET /api/department/schedule`
- `GET /api/department/workload`
- `POST /api/department/comments`
### Frontend
Создать `/department/` как отдельный кабинет:
- `subjects` — дисциплины кафедры;
- `teachers` — преподаватели кафедры и их дисциплины;
- `schedule` — расписание кафедры;
- `workload` — нагрузка;
- `comments` или встроенные комментарии в карточках.
## 11. Этап 7. Кабинет учебного отдела
### Функции
- Просмотр расписания по группам, аудиториям, преподавателям и кафедрам.
- Редактирование правил расписания.
- Точечный перенос пары.
- Отмена пары.
- Замена преподавателя.
- Замена аудитории.
- Закрепление времени, аудитории и преподавателя.
- Работа с конфликтами.
- Просмотр загруженности аудиторий и преподавателей.
### Backend
API:
- `GET /api/edu-office/schedule`
- `POST /api/edu-office/schedule/overrides`
- `PUT /api/edu-office/schedule/overrides/{id}`
- `DELETE /api/edu-office/schedule/overrides/{id}`
- `POST /api/edu-office/schedule/locks`
- `DELETE /api/edu-office/schedule/locks/{id}`
- `GET /api/edu-office/workload/classrooms`
- `GET /api/edu-office/workload/teachers`
- `GET /api/edu-office/conflicts`
### Frontend
Создать `/edu-office/` с вкладками:
- `Расписание`;
- `Редактор`;
- `Конфликты`;
- `Загруженность аудиторий`;
- `Загруженность преподавателей`;
- `Закрепления`.
Текущий админский конструктор расписания можно переиспользовать, но его нужно отделить от справочников и адаптировать под роль учебного отдела.
## 12. Этап 8. Универсальный просмотр расписания
Сейчас `GET /api/schedule` требует ровно `groupId` или `teacherId`. Для новых требований нужен отдельный эндпоинт или расширение текущего.
Рекомендуется добавить новый эндпоинт:
```http
GET /api/schedule/search
```
Фильтры:
- `startDate`;
- `endDate`;
- `groupId`;
- `teacherId`;
- `classroomId`;
- `departmentId`;
- `subjectId`;
- `lessonTypeId`;
- `timeSlotId`;
- `parity`;
- `includeArchived`.
Правило: можно передавать один или несколько фильтров. Если фильтров нет, доступ зависит от роли:
- `ADMIN` и `EDUCATION_OFFICE` могут видеть всё;
- `DEPARTMENT` видит свою кафедру;
- `TEACHER` видит себя;
- `STUDENT` видит доступные группы или свою группу, когда появится привязка студента к группе.
Форматы отображения:
- недельная сетка;
- табличный список;
- вид по аудиториям;
- вид по преподавателям;
- вид по кафедрам.
## 13. Этап 9. Загруженность по паре, преподавателю и кафедре
### Показатели
- занятость аудитории по дате и паре;
- занятость преподавателя по дате и паре;
- часы преподавателя за период;
- часы кафедры за период;
- распределение по дисциплинам;
- распределение по типам занятий;
- свободные аудитории на выбранную дату/пару;
- перегрузки и конфликты.
### API
- `GET /api/workload/classrooms`
- `GET /api/workload/teachers`
- `GET /api/workload/departments`
- `GET /api/workload/time-slots`
- `GET /api/workload/free-classrooms`
Общие параметры:
- `startDate`;
- `endDate`;
- `date`;
- `timeSlotId`;
- `teacherId`;
- `departmentId`;
- `classroomId`;
- `groupId`;
- `lessonTypeId`.
### Реализация
На первом этапе считать загруженность из `ScheduleGeneratorService` и фильтровать уже сгенерированные пары. Если отчёты станут тяжёлыми, добавить кэш или материализованную таблицу фактических занятий на период.
Важно: после внедрения `schedule_overrides` отчёты должны считать уже итоговое расписание, а не только базовые правила.
## 14. Миграции Flyway
Существующие миграции не изменять. Новые изменения оформлять новыми файлами:
1. `V2__roles_and_lifecycle.sql`
- новые роли;
- поля жизненного цикла для справочников;
- индексы по `status`, `active_from`, `active_to`.
2. `V3__teacher_department_history.sql`
- история принадлежности преподавателей к кафедрам;
- перенос текущих `users.department_id` в первую историческую запись.
3. `V4__subject_and_teacher_temporal_links.sql`
- комментарии;
- версионированные связи преподавателей и дисциплин;
- связь преподаватель + дисциплина + тип занятия с периодом действия.
4. `V5__schedule_versions_and_overrides.sql`
- версии правил;
- точечные изменения расписания;
- закрепления.
5. `V6__workload_indexes.sql`
- индексы под отчёты и фильтры расписания.
Нумерация может измениться, если в проекте появятся другие миграции до начала реализации.
## 15. Изменения документации
После каждого этапа обновлять:
- `docs/DATABASE.md`;
- `docs/API.md`;
- `docs/BUSINESS_LOGIC.md`;
- `docs/FRONTEND.md`;
- `docs/ARCHITECTURE.md`, если меняются роли, авторизация или темпоральная модель.
Для больших этапов лучше добавлять отдельные документы:
- `docs/RBAC.md`;
- `docs/TEMPORAL_MODEL.md`;
- `docs/SCHEDULE_EDITING.md`;
- `docs/WORKLOAD_ANALYTICS.md`.
## 16. Проверка и критерии готовности
### Backend
- `docker run --rm -v /mnt/HDD/magistr/magistr/backend:/app -w /app maven:3.9-eclipse-temurin-17 mvn -q clean compile`
- Прогон Flyway на чистой PostgreSQL БД.
- Проверка, что старые данные мигрировали в темпоральные таблицы.
- Тесты или ручные проверки:
- архивная аудитория не выбирается для будущей пары;
- старая пара с архивной аудиторией отображается;
- перевод преподавателя не меняет кафедру в отчёте за прошлый период;
- правка будущего правила не меняет прошлое расписание;
- конфликт преподавателя/аудитории блокируется или предупреждается.
### Frontend
- `node --check` для изменённых JS-файлов.
- `git diff --check`.
- Ручной smoke-test в браузере:
- вход под каждой ролью;
- видимость нужных вкладок;
- запрет доступа к чужим эндпоинтам;
- создание/архивирование/восстановление справочников;
- редактирование расписания учебным отделом;
- просмотр расписания и загруженности по разным фильтрам.
## 17. Риски и вопросы, которые нужно уточнить
1. Что именно руководитель имеет в виду под "темпоральными базами данных": только история изменений или полноценная bitemporal-модель с `valid_time` и `transaction_time`.
2. Нужно ли хранить фактические проведённые занятия отдельно от планового расписания.
3. Какие форматы файлов кафедра должна "грузить": XLSX, CSV, шаблон учебного плана, свободная таблица.
4. Кто именно является пользователем роли `DEPARTMENT`: заведующий кафедрой, оператор кафедры или несколько сотрудников.
5. Нужна ли студентам привязка к конкретной группе. Сейчас студент выбирает группу вручную.
6. Должно ли удаление/архивирование кафедры запрещаться при наличии активных преподавателей, групп и дисциплин или разрешаться с каскадной архивацией.
7. Нужно ли вести журнал всех действий пользователя для юридически значимой истории изменений.
## 18. Рекомендуемый порядок внедрения
1. Роли и серверная авторизация.
2. Мягкое удаление аудиторий и справочников.
3. История переводов преподавателей.
4. Версионирование расписания и точечные изменения.
5. Проверка конфликтов.
6. Универсальный `schedule/search`.
7. Загруженность преподавателей, кафедр и пар.
8. Кабинет кафедры.
9. Кабинет учебного отдела.
10. Финальная синхронизация документации и регрессионный smoke-test.
Такой порядок закрывает базовую целостность данных до того, как появятся новые роли и экраны, которые начнут массово создавать и менять расписание.