поправил документ описывающий концепцияю динамической генерации расписания
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
Данный документ представляет собой подробное архитектурное описание новой системы управления расписанием. Система переходит от статического хранения каждой отдельной пары к параметрическому: мы сохраняем **правила проведения** дисциплины и **календарную сетку**, а фактическое расписание на любую дату вычисляется «на лету» (генерируется).
|
||||
|
||||
> **Контекст миграции:** Новая система полностью заменяет существующие таблицы `lessons` (статическое расписание) и `schedule_data` (плановая нагрузка). Обе таблицы будут мигрированы в единую модель `schedule_rules` + `schedule_rule_slots`, которая совмещает хранение нагрузки (часы) и расписания (слоты) в одной структуре.
|
||||
|
||||
---
|
||||
|
||||
## 1. Подробное описание компонентов системы
|
||||
@@ -9,98 +11,168 @@
|
||||
Новая архитектура строится на строгом разделении данных на три логических слоя: Календарь (основа отсчета времени), Правила (шаблоны занятий) и Генератор (движок рендеринга фактического расписания).
|
||||
|
||||
### 1.1 Справочная база времени (Календарный учебный график)
|
||||
Чтобы система понимала, *когда* можно ставить пары, а когда нет, вводится понятие календарного графика. Он состоит из трех взаимосвязанных сущностей:
|
||||
* **Академические периоды (Учебные года и Семестры).** Мы задаем жесткую дату начала каждого семестра и года (например, `02.09.2024`). Именно от этой точки отсчитывается "Неделя 1", которая по умолчанию классифицируется как первая нечетная (верхняя). Это избавляет систему от уязвимостей, связанных с плавающими днями начала учебы, високосными годами и смещениями дней недели.
|
||||
* **Справочник исключений (Праздники и Выходные).** В этой таблице хранятся конкретные даты `YYYY-MM-DD`, когда университет юридически или физически закрыт (например, государственные праздники). Если по правилу пара должна быть в этот день, алгоритм будет знать, что её нужно пропустить без штрафов и ошибок.
|
||||
* **Матрица учебного графика.** Это цифровая копия эксель-таблицы (Курс/Специальность -> Номер недели -> Тип деятельности). Типы могут включать `THEORY` (Теория, пары идут в штатном режиме), `EXAM` (Э - экзаменационная сессия), `VACATION` (К - каникулы), `PRACTICE` (У, П - практика). Если, например, у 3-го курса на 18-й неделе стоит статус `EXAM`, алгоритм даже не будет пытаться генерировать для них теоретические лекции, а отобразит блок «Экзаменационная сессия».
|
||||
Чтобы система понимала, *когда* можно ставить пары, а когда нет, вводится понятие календарного графика. Он состоит из трёх взаимосвязанных сущностей:
|
||||
|
||||
### 1.2 Движок правил (Schedule Rules)
|
||||
Старый подход подразумевал, что каждая пара в базе (каждая клеточка) это изолированная запись `lessons` ("понедельник, 1-я пара, математика"). Новая система вводит сущность сводного **Правила Дисциплины**. Одно правило описывает расписание целого курса по конкретному предмету для конкретной студенческой группы.
|
||||
* **Академические периоды (Учебные года и Семестры).** Иерархия из двух уровней:
|
||||
* **Учебный год** — контейнер с названием и датами (напр. «2024/2025», `01.09.2024` — `30.06.2025`).
|
||||
* **Семестр** — дочерняя сущность учебного года. Содержит дату начала, от которой отсчитывается «Неделя 1» данного семестра. Нумерация недель начинается заново для каждого семестра. Тип семестра (`autumn` / `spring`) определяет, какой набор правил активен.
|
||||
|
||||
Именно от даты начала семестра отсчитывается «Неделя 1». Конвенция чётности (верхняя = чётная или нечётная) **настраивается на уровне тенанта**, так как у разных университетов разные традиции. Это избавляет систему от уязвимостей, связанных с плавающими днями начала учёбы, високосными годами и смещениями дней недели.
|
||||
|
||||
* **Справочник исключений (Праздники и Выходные).** В этой таблице хранятся конкретные даты `YYYY-MM-DD`, когда университет юридически или физически закрыт (например, государственные праздники). Если по правилу пара должна быть в этот день, алгоритм будет знать, что его нужно пропустить без штрафов и ошибок.
|
||||
|
||||
* **Матрица учебного графика.** Это цифровая копия эксель-таблицы (`Курс + Специальность` → `Номер недели` → `Тип деятельности`). Привязка идёт к `course_number` + `specialty_id`, а **не** к конкретной группе, так как учебный график одинаков для всех групп одного курса одной специальности. Номер текущего курса группы вычисляется из поля `year_start_study` модели `StudentGroup` относительно текущей даты по формуле: `course = текущий_учебный_год - year_start_study + 1`. Типы деятельности включают `THEORY` (Теория, пары идут в штатном режиме), `EXAM` (Э — экзаменационная сессия), `VACATION` (К — каникулы), `PRACTICE` (У, П — практика). Если, например, у 3-го курса на 18-й неделе стоит статус `EXAM`, алгоритм даже не будет пытаться генерировать для них теоретические лекции, а отобразит блок «Экзаменационная сессия».
|
||||
|
||||
### 1.2 Справочник временных слотов (Time Slots)
|
||||
Вместо хардкода фиксированных 7 пар, система хранит временные слоты в отдельной **настраиваемой таблице**. Каждый тенант (университет) может иметь собственное количество пар, их длительность и временные рамки.
|
||||
|
||||
Слот содержит:
|
||||
* `order_number` — порядковый номер пары в дне (1, 2, 3...).
|
||||
* `start_time` — время начала (напр. `08:00`).
|
||||
* `end_time` — время окончания (напр. `09:30`).
|
||||
* `duration_minutes` — длительность пары в минутах.
|
||||
|
||||
Это позволяет каждому университету настраивать количество и продолжительность пар без модификации кода.
|
||||
|
||||
### 1.3 Движок правил (Schedule Rules)
|
||||
Старый подход подразумевал, что каждая пара в базе (каждая клеточка) — это изолированная запись `lessons` («понедельник, 1-я пара, математика»). Новая система вводит сущность сводного **Правила Дисциплины**. Одно правило описывает расписание целого курса по конкретному предмету для одной или нескольких студенческих групп (включая потоковые лекции).
|
||||
|
||||
**Базовые параметры (Лимиты Правила):**
|
||||
* `subject_id` — ID преподаваемой дисциплины.
|
||||
* `group_id` — ID группы (или потока).
|
||||
* `startDate` — Дата или номер недели, с которой предмет начинает читаться (поскольку не все предметы идут строго с 1-й недели семестра).
|
||||
* `totalHours` — Полный объем выделенных академических часов. Это важнейший **лимитатор**, который обеспечивает автоматическую остановку генерации: как только заявленные часы будут вычитаны, предмет перестает отображаться в расписании студентов на последующих неделях.
|
||||
* `semester_id` — ID семестра, к которому привязано правило. Одна и та же дисциплина может читаться в разных семестрах с разными параметрами.
|
||||
* `startDate` — Дата или номер недели семестра, с которой предмет начинает читаться (поскольку не все предметы идут строго с 1-й недели семестра).
|
||||
* `totalHours` — Полный объём выделенных **академических часов** (1 ак. час = 45 минут; одна пара = 2 ак. часа). Это важнейший **лимитатор**, который обеспечивает автоматическую остановку генерации: как только заявленные часы будут вычитаны, предмет перестает отображаться в расписании студентов на последующих неделях.
|
||||
|
||||
**Связь с группами (Many-to-Many):**
|
||||
Одно правило может быть связано с несколькими группами через промежуточную таблицу `schedule_rule_groups`. Это обеспечивает поддержку **потоковых лекций** — когда один преподаватель читает лекцию нескольким группам одновременно в одной аудитории. При этом правило создаётся один раз, а группы к нему привязываются списком.
|
||||
|
||||
**Массив паттернов (Слоты правила):**
|
||||
Само "тело" правила разбивается на подчиненные слоты. Если предмет идет в Пн и Ср, это будет 2 слота внутри одного Правила. Слот содержит:
|
||||
* `dayOfWeek`: день недели (1-7, Пн-Вс).
|
||||
* `parity`: тип четности оборачивания (1 - каждую неделю, 2 - по четным/нижняя, 3 - по нечетным/верхняя).
|
||||
* `lessonOrder`: порядковый номер пары в дне (1, 2, 3 и т.д.).
|
||||
* `subgroup_id`: флаг подгруппы (Вся группа / Подгруппа A / Подгруппа B). *Это гарантирует, что мы сможем ставить разным подгруппам пересекающиеся занятия в разных аудиториях без алгоритмических конфликтов.*
|
||||
Само «тело» правила разбивается на подчинённые слоты. Если предмет идёт в Пн и Ср, это будет 2 слота внутри одного Правила. Слот содержит:
|
||||
* `dayOfWeek`: день недели (1–7, Пн–Вс).
|
||||
* `parity`: тип четности — `ENUM('BOTH', 'EVEN', 'ODD')`. `BOTH` — каждую неделю, `EVEN` — по чётным (нижним) неделям, `ODD` — по нечётным (верхним). Конкретное соответствие «чётная = верхняя или нижняя» определяется настройкой тенанта.
|
||||
* `time_slot_id`: FK на таблицу `time_slots` — порядковый номер и время пары.
|
||||
* `subgroup_id`: FK на подгруппу (NULL = вся группа). *Это гарантирует, что мы сможем ставить разным подгруппам пересекающиеся занятия в разных аудиториях без алгоритмических конфликтов.*
|
||||
* `teacher_id`: FK на преподавателя слота.
|
||||
* `classroom_id`: FK на аудиторию слота.
|
||||
* `lesson_type_id`: FK на тип занятия (`Лекция`, `Практическая работа`, `Лабораторная работа`).
|
||||
* `lesson_format`: формат проведения (`Очно` / `Онлайн`).
|
||||
|
||||
### 1.3 Генератор (Рендерер) расписания
|
||||
Это слой бизнес-логики (служба `ScheduleGeneratorService` в Java), который работает исключительно в оперативной памяти бэкенда и производит расчет расписания "on-demand" (по требованию) при запросе от клиента фронтенда.
|
||||
> **Обоснование:** Хранение `teacher_id`, `classroom_id`, `lesson_type_id` и `lesson_format` в **слотах**, а не в главном правиле, позволяет гибко описывать ситуации вроде: лекции в понедельник читает лектор Иванов (Аудитория 100), а лабораторные в среду ведёт практик Петров (Аудитория 102В) — в рамках одного правила по предмету «Программирование», расходуя общий `totalHours`.
|
||||
|
||||
### 1.4 Генератор (Рендерер) расписания
|
||||
Это слой бизнес-логики (служба `ScheduleGeneratorService` в Java), который работает исключительно в оперативной памяти бэкенда и производит расчёт расписания «on-demand» (по требованию) при запросе от клиента фронтенда.
|
||||
|
||||
**Пошаговый алгоритм работы генератора:**
|
||||
1. Фронтенд (Интерфейс пользователя) запрашивает: *"Дай мне расписание группы ИТ-21 на конкретный период, например, с 14 октября по 20 октября"*.
|
||||
2. Генератор вычисляет, что 14 октября соответствует, к примеру, 7-й неделе семестра (вычисление от `startDate` календаря).
|
||||
3. Он сверяется с *Матрицей учебного графика*. Если у ИТ-21 сейчас стоит `VACATION` (Каникулы) или `PRACTICE` (Практика, выходящая за пределы ВУЗа), он сразу возвращает пустой ответ или ответ со статусом периода.
|
||||
4. Если статус недели позволяет проводить занятия (`THEORY`), генератор поднимает из Базы Данных все активные **Правила** для группы ИТ-21.
|
||||
5. **Механика Лимитатора часов:** Для каждого правила алгоритм "симулирует" или "проматывает" прогон времени с даты старта этого правила до текущей запрошенной недели. Он вычитает успешно проведенные часы, игнорируя даты, попавшие в справочник праздников.
|
||||
6. Если у предмета лимит `totalHours` достиг значения `0`, программа понимает, что курс вычитан, и предмет не отображается. Если часы еще остались, алгоритм проецирует шаблоны (слоты правила) на запрошенную текущую неделю с учетом четности, аудиторий и подгрупп, отдавая готовый JSON массив в браузер пользователя.
|
||||
1. Фронтенд (Интерфейс пользователя) запрашивает: *«Дай мне расписание группы ИТ-21 на конкретный период, например, с 14 октября по 20 октября»*.
|
||||
2. Генератор определяет семестр по запрошенным датам и вычисляет, что 14 октября соответствует, к примеру, 7-й неделе семестра (вычисление от `startDate` семестра).
|
||||
3. Он сверяется с *Матрицей учебного графика*. Для этого генератор определяет текущий курс группы по формуле `текущий_учебный_год - year_start_study + 1` и находит `specialty_id` группы. Если у данного курса/специальности сейчас стоит `VACATION` (Каникулы) или `PRACTICE` (Практика), генератор сразу возвращает пустой ответ или ответ со статусом периода.
|
||||
4. Если статус недели позволяет проводить занятия (`THEORY`), генератор поднимает из Базы Данных все активные **Правила** для запрошенной группы (через таблицу `schedule_rule_groups`), привязанные к текущему семестру.
|
||||
5. **Механика Лимитатора часов:** Для каждого правила алгоритм «симулирует» прогон времени с даты старта правила до текущей запрошенной недели. Он подсчитывает количество успешно проведённых ак. часов (по 2 ак. часа за каждый отработанный слот), пропуская даты, попавшие в справочник праздников, и недели с типом деятельности отличным от `THEORY`.
|
||||
6. Если у правила лимит `totalHours` достиг значения `0`, программа понимает, что курс вычитан, и предмет не отображается. Если часы ещё остались, алгоритм проецирует шаблоны (слоты правила) на запрошенную текущую неделю с учётом чётности, аудиторий и подгрупп, отдавая готовый JSON-массив в браузер пользователя.
|
||||
|
||||
**Генерация расписания для преподавателя:**
|
||||
Аналогричный алгоритм, но поиск правил идёт не по привязке к группе, а по `teacher_id` в слотах. Генератор собирает все `schedule_rule_slots`, где `teacher_id` = ID текущего преподавателя, получает родительские правила и рендерит расписание, обогащая каждую запись списком групп из `schedule_rule_groups`.
|
||||
|
||||
**Кеширование:**
|
||||
Для оптимизации производительности (т.к. симуляция прогона за весь семестр для каждого запроса ресурсоёмка) предусмотрен кеш:
|
||||
* Список праздников текущего учебного года кешируется при первом обращении и инвалидируется при изменении таблицы `holidays`.
|
||||
* Матрица учебного графика кешируется по ключу `(course, specialty_id, semester_id)`.
|
||||
* Результаты подсчёта `consumed_hours` для каждого правила могут кешироваться с инвалидацией при изменении праздников или правил.
|
||||
|
||||
---
|
||||
|
||||
## 2. Архитектурные Решения и Открытые Вопросы
|
||||
## 2. Архитектурные Решения
|
||||
|
||||
На основе обсуждений были задокументированы следующие концептуальные решения по архитектуре:
|
||||
|
||||
### Утвержденные решения
|
||||
1. **Реакция на праздники (Динамическое вытеснение / Сдвиг):**
|
||||
*Решение:* **Алгоритм должен переносить пару, пара в праздничный день не сгорает.**
|
||||
Алгоритм будет воспринимать праздник просто как "пропуск хода", не отнимая проведенные часы от `totalHours`. Соответственно, предмет будет автоматически забирать время со смещением в будущее (продлевая дату окончания вычитки), пока преподаватель честно не выработает положенный объем часов.
|
||||
2. **Массивы vs Связанные таблицы (Использование строгой Нормализации БД):**
|
||||
*Решение:* **Мы делаем нормализацию через связанные таблицы в PostgreSQL, так как это правильнее с архитектурной точки зрения.**
|
||||
Мы не будем использовать сырые массивы (типа `INTEGER[]`) или JSONB колонки внутри одной строки с правилом. Будет реализована структура отношения "One-to-Many":
|
||||
* Главная таблица: `schedule_rules` (хранит лимиты и даты старта).
|
||||
* Подчиненная таблица: `schedule_rule_slots` (хранит конкретный день, четность, номер пары, прикрепленные к ID главного правила через Foreign Key).
|
||||
Это позволит базе данных легко и быстро строить сложные выборки в стиле "Покажи загруженность кабинета №21 во вторник на второй паре по четным неделям", исключая тяжелый парсинг JSON.
|
||||
3. **Поддержка Подгрупп внутри слотов:**
|
||||
*Решение:* **Модель обязательно должна учитывать деление на подгруппы.**
|
||||
В таблицы параметров слотов (`schedule_rule_slots`) и в бизнес-логику генератора будет введено поле `subgroup_id` (Id подгруппы). Алгоритм генератора сможет рендерить два предмета для одной группы одновременно и без конфликтов, если они ассоциированы с разными подгруппами одной материнской группы.
|
||||
1. **Реакция на праздники (Продление курса):**
|
||||
Алгоритм воспринимает праздник как «пропуск хода», не отнимая проведённые часы от `totalHours`. Это означает, что пара **не переносится** на другой день или время — она просто пропускается без вычета часов. Фактически предмет будет отображаться в расписании дольше (больше недель), пока `totalHours` не будет полностью исчерпан. Преподаватель честно выработает положенный объём часов за счёт увеличения количества недель преподавания.
|
||||
|
||||
### Открытые вопросы (Требуют обсуждения с коллегами)
|
||||
1. **Смена аудиторий и преподавателей в рамках одного Правила:**
|
||||
*Вопрос:* Как хранить в базе ситуацию, когда у предмета "Программирование" лекции в понедельник читает лектор Иванов (Аудитория 100), а лабораторные в среду ведет практик Петров (Аудитория 102В)?
|
||||
* **Вариант А (независимые правила):** Считать их абсолютно разными независимыми Правилами. Одно правило — на лекции Иванова (со своим собственным лимитом лекционных часов `totalHours`), другое правило — на лабораторные Петрова (со своим лимитом практических часов). У каждого — свои слоты.
|
||||
* **Вариант Б (слоты с обогащенным контекстом):** Считать это одним большим Правилом по предмету "Программирование", но при этом хранить `teacher_id`, `lesson_format` и `classroom_id` не в главной строке Правила, а в каждой строке параметрических Слотов (`schedule_rule_slots`). Таким образом расходуя общий `totalHours` дисциплины.
|
||||
*Статус:* **Открытый архитектурный вопрос. Решение будет принято после обсуждения.**
|
||||
2. **Нормализация через связанные таблицы:**
|
||||
Мы не используем сырые массивы (`INTEGER[]`) или JSONB-колонки. Реализована структура со строгой нормализацией:
|
||||
* Главная таблица: `schedule_rules` (хранит лимиты и дату старта).
|
||||
* Подчинённая таблица: `schedule_rule_slots` (хранит конкретный день, чётность, номер пары, преподавателя, аудиторию, тип и формат — прикреплённые к ID главного правила через Foreign Key).
|
||||
* Связующая таблица: `schedule_rule_groups` (Many-to-Many между правилом и группами).
|
||||
Это позволяет базе данных строить сложные выборки в стиле «Покажи загруженность кабинета №21 во вторник на второй паре по чётным неделям», исключая тяжёлый парсинг JSON.
|
||||
|
||||
3. **Поддержка подгрупп внутри слотов:**
|
||||
В таблицу `schedule_rule_slots` введено поле `subgroup_id` (Id подгруппы, nullable). Алгоритм генератора сможет рендерить два предмета для одной группы одновременно и без конфликтов, если они ассоциированы с разными подгруппами одной материнской группы.
|
||||
|
||||
4. **Обогащённые слоты (Вариант Б):**
|
||||
`teacher_id`, `classroom_id`, `lesson_type_id` и `lesson_format` хранятся в каждой строке `schedule_rule_slots`, а не в главном правиле. Это позволяет описывать лекции и практики одного предмета в рамках одного правила, расходуя общий `totalHours`.
|
||||
|
||||
5. **Потоковые лекции через Many-to-Many:**
|
||||
Одно правило связывается с несколькими группами через `schedule_rule_groups`. Для потоковой лекции создаётся одно правило, к которому привязываются все участвующие группы.
|
||||
|
||||
6. **Настраиваемость по тенантам:**
|
||||
Архитектурно все тенанты одинаковы — каждый университет получает идентичную пустую базу данных. Временные слоты (количество, длительность, время начала/окончания пар), конвенция чётности и прочие параметры не требуют специального механизма: каждый университет просто заполняет свою БД самостоятельно через панель администратора.
|
||||
|
||||
---
|
||||
|
||||
## 3. Подробный План Действий по Реализации
|
||||
|
||||
Интеграция новой архитектуры затронет весь стек приложения (DB -> Backend -> API -> Frontend). Работу предлагается вести строго поэтапно:
|
||||
Интеграция новой архитектуры затронет весь стек приложения (DB → Backend → API → Frontend). Работу предлагается вести строго поэтапно:
|
||||
|
||||
### Этап 1. База Данных (Flyway Миграции и Дата-Скрипты)
|
||||
* **Схема Календарного графика:**
|
||||
* `academic_years` (id, title, start_date, end_date).
|
||||
* `holidays` (id, date, academic_year_id, description).
|
||||
* `academic_calendar_matrix` (id, academic_year_id, target_group_id, week_number, activity_type_enum).
|
||||
* **Схема Движка Правил:**
|
||||
* `schedule_rules` (id, subject_id, target_group_id, active_from_date, total_academic_hours).
|
||||
* `schedule_rule_slots` (id, schedule_rule_id, day_of_week, parity_type, lesson_order_id, subgroup_id, [аудитория_id?, преподаватель_id? *зависит от решения открытого вопроса*]).
|
||||
* **Скрипт Миграции (Data ETL):** Написание SQL/Java скрипта, который вычитает "старые" статические записи из таблицы `lessons`, скомпонует их по логическим правилам и перенесет в новые таблицы `schedule_rules`, чтобы не потерять текущую информацию. Только после этого можно удалять старую таблицу `lessons`.
|
||||
### Этап 1. База Данных (Flyway Миграции)
|
||||
|
||||
**Схема Временных слотов:**
|
||||
* `time_slots` (id, order_number, start_time TIME, end_time TIME, duration_minutes INT).
|
||||
* Заполняется администратором. Нет фиксированных значений — каждый тенант настраивает свою сетку пар.
|
||||
|
||||
**Схема Календарного графика:**
|
||||
* `academic_years` (id, title VARCHAR, start_date DATE, end_date DATE).
|
||||
* `semesters` (id, academic_year_id FK, semester_type ENUM('autumn','spring'), start_date DATE, end_date DATE).
|
||||
* Именно от `semesters.start_date` отсчитывается «Неделя 1».
|
||||
* `holidays` (id, date DATE, academic_year_id FK, description VARCHAR).
|
||||
* `academic_calendar_matrix` (id, semester_id FK, course_number INT, specialty_id FK, week_number INT, activity_type ENUM('THEORY','EXAM','VACATION','PRACTICE')).
|
||||
* Привязка к `course_number` + `specialty_id`, а НЕ к конкретной группе.
|
||||
|
||||
**Схема Движка Правил:**
|
||||
* `schedule_rules` (id, subject_id FK, semester_id FK, active_from_date DATE, total_academic_hours INT).
|
||||
* `total_academic_hours` — в академических часах (1 ак. час = 45 мин, одна пара = 2 ак. часа).
|
||||
* `schedule_rule_groups` (schedule_rule_id FK, group_id FK) — PK составной.
|
||||
* Связующая таблица для потоковых лекций.
|
||||
* `schedule_rule_slots` (id, schedule_rule_id FK, day_of_week INT CHECK(1–7), parity ENUM('BOTH','EVEN','ODD'), time_slot_id FK, subgroup_id FK NULL, teacher_id FK, classroom_id FK, lesson_type_id FK, lesson_format VARCHAR).
|
||||
|
||||
**Скрипт Миграции (Data ETL):** Написание SQL/Java скрипта для миграции данных из двух источников:
|
||||
1. **Из `schedule_data`** → `schedule_rules` + `schedule_rule_groups`: перенос плановой нагрузки (`number_of_hours` → `total_academic_hours`, `group_id`, `subjects_id`, `teacher_id`, `lesson_type_id`, `is_division`, `semester_type`, `period`).
|
||||
2. **Из `lessons`** → `schedule_rule_slots`: перенос расписания с трансформацией данных:
|
||||
* `day` (строка «Понедельник»...«Суббота») → `day_of_week` (INT 1–6).
|
||||
* `time` (строка «8:00 - 9:30») → `time_slot_id` (FK на `time_slots`).
|
||||
* `week` (строка «Верхняя»/«Нижняя»/«Обе») → `parity` (ENUM `ODD`/`EVEN`/`BOTH`).
|
||||
* Группировка записей с одинаковым `(subject_id, group_id)` в одно правило.
|
||||
|
||||
После успешной миграции и верификации данных — удаление таблиц `lessons` и `schedule_data`.
|
||||
|
||||
### Этап 2. Бэкенд и Вычислительное Ядро (Java + Spring Boot)
|
||||
* `AcademicDateService.java` — сервис утилит для календарной математики (перевод дат в недели, получение типа четности недели, проверка попадания дня в справочник `holidays`).
|
||||
* `ScheduleRuleRepository.java` — JPA репозитории для извлечения графа правил из базы данных, с оптимизацией N+1 проблемы через JOIN со слотами.
|
||||
* `ScheduleGeneratorService.java` — Сердце системы. Основной метод: `List<RenderedLesson> buildSchedule(Group group, LocalDate startDate, LocalDate endDate)`. Реализует всю бизнес-логику из пункта 1.3 (вычитание часов, пропуск праздников).
|
||||
* Адаптация валидаторов пересечения аудиторий: теперь валидатор должен работать не на уровне "каждой пары", а симулировать весь семестр на этапе сохранения нового Правила в панели администратора.
|
||||
* `AcademicDateService.java` — сервис утилит для календарной математики:
|
||||
* Перевод дат в номер недели семестра.
|
||||
* Определение чётности недели с учётом настройки тенанта.
|
||||
* Проверка попадания дня в справочник `holidays`.
|
||||
* Вычисление текущего курса группы: `текущий_учебный_год - year_start_study + 1`.
|
||||
* `ScheduleRuleRepository.java` — JPA репозитории для извлечения графа правил из базы данных, с оптимизацией N+1 проблемы через `JOIN FETCH` со слотами и группами.
|
||||
* `ScheduleGeneratorService.java` — Сердце системы. Основные методы:
|
||||
* `List<RenderedLesson> buildScheduleForGroup(Long groupId, LocalDate startDate, LocalDate endDate)` — расписание группы.
|
||||
* `List<RenderedLesson> buildScheduleForTeacher(Long teacherId, LocalDate startDate, LocalDate endDate)` — расписание преподавателя (поиск по `teacher_id` в слотах, обогащение информацией о группах).
|
||||
* Реализует всю бизнес-логику из пункта 1.4 (подсчёт вычитанных часов, пропуск праздников, кеширование).
|
||||
* Адаптация валидаторов пересечения аудиторий: теперь валидатор должен работать не на уровне «каждой пары», а симулировать весь семестр на этапе сохранения нового Правила в панели администратора.
|
||||
|
||||
### Этап 3. Обновление REST API (Контроллеры)
|
||||
* Эндпоинт получения расписания (`GET /api/schedule`) нужно перевести на диапазонную модель. Теперь он должен принимать параметры: `?groupId=123&startDate=2024-10-14&endDate=2024-10-20`. Ответ сервера должен представлять собой массив объектов с закрепленными полными датами `YYYY-MM-DD` вместо абстрактных названий дней недели.
|
||||
* Создание CRUD-контроллеров для админки панели управления:
|
||||
* `/api/admin/calendar/matrix` (настройка каникул и сессий по курсам/неделям).
|
||||
* **Новый эндпоинт расписания:** `GET /api/schedule` переходит на диапазонную модель. Параметры: `?groupId=123&startDate=2024-10-14&endDate=2024-10-20` или `?teacherId=456&startDate=...&endDate=...`. Ответ — массив объектов с полными датами `YYYY-MM-DD`.
|
||||
* **Обратная совместимость:** Старый эндпоинт `GET /api/users/lessons` будет помечен как `@Deprecated` и продолжит работать до полной миграции фронтенда. После завершения миграции фронтенда — удаление.
|
||||
* **CRUD-контроллеры для админки:**
|
||||
* `/api/admin/time-slots` (настройка сетки временных слотов).
|
||||
* `/api/admin/calendar/years` (учебные годы и семестры).
|
||||
* `/api/admin/calendar/matrix` (настройка каникул и сессий по курсам/специальностям/неделям).
|
||||
* `/api/admin/calendar/holidays` (добавление исключений).
|
||||
* `/api/admin/schedule-rules` (управление жизненным циклом Правил и их слотами).
|
||||
* `/api/admin/schedule-rules` (управление жизненным циклом Правил, их слотами и привязкой к группам).
|
||||
|
||||
### Этап 4. Интерфейсы Frontend (Vanilla JS + HTML)
|
||||
* **Страницы просмотра (Студенты и Преподаватели):**
|
||||
* Реализация переключателя календарных дат (Date Picker или кнопки-перелистывания недель).
|
||||
* Логика, которая при свайпе или клике запрашивает у API конкретный диапазон дат и перерисовывает DOM дерево.
|
||||
* **Панель Администратора (SPA интерфейсы):**
|
||||
* **Вкладка "Учебный график":** Визуальная сетка-матрица (52 недели по горизонтали, Курсы/Специальности по вертикали), где админ может закрашивать пересечения разными цветами, назначая статусы (Практика, Каникулы, Теория).
|
||||
* **Вкладка "Конструктор Правил":** Глобально новый визуальный инструмент расписания. Админ выбирает Группу и Дисциплину, задает `totalHours`, а затем динамически добавляет строчки массива слотов через кнопу "Добавить занятие" со списками (Selects) для Дня Недели, Времени, Подгруппы и Четности.
|
||||
* Логика, которая при свайпе или клике запрашивает у API конкретный диапазон дат и перерисовывает DOM-дерево.
|
||||
* Для преподавателей — отображение всех групп, привязанных к каждому занятию.
|
||||
* **Панель Администратора (SPA-интерфейсы):**
|
||||
* **Вкладка «Временные слоты»:** Настройка сетки пар — количество, время начала/окончания, длительность.
|
||||
* **Вкладка «Учебный график»:** Визуальная сетка-матрица (недели по горизонтали, Курсы/Специальности по вертикали), где админ может закрашивать пересечения разными цветами, назначая статусы (Практика, Каникулы, Теория, Экзамены).
|
||||
* **Вкладка «Конструктор Правил»:** Глобально новый визуальный инструмент расписания. Админ выбирает Группы (одну или несколько для потока) и Дисциплину, задаёт `totalHours` в академических часах, а затем динамически добавляет строчки массива слотов через кнопку «Добавить занятие» со списками (Selects) для Дня Недели, Временного слота, Подгруппы, Чётности, Преподавателя, Аудитории и Типа занятия.
|
||||
|
||||
Reference in New Issue
Block a user