подправил создание правил расписания

This commit is contained in:
Zuev
2026-05-08 13:41:25 +03:00
parent e12a5bcccd
commit 02c5ea9118
20 changed files with 592 additions and 126 deletions

View File

@@ -149,6 +149,8 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
"lessonFormat": "Очно",
"subgroupId": null,
"subgroupName": null,
"subgroupIds": [],
"subgroupNames": [],
"groupIds": [1],
"groupNames": ["ИВТ-21-1"],
"activityType": "Т",
@@ -292,7 +294,8 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
"dayOfWeek": 1,
"parity": "BOTH",
"timeSlotId": 3,
"subgroupId": null,
"subgroupId": null,
"subgroupIds": [],
"teacherId": 2,
"classroomId": 1,
"lessonTypeId": 1,
@@ -314,7 +317,7 @@ CRUD доступен по:
`timeSlotId` должен ссылаться на базовый слот (`scopeApplyMode = DEFAULT`). Субботняя и ручные сетки не выбираются в правиле напрямую.
`subgroupId` можно передавать только для лабораторного слота. Подгруппа должна относиться к одной из групп правила. Для лекций и практик поле должно быть `null`, иначе API вернёт ошибку валидации.
`subgroupIds` можно передавать только для лабораторного слота. Каждая подгруппа должна относиться к одной из групп правила. Если лабораторная проводится у нескольких групп одновременно, в одном слоте можно передать разные подгруппы этих групп, например `[10, 22]`. Для совместимости одиночный `subgroupId` тоже принимается, но новый формат — `subgroupIds`. Для лекций и практик оба поля должны быть пустыми, иначе API вернёт ошибку валидации. В одном слоте нельзя выбрать больше одной подгруппы одной и той же группы.
Часы и недели начала задаются отдельно для лекций, лабораторных и практик. Если для типа занятий указан ненулевой лимит часов, в правиле должен быть хотя бы один слот этого типа; если слот типа есть, его лимит часов должен быть больше нуля.
@@ -460,7 +463,7 @@ CRUD доступен по:
### Подгруппы группы
Подгруппы используются только для деления лабораторных занятий. Лекции и практики не принимают `subgroupId`.
Подгруппы используются только для деления лабораторных занятий. Лекции и практики не принимают `subgroupId` и `subgroupIds`.
| Метод | URL | Назначение |
|-------|-----|------------|

View File

@@ -84,7 +84,8 @@
| `time_slots` | Настраиваемая сетка пар для тенанта |
| `schedule_rules` | Лимиты часов и недели начала по лекциям, лабораторным и практикам |
| `schedule_rule_groups` | Группы правила, включая потоковые лекции |
| `schedule_rule_slots` | День, чётность, слот, преподаватель, аудитория, тип, формат и опциональная лабораторная подгруппа |
| `schedule_rule_slots` | День, чётность, слот, преподаватель, аудитория, тип и формат занятия |
| `schedule_rule_slot_subgroups` | Подгруппы лабораторного слота |
Генератор `ScheduleGeneratorService` рендерит расписание по запросу:
1. Определяет семестр для каждой даты диапазона.
@@ -98,7 +99,7 @@
9. Считает уже проведённые часы отдельно для лекций, практик и каждой лабораторной подгруппы.
10. Останавливает вывод слотов конкретного типа, когда достигнут его лимит часов.
Лабораторные работы могут делиться на подгруппы через `schedule_rule_slots.subgroup_id`. Если подгруппа выбрана, занятие выводится только для родительской группы этой подгруппы, а лимит лабораторных часов списывается отдельно по этой подгруппе. Лекции и практики не делятся на подгруппы: такие слоты сохраняются только с `subgroup_id = NULL`.
Лабораторные работы могут делиться на подгруппы через `schedule_rule_slot_subgroups`. Если подгруппы выбраны, занятие выводится только для родительских групп этих подгрупп, а лимит лабораторных часов списывается отдельно по каждой подгруппе. Если лабораторная проводится у нескольких групп одновременно, один слот может содержать разные подгруппы разных групп. Лекции и практики не делятся на подгруппы.
Обычные пары генерируются только на коде `Т` (`allow_schedule = true`). Экзамены, каникулы, практики, нерабочие дни, праздники `*` и дни вне учебного года `=` считаются пропуском: занятие не переносится и не списывает академические часы. Если у группы нет назначения графика на учебный год, `GET /api/schedule` возвращает пустой список для этой группы без ошибки.
@@ -126,7 +127,7 @@
- **Покрытие типов:** если для лекций, лабораторных или практик указан лимит часов, должен быть хотя бы один слот этого типа; слот типа не сохраняется с нулевым лимитом часов.
- **Слот:** день недели должен быть от 1 до 7, чётность недели обязательна.
- **Связанные сущности:** базовый временной слот, преподаватель, аудитория и тип занятия должны существовать в БД.
- **Подгруппы:** `subgroupId` разрешён только для лабораторных слотов и должен относиться к одной из групп правила.
- **Подгруппы:** `subgroupIds` разрешены только для лабораторных слотов, должны относиться к группам правила, и в одном слоте можно выбрать не больше одной подгруппы каждой группы.
- **Формат:** `lessonFormat` обязателен и хранится в слоте правила.
---

