# Реализация динамического расписания Этот файл фиксирует текущий результат внедрения динамической системы расписания: что уже добавлено в проект, как связаны база данных, backend, API и frontend, как работает генератор расписания и какие ограничения остаются для следующего этапа. ## Статус внедрения Реализован первый рабочий срез динамического расписания. Новая модель теперь является базовой схемой проекта и создаётся сразу в `V1__init.sql`. Старые таблицы `lessons` и `schedule_data`, старые контроллеры, DTO, модели, репозитории и админские вызовы старых API удалены. Новая модель расписания является единственным базовым способом хранения и генерации расписания. ## Что добавлено ### База данных Динамическая схема встроена в базовую Flyway-миграцию `V1__init.sql`. Она создаёт следующие таблицы: | Таблица | Назначение | |---------|------------| | `time_slots` | Настраиваемая сетка пар: номер, время начала, время окончания, длительность | | `academic_years` | Учебные годы | | `semesters` | Семестры учебного года, от `start_date` считается первая неделя | | `holidays` | Праздники и исключённые даты, когда занятия не проводятся | | `academic_calendar_matrix` | Матрица учебного графика по семестру, курсу, специальности и неделе | | `schedule_rules` | Правила проведения дисциплины с лимитом академических часов | | `schedule_rule_groups` | Привязка правила к одной или нескольким группам | | `schedule_rule_slots` | Конкретные шаблонные слоты правила: день, чётность, пара, преподаватель, аудитория, тип и формат | Миграция также создаёт тестовые данные сразу в новой модели: 1. Создаёт базовые временные слоты. 2. Создаёт учебные годы `2024-2025` и `2025-2026`. 3. Заполняет матрицу учебного графика базовым статусом `THEORY`. 4. Создаёт тестовые `schedule_rules` для групп и дисциплин. 5. Создаёт привязки групп в `schedule_rule_groups`, включая потоковую лекцию. 6. Создаёт шаблонные занятия в `schedule_rule_slots`. ### Backend Добавлены JPA-модели для новой схемы: - `TimeSlot` - `AcademicYear` - `Semester` - `Holiday` - `AcademicCalendarMatrix` - `ScheduleRule` - `ScheduleRuleSlot` - `AcademicActivityType` - `ScheduleParity` Добавлены DTO для API: - `TimeSlotDto` - `AcademicYearDto` - `SemesterDto` - `HolidayDto` - `AcademicCalendarMatrixDto` - `ScheduleRuleDto` - `ScheduleRuleSlotDto` - `RenderedLessonDto` Добавлены репозитории для новых таблиц: - `TimeSlotRepository` - `AcademicYearRepository` - `SemesterRepository` - `HolidayRepository` - `AcademicCalendarMatrixRepository` - `ScheduleRuleRepository` - `ScheduleRuleSlotRepository` Добавлены сервисы: | Сервис | Назначение | |--------|------------| | `AcademicDateService` | Календарная математика: семестр по дате, номер недели, чётность, праздник, курс группы, тип активности | | `ScheduleGeneratorService` | Генерация расписания группы или преподавателя на диапазон дат | Старый ручной слой занятий удалён: в backend больше нет `LessonsController`, `ScheduleDataController`, сущностей `Lesson`/`ScheduleData`, их DTO, репозиториев и валидаторов старых строк расписания. ### API Добавлен новый endpoint просмотра расписания: ```http GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03 GET /api/schedule?teacherId=2&startDate=2026-04-27&endDate=2026-05-03 ``` Правило запроса: нужно передать ровно один идентификатор — `groupId` или `teacherId`. Ответ — массив `RenderedLessonDto`, где у каждого занятия есть: - полная дата занятия; - номер дня недели; - название дня; - номер недели семестра; - чётность; - временной слот; - дисциплина; - преподаватель; - аудитория; - тип и формат занятия; - группы; - лимит часов правила; - уже списанные часы перед занятием; - остаток часов после занятия. Добавлены административные API: | API | Назначение | |-----|------------| | `/api/admin/time-slots` | CRUD временных слотов | | `/api/admin/calendar/years` | CRUD учебных годов | | `/api/admin/calendar/years/{academicYearId}/semesters` | Семестры учебного года | | `/api/admin/calendar/semesters/{id}` | Обновление семестра | | `/api/admin/calendar/holidays` | CRUD праздников | | `/api/admin/calendar/matrix` | Получение и массовое сохранение матрицы учебного графика | | `/api/admin/schedule-rules` | CRUD правил расписания | ### Авторизация Ответ `POST /api/auth/login` расширен полем `userId`. Frontend сохраняет его в `localStorage`, чтобы кабинет преподавателя мог запрашивать личное расписание через: ```http GET /api/schedule?teacherId={userId}&startDate=...&endDate=... ``` ### Frontend Заглушки кабинетов заменены на базовые экраны просмотра расписания. Админский экран `schedule` показывает текущие `schedule_rules` и сетку `time_slots` через новые admin API. #### Кабинет студента Файл: `frontend/student/index.html`. Текущая модель пользователя-студента не содержит прямой связи с учебной группой, поэтому экран работает через выбор группы: 1. Загружает список групп из `/api/groups`. 2. Сохраняет выбранную группу в `localStorage.studentGroupId`. 3. Строит недельный диапазон дат. 4. Запрашивает расписание через `GET /api/schedule?groupId=...`. 5. Показывает занятия по дням недели. #### Кабинет преподавателя Файл: `frontend/teacher/index.html`. Экран использует `localStorage.userId`, который появляется после нового ответа авторизации: 1. Определяет текущую неделю. 2. Позволяет переключать неделю стрелками или через выбор даты. 3. Запрашивает расписание через `GET /api/schedule?teacherId=...`. 4. Показывает занятия преподавателя в недельной сетке. 5. В занятии отображаются дисциплина, время, тип, аудитория и группы. ## Как работает генерация расписания Генерация выполняется на backend по запросу клиента. Фактические занятия не хранятся отдельными строками на каждую дату, а вычисляются из правил. ### Расписание группы Алгоритм `buildScheduleForGroup(groupId, startDate, endDate)`: 1. Проверяет корректность диапазона дат. 2. Находит группу. 3. Для каждой даты диапазона определяет семестр. 4. Вычисляет номер недели семестра. 5. Вычисляет текущий курс группы по `year_start_study`. 6. Проверяет матрицу учебного графика. 7. Пропускает дату, если это праздник или не `THEORY`. 8. Загружает правила группы для найденного семестра. 9. Для каждого правила считает, сколько академических часов уже проведено до текущей даты. 10. Если лимит `total_academic_hours` ещё не исчерпан, проецирует подходящие слоты правила на текущую дату. ### Расписание преподавателя Алгоритм `buildScheduleForTeacher(teacherId, startDate, endDate)` похож, но правила ищутся не по группе, а по преподавателю в `schedule_rule_slots.teacher_id`. В ответе занятие дополнительно содержит все группы, привязанные к правилу. Это поддерживает потоковые лекции. ### Чётность недели Чётность вычисляется от `semesters.start_date`. Настройка: ```properties app.schedule.even-week-is-upper=false ``` По умолчанию: - нечётная неделя семестра соответствует `ODD`; - чётная неделя семестра соответствует `EVEN`. Если `app.schedule.even-week-is-upper=true`, соответствие инвертируется. ### Праздники Праздник считается пропуском занятия: - занятие не отображается в расписании; - академические часы не списываются; - дисциплина фактически продолжается дольше, пока не будет исчерпан лимит часов. ### Лимит часов Каждый `schedule_rules.total_academic_hours` задаёт общий лимит академических часов дисциплины. Генератор симулирует период от `active_from_date` до запрошенной даты и считает только реально проведённые слоты: - один слот = 2 академических часа; - праздники не считаются; - недели с активностью не `THEORY` не считаются; - после достижения лимита правило перестаёт выводиться. ## Поток данных Новая базовая БД сразу создаёт рабочую модель так: ```text V1__init.sql -> time_slots -> academic_years / semesters / holidays / academic_calendar_matrix -> schedule_rules -> schedule_rule_groups -> schedule_rule_slots ``` Для просмотра: ```text frontend -> GET /api/schedule -> ScheduleController -> ScheduleGeneratorService -> schedule_rules + schedule_rule_slots + calendar tables -> RenderedLessonDto[] ``` ## Что проверено Проверка backend: ```bash docker run --rm -v /mnt/HDD/magistr/magistr/backend:/app -w /app maven:3.9-eclipse-temurin-17 mvn -q -DskipTests compile ``` Результат: компиляция backend прошла успешно. Проверка миграции: 1. Поднят временный PostgreSQL-контейнер на образе `postgres:alpine3.23`. 2. Применён `V1__init.sql`. 3. Проверены контрольные количества строк новой модели. Контрольный результат: | Объект | Количество | |--------|------------| | `schedule_rules` | 4 | | `schedule_rule_groups` | 5 | | `schedule_rule_slots` | 4 | | `time_slots` | 7 | | `academic_calendar_matrix` | 132 | | `lessons` | таблица отсутствует | | `schedule_data` | таблица отсутствует | Также выполнен: ```bash git diff --check ``` Результат: замечаний по whitespace нет. ## Ограничения текущего среза Этот срез создаёт рабочее ядро динамического расписания, но ещё не закрывает весь продуктовый объём. Ограничения: - строгая проверка конфликтов преподавателей, аудиторий и групп при сохранении правил ещё не реализована; - студент пока выбирает группу вручную, потому что в модели пользователя нет связи `student -> group`. ## Следующий этап Рекомендуемый следующий этап: 1. Реализовать backend-валидацию конфликтов: - преподаватель не может вести разные занятия одновременно; - аудитория не может быть занята двумя разными занятиями одновременно; - группа не может быть на двух занятиях одновременно, кроме корректного деления по подгруппам. 2. Добавить модель связи студента с учебной группой, чтобы кабинет студента не требовал ручного выбора группы. 3. Добавить отдельное управление подгруппами и подключить выбор `subgroupId` в конструкторе правил. ## Связанные документы - `SCHEDULE_PROPOSAL.md` — исходная концепция динамического расписания. - `SCHEDULE_TASKS.md` — исходная декомпозиция задач. - `docs/API.md` — описание REST API. - `docs/DATABASE.md` — описание схемы БД. - `docs/BUSINESS_LOGIC.md` — бизнес-правила. - `docs/FRONTEND.md` — frontend-архитектура.