Добавить дисциплины календарных графиков
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
- `academic_calendars` хранит график профиля, формы обучения и учебного года.
|
||||
- Форма обучения графика берётся из общего справочника `education_forms`; отдельной таблицы форм для календаря нет.
|
||||
- `academic_calendar_days` хранит дневную сетку графика по курсу и дате.
|
||||
- `academic_calendar_subjects` хранит дисциплины графика по номерам учебных семестров; дисциплины выбираются из справочника `subjects`.
|
||||
- `student_groups` связана со специальностью, профилем и той же таблицей форм обучения.
|
||||
- `student_group_calendar_assignments` назначает группе конкретный график на учебный год.
|
||||
|
||||
@@ -64,7 +65,8 @@
|
||||
3. В разделе календарного графика администратор создаёт учебный год и семестры.
|
||||
4. Там же создаётся календарный учебный график: название, учебный год, специальность, профиль, форма обучения из общего справочника и количество курсов.
|
||||
5. Администратор открывает сетку графика, заполняет дни вручную или массово через диапазон дат.
|
||||
6. В разделе групп администратор назначает календарный график группе на нужный учебный год.
|
||||
6. На вкладке дисциплин графика администратор вручную привязывает дисциплины к учебным семестрам: например, для четырёх курсов доступны семестры `1..8`.
|
||||
7. В разделе групп администратор назначает календарный график группе на нужный учебный год; вместе с назначением виден список дисциплин по семестрам.
|
||||
|
||||
После сохранения графика или назначения группы кэш динамического расписания сбрасывается. Следующий запрос `GET /api/schedule` сразу использует новые правила календаря.
|
||||
|
||||
|
||||
38
docs/API.md
38
docs/API.md
@@ -361,6 +361,8 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
| `DELETE` | `/api/admin/academic-calendars/{id}` | Удалить график |
|
||||
| `GET` | `/api/admin/academic-calendars/{id}/grid` | Дневная сетка графика |
|
||||
| `PUT` | `/api/admin/academic-calendars/{id}/grid` | Полное сохранение дневной сетки |
|
||||
| `GET` | `/api/admin/academic-calendars/{id}/subjects` | Дисциплины графика по номерам учебных семестров |
|
||||
| `PUT` | `/api/admin/academic-calendars/{id}/subjects` | Полная замена привязок дисциплин графика |
|
||||
|
||||
**Тело создания/обновления графика:**
|
||||
```json
|
||||
@@ -375,6 +377,7 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
```
|
||||
|
||||
`studyFormId` берётся из общего справочника форм обучения `GET /api/education-forms`; отдельного справочника форм для календарных графиков нет.
|
||||
`courseCount` должен быть в диапазоне `1..8`.
|
||||
|
||||
**Ячейка сетки графика:**
|
||||
```json
|
||||
@@ -388,6 +391,17 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
}
|
||||
```
|
||||
|
||||
**Привязка дисциплин к графику:**
|
||||
```json
|
||||
[
|
||||
{ "semesterNumber": 1, "subjectId": 1 },
|
||||
{ "semesterNumber": 1, "subjectId": 2 },
|
||||
{ "semesterNumber": 2, "subjectId": 3 }
|
||||
]
|
||||
```
|
||||
|
||||
`semesterNumber` — номер учебного семестра внутри графика: для 4 курсов доступны значения `1..8`. API принимает только существующие неархивные дисциплины из `/api/subjects`, не допускает дубли одной дисциплины в одном семестре и возвращает сохранённые записи с `subjectName`, `subjectCode` и `departmentId`.
|
||||
|
||||
### `POST /api/admin/schedule-rules`
|
||||
|
||||
Создание правила динамического расписания.
|
||||
@@ -729,6 +743,30 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ назначения:**
|
||||
```json
|
||||
{
|
||||
"id": 12,
|
||||
"groupId": 1,
|
||||
"groupName": "ИВТ-21-1",
|
||||
"academicYearId": 1,
|
||||
"academicYearTitle": "2025-2026",
|
||||
"calendarId": 5,
|
||||
"calendarTitle": "09.03.04 очная форма 2025-2026",
|
||||
"subjects": [
|
||||
{
|
||||
"id": 44,
|
||||
"calendarId": 5,
|
||||
"semesterNumber": 1,
|
||||
"subjectId": 1,
|
||||
"subjectName": "Высшая математика",
|
||||
"subjectCode": "Б1.О.01",
|
||||
"departmentId": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Назначаемый график должен относиться к тому же учебному году, специальности, профилю и форме обучения, что и группа.
|
||||
|
||||
---
|
||||
|
||||
@@ -60,6 +60,7 @@ Bearer-токен проверяется на backend. Frontend-скрытие
|
||||
- **Курс:** вычисляется относительно учебного года: `год начала учебного года - year_start_study + 1`, но до начала обучения отдаётся как `0`, а не отрицательное число
|
||||
- **Подгруппы:** Возможно деление группы на подгруппы (таблица `subgroups`)
|
||||
- **Календарь:** на каждый учебный год группе назначается конкретный календарный учебный график
|
||||
- **Дисциплины графика:** при назначении графика группе отображаются дисциплины, вручную привязанные к номерам семестров этого графика
|
||||
- **Завершение обучения:** если текущий курс больше `course_count` назначенного календарного графика, группа считается завершившей обучение и не попадает в обычные списки выбора. Историческое расписание по датам периода обучения остаётся доступным.
|
||||
|
||||
### Аудитории (Classrooms)
|
||||
@@ -118,6 +119,7 @@ Bearer-токен проверяется на backend. Frontend-скрытие
|
||||
| `academic_calendars` | Календарный учебный график профиля, формы обучения и учебного года |
|
||||
| `academic_calendar_activity_types` | Коды Excel-графика: `Т`, `Э`, `К`, `У`, `П`, `Пд`, `Н`, `Г`, `Д`, `ПА`, `С`, `*`, `=` |
|
||||
| `academic_calendar_days` | Дневная сетка по курсу и дате |
|
||||
| `academic_calendar_subjects` | Дисциплины графика по номерам учебных семестров |
|
||||
| `student_group_calendar_assignments` | Назначение конкретного графика группе на учебный год |
|
||||
| `time_slots` | Настраиваемая сетка пар для тенанта |
|
||||
| `schedule_rules` | Лимиты часов и недели начала по лекциям, лабораторным и практикам |
|
||||
|
||||
@@ -233,6 +233,14 @@ erDiagram
|
||||
BIGINT activity_type_id FK
|
||||
}
|
||||
|
||||
academic_calendar_subjects {
|
||||
BIGSERIAL id PK
|
||||
BIGINT calendar_id FK
|
||||
INT semester_number
|
||||
BIGINT subject_id FK
|
||||
TIMESTAMP created_at
|
||||
}
|
||||
|
||||
student_group_calendar_assignments {
|
||||
BIGSERIAL id PK
|
||||
BIGINT group_id FK
|
||||
@@ -311,6 +319,7 @@ erDiagram
|
||||
subjects ||--o{ teacher_subjects : "subject_id"
|
||||
subjects ||--o{ subject_comments : "subject_id"
|
||||
subjects ||--o{ teacher_lesson_types : "subject_id"
|
||||
subjects ||--o{ academic_calendar_subjects : "subject_id"
|
||||
subjects ||--o{ schedule_rules : "subject_id"
|
||||
lesson_types ||--o{ teacher_lesson_types : "lesson_type_id"
|
||||
lesson_types ||--o{ schedule_rule_slots : "lesson_type_id"
|
||||
@@ -325,6 +334,7 @@ erDiagram
|
||||
specialty_profiles ||--o{ academic_calendars : "specialty_profile_id"
|
||||
education_forms ||--o{ academic_calendars : "study_form_id"
|
||||
academic_calendars ||--o{ academic_calendar_days : "calendar_id"
|
||||
academic_calendars ||--o{ academic_calendar_subjects : "calendar_id"
|
||||
academic_calendars ||--o{ student_group_calendar_assignments : "calendar_id"
|
||||
academic_calendar_activity_types ||--o{ academic_calendar_days : "activity_type_id"
|
||||
schedule_rules ||--o{ schedule_rule_groups : "schedule_rule_id"
|
||||
@@ -608,7 +618,7 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
| `specialty_id` | BIGINT FK → specialties | Специальность |
|
||||
| `specialty_profile_id` | BIGINT FK → specialty_profiles | Профиль обучения |
|
||||
| `study_form_id` | BIGINT FK → education_forms | Форма обучения из общего справочника |
|
||||
| `course_count` | INT CHECK(> 0) | Количество курсов в сетке |
|
||||
| `course_count` | INT CHECK(1–8) | Количество курсов в сетке |
|
||||
| `created_at` | TIMESTAMP | Дата создания |
|
||||
| `updated_at` | TIMESTAMP | Дата обновления |
|
||||
|
||||
@@ -623,6 +633,17 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
| `day_of_week` | INT CHECK(1–7) | День недели ISO |
|
||||
| `activity_type_id` | BIGINT FK → academic_calendar_activity_types | Код активности |
|
||||
|
||||
#### `academic_calendar_subjects` — Дисциплины календарного графика
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID |
|
||||
| `calendar_id` | BIGINT FK → academic_calendars (CASCADE) | Календарный график |
|
||||
| `semester_number` | INT CHECK(> 0) | Номер учебного семестра внутри графика: 1, 2, 3 ... |
|
||||
| `subject_id` | BIGINT FK → subjects | Дисциплина из справочника |
|
||||
| `created_at` | TIMESTAMP | Дата создания привязки |
|
||||
|
||||
Уникальность задаётся по `calendar_id + semester_number + subject_id`, поэтому одну дисциплину нельзя дважды добавить в один семестр одного графика. Верхняя граница номера семестра проверяется backend по `academic_calendars.course_count * 2`.
|
||||
|
||||
#### `student_group_calendar_assignments` — Назначения графиков группам
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
@@ -719,7 +740,7 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
|
||||
| Файл | Описание |
|
||||
|------|----------|
|
||||
| `V1__init.sql` | Инициализация: справочники, роли, refresh-сессии JWT, lifecycle-поля, история кафедр преподавателей, комментарии дисциплин, календарные учебные графики, динамическое расписание, версии/закрепления правил, точечные изменения расписания, тестовые правила, триггеры, комментарии |
|
||||
| `V1__init.sql` | Инициализация: справочники, роли, refresh-сессии JWT, lifecycle-поля, история кафедр преподавателей, комментарии дисциплин, календарные учебные графики, привязки дисциплин к графикам по семестрам, динамическое расписание, версии/закрепления правил, точечные изменения расписания, тестовые правила, триггеры, комментарии |
|
||||
|
||||
### Накатывание на существующих тенантов
|
||||
|
||||
|
||||
@@ -139,20 +139,20 @@ frontend/
|
||||
| `department-workspace` | Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели и нагрузка | `/api/department/*`, `/api/workload/teachers` |
|
||||
| `schedule-view` | Read-only просмотр расписаний: по одной выбранной дате строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а на экране отображается одна активная совмещённая таблица чётной/нечётной недели | `/api/schedule/search` |
|
||||
| `schedule` | Конструктор правил динамического расписания с выезжающей визуальной матрицей групп по дням и времени | `/api/admin/schedule-rules`, `/api/admin/time-slots`, `/api/admin/calendar/years`, `/api/lesson-types`, `/api/subgroups` |
|
||||
| `academic-calendar` | Учебные годы, семестры, создание календарных графиков и Excel-подобный редактор дневной сетки | `/api/admin/calendar`, `/api/admin/academic-calendars`, `/api/admin/calendar/activity-types`, `/api/education-forms` |
|
||||
| `academic-calendar` | Учебные годы, семестры, создание календарных графиков, Excel-подобный редактор дневной сетки и привязка дисциплин к семестрам графика | `/api/admin/calendar`, `/api/admin/academic-calendars`, `/api/admin/academic-calendars/{id}/subjects`, `/api/admin/calendar/activity-types`, `/api/education-forms`, `/api/subjects` |
|
||||
| `auditorium-workload` | Динамическая загруженность аудиторий, преподавателей и кафедр: сводная матрица по дате или совмещённая таблица выбранной сущности по чётной/нечётной неделе | `/api/classrooms`, `/api/users/teachers`, `/api/departments`, `/api/admin/time-slots`, `/api/equipments`, `/api/groups`, `/api/schedule`, `/api/admin/calendar/years` |
|
||||
| `database` | Тенанты | `/api/database` |
|
||||
| `departments-data` | Создание, редактирование и удаление кафедр/специальностей | `/api/departments`, `/api/specialties` |
|
||||
|
||||
### Особенности админских вкладок
|
||||
|
||||
- Вкладка `groups` загружает кафедры, специальности, профили, учебные годы и календарные графики. Список групп открывается через `/api/groups?includeArchived=true`, поэтому в таблице видны активные, будущие, завершившие обучение и архивные группы со статусом. Группа создаётся через `/api/groups` с `specialtyId` и `specialtyProfileId`, блок подгрупп использует `/api/subgroups` и `/api/groups/{id}/subgroups`, а блок назначений использует `/api/groups/{id}/calendar-assignments`. В селекты подгрупп и назначений попадают только группы с `active=true`.
|
||||
- Вкладка `groups` загружает кафедры, специальности, профили, учебные годы и календарные графики. Список групп открывается через `/api/groups?includeArchived=true`, поэтому в таблице видны активные, будущие, завершившие обучение и архивные группы со статусом. Группа создаётся через `/api/groups` с `specialtyId` и `specialtyProfileId`, блок подгрупп использует `/api/subgroups` и `/api/groups/{id}/subgroups`, а блок назначений использует `/api/groups/{id}/calendar-assignments`. После назначения графика в таблице назначений сразу выводятся дисциплины графика, сгруппированные по номерам семестров. В селекты подгрупп и назначений попадают только группы с `active=true`.
|
||||
- Вкладка `department-workspace` в блоке преподавателей объединяет данные `/api/department/teachers` и `/api/workload/teachers`: каждый преподаватель показывается одной карточкой с должностью и нагрузкой за выбранный период, преподаватели без занятий получают нулевую нагрузку, а преподаватели из расписания добавляются без дублей. Если дата начала периода выбрана позже даты окончания, поле окончания очищается, а расчёт нагрузки ждёт корректный период.
|
||||
- Вкладка `schedule-view` показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; frontend запрашивает двухнедельный диапазон от понедельника выбранной даты и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и роли `DEPARTMENT` расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Бейдж диапазона недель скрывается для занятий на весь семестр, а для занятий до конца семестра показывает только неделю начала в формате `(с 5 нед.)`. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
|
||||
- Вкладка `auditorium-workload` стала общей вкладкой `Загруженность`: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из `/api/admin/time-slots/effective`, занятость собирается из динамического расписания `/api/schedule` по группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из `/api/admin/calendar/years`.
|
||||
- Вкладка `profiles` выделена под профили обучения: администратор выбирает специальность, создаёт профиль, редактирует описание и удаляет неиспользуемые профили.
|
||||
- Вкладка `schedule` не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате `(с 5 нед.)`, а ограниченный диапазон — как `(с 1 по 3 нед.)`. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары закрывает матрицу и открывает соответствующее правило в форме редактирования. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними.
|
||||
- Вкладка `academic-calendar` полностью отделяет календарную систему от расписания занятий: администратор создаёт учебные годы и семестры, заводит календарные графики, выбирает форму обучения из общего справочника `/api/education-forms`, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через `/api/admin/academic-calendars/{id}/grid`.
|
||||
- Вкладка `academic-calendar` полностью отделяет календарную систему от расписания занятий и внутри себя разделена на три вкладки: `Графики` для учебных годов, семестров и карточек календарных графиков, `Сетки` для редактора дневной сетки, `Дисциплины` для ручной привязки дисциплин из `/api/subjects` к номерам учебных семестров графика. Администратор выбирает форму обучения из общего справочника `/api/education-forms`, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через `/api/admin/academic-calendars/{id}/grid`.
|
||||
- Редактор годового графика во вкладке `academic-calendar` показывает компактную табличную сетку: курсы раскрываются отдельными секциями со стрелкой, семестры внутри курса на широком экране идут рядом в две равные колонки одинаковой высоты, столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. В рамках одного курса семестровые таблицы получают одинаковое число недельных колонок: недостающие колонки заполняются пустыми ячейками, поэтому левая и правая части занимают всю ширину секции курса без внешней пустоты. Подробная расшифровка и изменение кода открываются в компактном модальном окне по клику на ячейку; при наведении отображается кастомная подсказка с датой, курсом, кодом активности и временной сеткой, а код активности в подсказке центрируется внутри квадратного индикатора. Ячейки можно выделять протяжкой мышью и менять код активности через ту же модалку, выбранные ячейки подсвечиваются мягкой заливкой, а кнопка «Применить» закрывает окно.
|
||||
- Вкладка `departments-data` использует модальные формы редактирования и маршруты `PUT/DELETE /api/departments/{id}` и `PUT/DELETE /api/specialties/{id}`.
|
||||
- Вкладка `classrooms` теперь показывает архивные аудитории через `includeArchived=true`. Кнопка удаления заменена на архивирование: аудитория выводится из эксплуатации, но остаётся в историческом расписании. Для архивных аудиторий доступно восстановление.
|
||||
|
||||
Reference in New Issue
Block a user