создал систему календарного учебного графика
This commit is contained in:
@@ -1,114 +1,57 @@
|
||||
# Реализация динамического расписания
|
||||
|
||||
Этот файл фиксирует текущий результат внедрения динамической системы расписания: что уже добавлено в проект, как связаны база данных, backend, API и frontend, как работает генератор расписания и какие ограничения остаются для следующего этапа.
|
||||
Файл фиксирует текущий результат внедрения динамического расписания и календарных учебных графиков.
|
||||
|
||||
## Статус внедрения
|
||||
## Статус
|
||||
|
||||
Реализован первый рабочий срез динамического расписания. Новая модель теперь является базовой схемой проекта и создаётся сразу в `V1__init.sql`.
|
||||
Новая модель является базовой схемой проекта и создаётся в `backend/src/main/resources/db/migration/V1__init.sql`. По требованию текущей ветки новая отдельная Flyway-миграция не создавалась; применение рассчитано на полный сброс БД.
|
||||
|
||||
Старые таблицы `lessons` и `schedule_data`, старые контроллеры, DTO, модели, репозитории и админские вызовы старых API удалены. Новая модель расписания является единственным базовым способом хранения и генерации расписания.
|
||||
Старый ручной слой занятий удалён. Динамическое расписание строится из правил `schedule_rules`, слотов `schedule_rule_slots` и календарного учебного графика группы.
|
||||
|
||||
## Что добавлено
|
||||
## База данных
|
||||
|
||||
### База данных
|
||||
|
||||
Динамическая схема встроена в базовую Flyway-миграцию `V1__init.sql`.
|
||||
|
||||
Она создаёт следующие таблицы:
|
||||
`V1__init.sql` создаёт и заполняет:
|
||||
|
||||
| Таблица | Назначение |
|
||||
|---------|------------|
|
||||
| `time_slots` | Настраиваемая сетка пар: номер, время начала, время окончания, длительность |
|
||||
| `academic_years` | Учебные годы |
|
||||
| `semesters` | Семестры учебного года, от `start_date` считается первая неделя |
|
||||
| `holidays` | Праздники и исключённые даты, когда занятия не проводятся |
|
||||
| `academic_calendar_matrix` | Матрица учебного графика по семестру, курсу, специальности и неделе |
|
||||
| `schedule_rules` | Правила проведения дисциплины с лимитом академических часов |
|
||||
| `schedule_rule_groups` | Привязка правила к одной или нескольким группам |
|
||||
| `schedule_rule_slots` | Конкретные шаблонные слоты правила: день, чётность, пара, преподаватель, аудитория, тип и формат |
|
||||
| `specialties` | Коды и названия специальностей |
|
||||
| `specialty_profiles` | Профили обучения внутри специальности |
|
||||
| `student_groups` | Группы со ссылками на специальность и профиль |
|
||||
| `calendar_study_forms` | Формы обучения календарного графика |
|
||||
| `academic_calendar_activity_types` | Коды активностей из Excel-графика |
|
||||
| `academic_calendars` | Календарные графики профиля, формы обучения и учебного года |
|
||||
| `academic_calendar_days` | Дневная сетка графика по курсу и дате |
|
||||
| `student_group_calendar_assignments` | Назначения графиков группам по учебному году |
|
||||
| `time_slots` | Настраиваемая сетка пар |
|
||||
| `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
|
||||
|
||||
### 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` | Генерация расписания группы или преподавателя на диапазон дат |
|
||||
| `AcademicDateService` | Семестр по дате, номер недели, чётность, курс группы, активность дня по назначенному графику |
|
||||
| `ScheduleGeneratorService` | Рендер расписания группы или преподавателя на диапазон дат |
|
||||
|
||||
Старый ручной слой занятий удалён: в backend больше нет `LessonsController`, `ScheduleDataController`, сущностей `Lesson`/`ScheduleData`, их DTO, репозиториев и валидаторов старых строк расписания.
|
||||
`ScheduleGeneratorService` пропускает день, если код активности не разрешает обычные пары. Если у группы нет назначенного графика на учебный год, расписание этой группы возвращается пустым списком.
|
||||
|
||||
### API
|
||||
## 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 | Назначение |
|
||||
|-----|------------|
|
||||
@@ -116,196 +59,48 @@ GET /api/schedule?teacherId=2&startDate=2026-04-27&endDate=2026-05-03
|
||||
| `/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/calendar/activity-types` | CRUD кодов активностей |
|
||||
| `/api/admin/academic-calendars` | CRUD календарных учебных графиков |
|
||||
| `/api/admin/academic-calendars/{id}/grid` | Получение и сохранение дневной сетки |
|
||||
| `/api/specialties/{id}/profiles` | CRUD профилей специальности |
|
||||
| `/api/groups/{id}/calendar-assignments` | Назначения графиков группе |
|
||||
| `/api/admin/schedule-rules` | CRUD правил расписания |
|
||||
|
||||
### Авторизация
|
||||
## Frontend
|
||||
|
||||
Ответ `POST /api/auth/login` расширен полем `userId`.
|
||||
Админский экран `schedule` содержит:
|
||||
|
||||
Frontend сохраняет его в `localStorage`, чтобы кабинет преподавателя мог запрашивать личное расписание через:
|
||||
- конструктор правил динамического расписания;
|
||||
- управление временными слотами;
|
||||
- учебные годы и семестры;
|
||||
- CRUD календарных графиков;
|
||||
- Excel-подобный редактор дневной сетки по курсам, датам и кодам активностей.
|
||||
|
||||
```http
|
||||
GET /api/schedule?teacherId={userId}&startDate=...&endDate=...
|
||||
```
|
||||
Экран `groups` создаёт и редактирует группы с профилем обучения, а также назначает календарный график группе на учебный год.
|
||||
|
||||
### Frontend
|
||||
Экран `departments-data` управляет кафедрами, специальностями и профилями специальностей.
|
||||
|
||||
Заглушки кабинетов заменены на базовые экраны просмотра расписания.
|
||||
## Проверки
|
||||
|
||||
Админский экран `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
|
||||
node --check frontend/admin/js/views/schedule.js
|
||||
node --check frontend/admin/js/views/groups.js
|
||||
node --check frontend/admin/js/views/departments-data.js
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Результат: замечаний по whitespace нет.
|
||||
Полная проверка требует окружения с Maven и доступом к Docker:
|
||||
|
||||
## Ограничения текущего среза
|
||||
```bash
|
||||
mvn -DskipTests compile
|
||||
docker compose down -v
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Этот срез создаёт рабочее ядро динамического расписания, но ещё не закрывает весь продуктовый объём.
|
||||
## Следующие этапы
|
||||
|
||||
Ограничения:
|
||||
|
||||
- строгая проверка конфликтов преподавателей, аудиторий и групп при сохранении правил ещё не реализована;
|
||||
- студент пока выбирает группу вручную, потому что в модели пользователя нет связи `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-архитектура.
|
||||
- Добавить интеграционные тесты генерации по кодам `Т`, `Э`, `К`, `*`, `=`.
|
||||
- Добавить строгую backend-валидацию конфликтов преподавателей, аудиторий и групп при сохранении правил.
|
||||
- Добавить связь пользователя-студента с учебной группой, чтобы кабинет студента не требовал ручного выбора группы.
|
||||
|
||||
Reference in New Issue
Block a user