View File

@@ -226,12 +226,16 @@ erDiagram
INT day_of_week
VARCHAR parity
BIGINT time_slot_id FK
BIGINT subgroup_id FK
BIGINT teacher_id FK
BIGINT classroom_id FK
BIGINT lesson_type_id FK
VARCHAR lesson_format
}
schedule_rule_slot_subgroups {
BIGINT schedule_rule_slot_id FK,PK
BIGINT subgroup_id FK,PK
}
departments ||--o{ users : "department_id"
departments ||--o{ student_groups : "department_id"
@@ -265,10 +269,11 @@ erDiagram
academic_calendar_activity_types ||--o{ academic_calendar_days : "activity_type_id"
schedule_rules ||--o{ schedule_rule_groups : "schedule_rule_id"
schedule_rules ||--o{ schedule_rule_slots : "schedule_rule_id"
schedule_rule_slots ||--o{ schedule_rule_slot_subgroups : "schedule_rule_slot_id"
time_slot_scopes ||--o{ time_slots : "time_slot_scope_id"
time_slot_scopes ||--o{ time_slot_date_assignments : "time_slot_scope_id"
time_slots ||--o{ schedule_rule_slots : "time_slot_id"
subgroups ||--o{ schedule_rule_slots : "subgroup_id"
subgroups ||--o{ schedule_rule_slot_subgroups : "subgroup_id"
users ||--o{ schedule_rule_slots : "teacher_id"
```
@@ -534,13 +539,18 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
| `day_of_week` | INT CHECK(17) | День недели: 1 — понедельник |
| `parity` | VARCHAR(10) | `BOTH`, `EVEN`, `ODD` |
| `time_slot_id` | BIGINT FK → time_slots | Базовый временной слот |
| `subgroup_id` | BIGINT FK → subgroups, NULL | Подгруппа лабораторной работы |
| `teacher_id` | BIGINT FK → users | Преподаватель |
| `classroom_id` | BIGINT FK → classrooms | Аудитория |
| `lesson_type_id` | BIGINT FK → lesson_types | Тип занятия |
| `lesson_format` | VARCHAR(30) | `Очно` или `Онлайн` |
`subgroup_id` заполняется только для лабораторных слотов. Лекции и практики не делятся на подгруппы и должны хранить `NULL`. Ограничение дополнительно проверяется триггером `validate_schedule_rule_slot_subgroup`; для быстрых выборок есть индекс `idx_schedule_rule_slots_subgroup`.
#### `schedule_rule_slot_subgroups` — Подгруппы лабораторного слота
| Колонка | Тип | Описание |
|---------|-----|----------|
| `schedule_rule_slot_id` | BIGINT PK, FK → schedule_rule_slots (CASCADE) | Слот правила |
| `subgroup_id` | BIGINT PK, FK → subgroups (CASCADE) | Подгруппа |
Связь заполняется только для лабораторных слотов. Для потоковой лабораторной можно выбрать разные подгруппы разных групп в одном слоте. Лекции и практики не делятся на подгруппы и не имеют записей в этой таблице. Триггер `validate_schedule_rule_slot_subgroups` проверяет тип занятия, принадлежность подгруппы к группам правила и запрет на две подгруппы одной группы в одном слоте; для быстрых выборок есть индекс `idx_schedule_rule_slot_subgroups_subgroup`.
---

View File

@@ -25,7 +25,8 @@
| `time_slots` | Настраиваемая сетка пар |
| `schedule_rules` | Правила проведения дисциплин с отдельными часами и стартовыми неделями лекций, лабораторных и практик |
| `schedule_rule_groups` | Группы правила |
| `schedule_rule_slots` | Шаблонные слоты правила, включая опциональную лабораторную подгруппу |
| `schedule_rule_slots` | Шаблонные слоты правила |
| `schedule_rule_slot_subgroups` | Подгруппы лабораторного слота |
Праздничные и неучебные дни больше не хранятся отдельной сущностью: они задаются кодами `*` и `=` в дневной сетке.
@@ -40,7 +41,7 @@
| `AcademicDateService` | Семестр по дате, номер недели, чётность, курс группы, активность дня по назначенному графику |
| `ScheduleGeneratorService` | Рендер расписания группы или преподавателя на диапазон дат |
`ScheduleGeneratorService` пропускает день, если код активности не разрешает обычные пары. Для лекций, лабораторных и практик генератор отдельно проверяет стартовую неделю и отдельно списывает академические часы. Лабораторные подгруппы имеют отдельный счётчик лабораторных часов; лекции и практики на подгруппы не делятся. Если у группы нет назначенного графика на учебный год, расписание этой группы возвращается пустым списком.
`ScheduleGeneratorService` пропускает день, если код активности не разрешает обычные пары. Для лекций, лабораторных и практик генератор отдельно проверяет стартовую неделю и отдельно списывает академические часы. Лабораторные подгруппы имеют отдельный счётчик лабораторных часов; один лабораторный слот может включать разные подгруппы разных групп. Лекции и практики на подгруппы не делятся. Если у группы нет назначенного графика на учебный год, расписание этой группы возвращается пустым списком.
## API
@@ -73,7 +74,7 @@ GET /api/schedule?teacherId=2&startDate=2026-04-27&endDate=2026-05-03
- конструктор правил динамического расписания;
- отдельные поля часов и недель начала для лекций, лабораторных и практик;
- выбор подгруппы только для лабораторного слота;
- выбор подгрупп только для лабораторного слота;
- управление временными слотами;
- учебные годы и семестры;
- CRUD календарных графиков;

View File

@@ -123,7 +123,7 @@ frontend/
- Вкладка `groups` загружает кафедры, специальности, профили, учебные годы и календарные графики. Группа создаётся через `/api/groups` с `specialtyId` и `specialtyProfileId`, блок подгрупп использует `/api/subgroups` и `/api/groups/{id}/subgroups`, а блок назначений использует `/api/groups/{id}/calendar-assignments`.
- Вкладка `auditorium-workload` показывает матрицу загруженности по выбранной дате: строки — реальные аудитории из `/api/classrooms`, столбцы — эффективные временные слоты выбранного дня из `/api/admin/time-slots/effective`, занятость собирается из динамического расписания `/api/schedule` по группам. Фильтры корпуса, вместимости и оборудования заполняются из API.
- Вкладка `profiles` выделена под профили обучения: администратор выбирает специальность, создаёт профиль, редактирует описание и удаляет неиспользуемые профили.
- Вкладка `schedule` не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. В слоте подгруппа доступна только при выборе лабораторной работы; для лекций и практик селект подгруппы заблокирован. Из календарной системы здесь используется список семестров для выбора периода действия правила.
- Вкладка `schedule` не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Из календарной системы здесь используется список семестров для выбора периода действия правила.
- Вкладка `academic-calendar` полностью отделяет календарную систему от расписания занятий: администратор создаёт учебные годы и семестры, заводит календарные графики, выбирает форму обучения из общего справочника `/api/education-forms`, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через `/api/admin/academic-calendars/{id}/grid`.
- Редактор годового графика во вкладке `academic-calendar` показывает мелкую табличную сетку: каждый курс разделён на семестры (`1`/`2`, `3`/`4` и далее), внутри семестра столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. Подробная расшифровка и изменение кода открываются в модальном окне по клику на ячейку.
- Вкладка `departments-data` использует модальные формы редактирования и маршруты `PUT/DELETE /api/departments/{id}` и `PUT/DELETE /api/specialties/{id}`.
@@ -218,7 +218,7 @@ export function isAuthenticatedAsAdmin() {
- навигация по неделям: предыдущая, текущая, следующая;
- выбор даты через `input[type="date"]`;
- запрос `GET /api/schedule?teacherId={userId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
- отображение дисциплины, времени, типа занятия, лабораторной подгруппы, аудитории и всех групп правила.
- отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила.
Если пользователь вошёл до появления поля `userId`, страница попросит выполнить вход заново.
@@ -230,7 +230,7 @@ export function isAuthenticatedAsAdmin() {
- селект группы с сохранением выбора в `localStorage.studentGroupId`;
- недельная сетка по дням;
- запрос `GET /api/schedule?groupId={groupId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторной подгруппы.
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп.
---

View File

@@ -8,7 +8,7 @@
- календарный учебный график по специальности, профилю, форме обучения и учебному году;
- правила расписания дисциплин;
- слоты правил: день недели, чётность, пара, преподаватель, аудитория, тип занятия, формат и опциональная подгруппа для лабораторных;
- слоты правил: день недели, чётность, пара, преподаватель, аудитория, тип занятия, формат и опциональные подгруппы для лабораторных;
- назначение конкретного календарного графика учебной группе на учебный год.
При запросе `GET /api/schedule` backend рендерит расписание на диапазон дат. Если дата не является днём теоретического обучения по назначенному группе графику, обычные пары не выводятся и академические часы не списываются.
@@ -29,7 +29,7 @@
- `time_slots` задаёт сетку пар для тенанта.
- `schedule_rules` хранит дисциплину, семестр, отдельные лимиты часов и стартовые недели для лекций, лабораторных и практик.
- `schedule_rule_groups` связывает правило с одной или несколькими группами.
- `schedule_rule_slots` хранит шаблонные занятия правила; `subgroup_id` разрешён только для лабораторных работ.
- `schedule_rule_slots` хранит шаблонные занятия правила, а `schedule_rule_slot_subgroups` связывает лабораторный слот с одной или несколькими подгруппами.
## Алгоритм генерации
@@ -41,7 +41,7 @@
6. Для разрешённого дня выбираются активные правила и слоты.
7. Лимит часов считается только по реально проведённым занятиям; для лабораторных подгрупп счётчик ведётся отдельно по каждой подгруппе.
Лекции и практики нельзя делить на подгруппы. Если лабораторный слот привязан к подгруппе, он выводится только для родительской группы этой подгруппы.
Лекции и практики нельзя делить на подгруппы. Если лабораторный слот привязан к нескольким подгруппам разных групп, он выводится как совместное занятие этих групп, а часы списываются отдельно по каждой подгруппе.
Если назначение графика отсутствует, расписание группы возвращается пустым списком без ошибки.

View File

@@ -12,7 +12,7 @@
- [x] Обновлён `AcademicDateService`: активность дня определяется по назначенному группе графику.
- [x] Обновлён `ScheduleGeneratorService`: обычные пары генерируются только на разрешённых днях.
- [x] В `V1__init.sql` добавлены отдельные часы и недели начала для лекций, лабораторных и практик.
- [x] Добавлены подгруппы для лабораторных слотов; лекции и практики не делятся на подгруппы.
- [x] Добавлены подгруппы для лабораторных слотов, включая выбор разных подгрупп разных групп в одном поточном лабораторном слоте; лекции и практики не делятся на подгруппы.
- [x] Добавлены API профилей, календарей, дневной сетки, кодов активностей и назначений группам.
- [x] Обновлены админские экраны специальностей, групп и расписания.
- [x] Добавлена документация `docs/ACADEMIC_CALENDAR.md`.