подправил создание правил расписания
This commit is contained in:
@@ -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 | Назначение |
|
||||
|-------|-----|------------|
|
||||
|
||||
@@ -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` обязателен и хранится в слоте правила.
|
||||
|
||||
---
|
||||
|
||||
@@ -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(1–7) | День недели: 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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 календарных графиков;
|
||||
|
||||
@@ -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}`;
|
||||
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторной подгруппы.
|
||||
- отображение дисциплины, времени, преподавателя, аудитории, формата, типа занятия и лабораторных подгрупп.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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. Лимит часов считается только по реально проведённым занятиям; для лабораторных подгрупп счётчик ведётся отдельно по каждой подгруппе.
|
||||
|
||||
Лекции и практики нельзя делить на подгруппы. Если лабораторный слот привязан к подгруппе, он выводится только для родительской группы этой подгруппы.
|
||||
Лекции и практики нельзя делить на подгруппы. Если лабораторный слот привязан к нескольким подгруппам разных групп, он выводится как совместное занятие этих групп, а часы списываются отдельно по каждой подгруппе.
|
||||
|
||||
Если назначение графика отсутствует, расписание группы возвращается пустым списком без ошибки.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- [x] Обновлён `AcademicDateService`: активность дня определяется по назначенному группе графику.
|
||||
- [x] Обновлён `ScheduleGeneratorService`: обычные пары генерируются только на разрешённых днях.
|
||||
- [x] В `V1__init.sql` добавлены отдельные часы и недели начала для лекций, лабораторных и практик.
|
||||
- [x] Добавлены подгруппы для лабораторных слотов; лекции и практики не делятся на подгруппы.
|
||||
- [x] Добавлены подгруппы для лабораторных слотов, включая выбор разных подгрупп разных групп в одном поточном лабораторном слоте; лекции и практики не делятся на подгруппы.
|
||||
- [x] Добавлены API профилей, календарей, дневной сетки, кодов активностей и назначений группам.
|
||||
- [x] Обновлены админские экраны специальностей, групп и расписания.
|
||||
- [x] Добавлена документация `docs/ACADEMIC_CALENDAR.md`.
|
||||
|
||||
Reference in New Issue
Block a user