This commit is contained in:
84
docs/API.md
84
docs/API.md
@@ -416,6 +416,25 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/schedule/semesters`
|
||||
|
||||
Доступный только для чтения список семестров для фильтров просмотра расписания. Доступен всем ролям,
|
||||
которые могут просматривать расписание, включая `DEPARTMENT` и `SCHEDULE_VIEWER`.
|
||||
Семестры возвращаются от новых к старым.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"academicYearId": 1,
|
||||
"academicYearTitle": "2025/2026",
|
||||
"semesterType": "spring",
|
||||
"startDate": "2026-02-09",
|
||||
"endDate": "2026-06-30"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/admin/time-slots`
|
||||
|
||||
Список временных слотов занятий. Слот принадлежит конкретной сетке времени: базовой, автоматической субботней или ручной.
|
||||
@@ -737,45 +756,82 @@ GET /api/schedule/search?classroomId=1&startDate=2026-05-20&endDate=2026-05-27
|
||||
```
|
||||
|
||||
Ответ совпадает со структурой `RenderedLessonDto` из `GET /api/schedule`.
|
||||
Для занятия, к которому применено разовое изменение, дополнительно заполнены:
|
||||
|
||||
- `scheduleOverrideId` — идентификатор изменения;
|
||||
- `overrideAction` — `MOVE` или `REPLACE` (`CANCEL` в выдачу не попадает);
|
||||
- `originalLessonDate` — исходная дата занятия из базового правила.
|
||||
|
||||
Перенос удаляет занятие из исходного дня и добавляет его в целевой. Поиск учитывает
|
||||
изменение, если в запрошенный диапазон попала исходная **или** целевая дата, поэтому
|
||||
входящий перенос находится даже запросом только по целевому диапазону. Для целевой даты
|
||||
пересчитываются день недели, номер недели и чётность; учёт академических часов остаётся
|
||||
привязан к исходному занятию.
|
||||
|
||||
### Точечные изменения расписания учебного отдела
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/edu-office/schedule/overrides` | Список точечных изменений |
|
||||
| `GET` | `/api/edu-office/schedule/overrides?startDate=&endDate=` | Список изменений; диапазон проверяется по исходной или целевой дате |
|
||||
| `GET` | `/api/edu-office/schedule/overrides/availability?baseRuleSlotId=&lessonDate=` | Границы семестра и допустимые учебные даты для конкретного занятия |
|
||||
| `POST` | `/api/edu-office/schedule/overrides` | Создать перенос, отмену или замену |
|
||||
| `PUT` | `/api/edu-office/schedule/overrides/{id}` | Обновить изменение |
|
||||
| `DELETE` | `/api/edu-office/schedule/overrides/{id}` | Удалить изменение |
|
||||
| `DELETE` | `/api/edu-office/schedule/overrides/{id}` | Удалить изменение и вернуть актуальный вариант из правила |
|
||||
|
||||
`startDate` и `endDate` у списка передаются только парой, включительно; максимальный
|
||||
диапазон — 120 дней. Ответ реестра содержит исходные дату, время, преподавателя,
|
||||
аудиторию, формат, дисциплину, тип занятия, группы и границы семестра, поэтому отменённую
|
||||
или перенесённую пару можно открыть без присутствия в текущей выдаче расписания.
|
||||
|
||||
```json
|
||||
{
|
||||
"baseRuleSlotId": 31,
|
||||
"lessonDate": "2026-05-21",
|
||||
"action": "REPLACE",
|
||||
"targetLessonDate": "2026-05-27",
|
||||
"action": "MOVE",
|
||||
"newTimeSlotId": 4,
|
||||
"newClassroomId": 2,
|
||||
"newTeacherId": 5,
|
||||
"comment": "Замена аудитории и преподавателя"
|
||||
"newLessonFormat": "Онлайн",
|
||||
"comment": "Перенос конкретного занятия"
|
||||
}
|
||||
```
|
||||
|
||||
`lessonDate` всегда обозначает исходное занятие из правила. `targetLessonDate` передаётся
|
||||
только при переносе на другой день и не заменяет идентификатор исходной пары
|
||||
`baseRuleSlotId + lessonDate`.
|
||||
|
||||
Правила payload:
|
||||
|
||||
- `CANCEL` отменяет конкретную пару; поля `newTimeSlotId`, `newClassroomId`,
|
||||
`newTeacherId` и `newLessonFormat` должны отсутствовать;
|
||||
- `MOVE` требует новый временной слот или аудиторию; преподавателя и формат можно изменить
|
||||
в том же запросе;
|
||||
- `REPLACE` требует нового преподавателя, аудиторию или формат; временной слот можно
|
||||
изменить в том же запросе;
|
||||
- `CANCEL` отменяет конкретную пару; `targetLessonDate` и все поля `new*` должны отсутствовать;
|
||||
- `MOVE` требует новый временной слот; при переносе даты слот выбирается из эффективной
|
||||
сетки целевого дня, а преподавателя, аудиторию и формат можно изменить тем же запросом;
|
||||
- `REPLACE` используется только без изменения даты и времени и требует нового
|
||||
преподавателя, аудиторию или формат;
|
||||
- формат принимает только `Очно` или `Онлайн`;
|
||||
- `MOVE` и `REPLACE` должны фактически менять основные параметры действия. Другой ID
|
||||
временного слота с тем же интервалом не считается переносом.
|
||||
|
||||
До сохранения backend строит базовую пару на `lessonDate` по тем же правилам, что и обычное
|
||||
расписание: семестр, календарный график, чётность, активность сущностей и остаток часов.
|
||||
Если пара не формируется или нарушена матрица действия, API возвращает `400` с русским
|
||||
сообщением. Если результирующее время пересекается с занятым преподавателем, аудиторией,
|
||||
группой или той же подгруппой, API возвращает `409 Conflict`; соседние интервалы и разные
|
||||
подгруппы одной группы не конфликтуют.
|
||||
При переносе даты backend дополнительно проверяет тот же семестр, действие правила и
|
||||
дисциплины, lifecycle итоговых ресурсов, учебный календарь всех затронутых групп и
|
||||
принадлежность времени эффективной сетке целевого дня. Если пара не формируется или
|
||||
нарушена матрица действия, API возвращает `400` с русским сообщением. Если результирующее
|
||||
время пересекается с занятым преподавателем, аудиторией, группой или той же подгруппой,
|
||||
API возвращает `409 Conflict`; соседние интервалы и разные подгруппы одной группы не
|
||||
конфликтуют.
|
||||
|
||||
Пример ответа availability:
|
||||
|
||||
```json
|
||||
{
|
||||
"semesterId": 3,
|
||||
"semesterStartDate": "2026-02-09",
|
||||
"semesterEndDate": "2026-06-30",
|
||||
"availableDates": ["2026-05-21", "2026-05-22", "2026-05-25"]
|
||||
}
|
||||
```
|
||||
|
||||
## Загруженность
|
||||
|
||||
|
||||
@@ -263,30 +263,44 @@ Bearer-токен проверяется на backend. Frontend-скрытие
|
||||
Учебный отдел может создать изменение конкретной пары:
|
||||
|
||||
- `CANCEL` — отменить пару;
|
||||
- `MOVE` — перенести пару на другой фактический интервал или в другую аудиторию;
|
||||
- `REPLACE` — заменить преподавателя, аудиторию или формат.
|
||||
- `MOVE` — перенести пару на другую учебную дату того же семестра или другой фактический интервал;
|
||||
- `REPLACE` — без изменения даты и времени заменить преподавателя, аудиторию или формат.
|
||||
|
||||
`CANCEL` не принимает новые ресурсы. Для `MOVE` обязателен новый временной слот или
|
||||
аудитория, для `REPLACE` — преподаватель, аудитория или формат. Дополнительные изменения
|
||||
можно объединять в одном payload, но основное действие должно фактически менять свою
|
||||
часть пары. Формат ограничен значениями `Очно` и `Онлайн`.
|
||||
`CANCEL` не принимает целевую дату и новые ресурсы. Для `MOVE` обязателен новый временной
|
||||
слот, для `REPLACE` — преподаватель, аудитория или формат. Перенос даты требует явно
|
||||
выбранного слота эффективной сетки целевого дня. Дополнительные изменения преподавателя,
|
||||
аудитории и формата можно объединить с `MOVE`, но основное действие должно фактически
|
||||
менять свою часть пары. Формат ограничен значениями `Очно` и `Онлайн`; дисциплина и тип
|
||||
занятия не изменяются.
|
||||
|
||||
Изменения не переписывают базовое правило, а накладываются поверх сгенерированного
|
||||
расписания на конкретную дату. Перед записью `ScheduleOverrideService`:
|
||||
расписания. Исходная пара всегда идентифицируется как `baseRuleSlotId + lessonDate`, а
|
||||
`targetLessonDate` хранит только новую дату единственного занятия. Перед записью
|
||||
`ScheduleOverrideService`:
|
||||
|
||||
1. захватывает transaction advisory lock PostgreSQL для tenant-БД и даты;
|
||||
1. захватывает transaction advisory lock PostgreSQL для исходных и целевых дат в стабильном порядке;
|
||||
2. строит базовый день для всех групп без интерактивного лимита широкого поиска;
|
||||
3. доказывает существование исходной пары с учётом семестра, календарного графика,
|
||||
чётности, недели начала, лимита часов и lifecycle;
|
||||
4. применяет сохранённые overrides и кандидат общей логикой `ScheduleQueryService`;
|
||||
5. проверяет полуоткрытые временные интервалы `[start, end)` и итоговые ресурсы;
|
||||
6. сохраняет изменение только при отсутствии конфликта.
|
||||
4. для новой даты проверяет границы того же семестра, активность правила и дисциплины,
|
||||
учебный день всех затронутых групп и эффективную сетку времени;
|
||||
5. проверяет lifecycle итоговых преподавателя и аудитории на целевую дату;
|
||||
6. применяет сохранённые overrides и кандидат общей логикой `ScheduleQueryService`;
|
||||
7. проверяет полуоткрытые временные интервалы `[start, end)` и итоговые ресурсы целевого дня;
|
||||
8. сохраняет изменение только при отсутствии конфликта.
|
||||
|
||||
В поиске `MOVE` удаляет исходное вхождение и добавляет занятие в целевой день, включая
|
||||
случай, когда в запрос попала только целевая дата. День недели, номер недели и чётность
|
||||
вычисляются заново по семестру, а расход академических часов остаётся у исходной пары.
|
||||
`CANCEL` скрывает занятие. Удаление override возвращает текущий вариант, который снова
|
||||
формируется базовым правилом.
|
||||
|
||||
Совпадение преподавателя или аудитории в пересекающееся время всегда является конфликтом.
|
||||
Для общей учебной группы занятие целой группы конфликтует с любой её подгруппой; разные
|
||||
подгруппы одной группы могут идти параллельно при свободных преподавателях и аудиториях.
|
||||
Конфликт возвращается как `409 Conflict` с русским сообщением. Блокировка PostgreSQL общая
|
||||
для backend-pod, поэтому два конкурентных изменения одной даты проверяются последовательно.
|
||||
для backend-pod, поэтому два конкурентных переноса с разных исходных дат на одну целевую
|
||||
дату и одинаковые ресурсы проверяются последовательно.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -326,6 +326,7 @@ erDiagram
|
||||
BIGSERIAL id PK
|
||||
BIGINT base_rule_slot_id FK
|
||||
DATE lesson_date
|
||||
DATE target_lesson_date
|
||||
VARCHAR action
|
||||
BIGINT new_time_slot_id FK
|
||||
BIGINT new_classroom_id FK
|
||||
@@ -873,7 +874,8 @@ V1 добавляет `uq_schedule_rule_slots_exact_payload`: в одном пр
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID изменения |
|
||||
| `base_rule_slot_id` | BIGINT FK → schedule_rule_slots | Базовый слот правила |
|
||||
| `lesson_date` | DATE | Дата конкретной пары |
|
||||
| `lesson_date` | DATE | Исходная дата конкретной пары из базового правила |
|
||||
| `target_lesson_date` | DATE NULL | Новая дата единственного занятия при `MOVE` |
|
||||
| `action` | VARCHAR(20) | `MOVE`, `CANCEL`, `REPLACE` |
|
||||
| `new_time_slot_id` | BIGINT FK → time_slots | Новый временной слот |
|
||||
| `new_classroom_id` | BIGINT FK → classrooms | Новая аудитория |
|
||||
@@ -887,12 +889,19 @@ V1 добавляет `uq_schedule_rule_slots_exact_payload`: в одном пр
|
||||
одной и той же пары. Базовая схема V1 добавляет структурные инварианты:
|
||||
|
||||
- `CANCEL` не содержит новых ресурсов;
|
||||
- `MOVE` содержит новый временной слот или аудиторию;
|
||||
- `REPLACE` содержит нового преподавателя, аудиторию или формат;
|
||||
- `MOVE` содержит новый временной слот;
|
||||
- `REPLACE` не содержит нового времени и содержит нового преподавателя, аудиторию или формат;
|
||||
- `new_lesson_format` равен `Очно`, `Онлайн` либо `NULL`.
|
||||
- `target_lesson_date` отличается от `lesson_date`, разрешена только для `MOVE` и требует
|
||||
`new_time_slot_id`.
|
||||
|
||||
Фактическое существование пары, реальность изменения и ресурсные конфликты зависят от
|
||||
календаря конкретной даты и проверяются транзакционным сервисом, а не SQL CHECK.
|
||||
Частичный индекс `idx_schedule_overrides_target_date` ускоряет выборку входящих переносов,
|
||||
а индекс исходной даты `idx_schedule_overrides_date` обслуживает исходящие изменения и
|
||||
отмены. Диапазонный реестр и поиск используют обе даты.
|
||||
|
||||
Фактическое существование пары, тот же семестр, учебный календарь затронутых групп,
|
||||
lifecycle ресурсов, эффективная сетка целевого дня, реальность изменения и ресурсные
|
||||
конфликты проверяются транзакционным сервисом, а не SQL CHECK.
|
||||
|
||||
---
|
||||
|
||||
@@ -910,7 +919,7 @@ V1 добавляет `uq_schedule_rule_slots_exact_payload`: в одном пр
|
||||
|
||||
| Файл | Описание |
|
||||
|------|----------|
|
||||
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики, динамическое расписание, точечные изменения, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
|
||||
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики, динамическое расписание, точечные изменения с переносом даты, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
|
||||
|
||||
### Этап разработки
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ frontend/
|
||||
├── tests/
|
||||
│ ├── auth-session.test.mjs # Login/refresh/reload/logout и single-flight refresh
|
||||
│ ├── dashboard-conflicts.test.mjs # Регрессии дат и состояний проверки конфликтов
|
||||
│ ├── schedule-overrides.test.mjs # Действия, роли, недельный выбор и подбор времени разовой правки
|
||||
│ ├── schedule-view-semesters.test.mjs # Выбор семестра и расчёт двухнедельного диапазона просмотра
|
||||
│ └── security-policy.test.mjs # Web Storage, XSS, пароли, язык, CSP и Dockerfile
|
||||
├── index.html # 🔐 Страница авторизации (общая)
|
||||
├── script.js # Логика авторизации
|
||||
@@ -59,7 +61,8 @@ frontend/
|
||||
│ │ ├── subjects.js # Управление дисциплинами
|
||||
│ │ ├── university-structure.js # Кафедры, специальности и профили обучения
|
||||
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
|
||||
│ │ ├── schedule-view.js # Read-only просмотр расписаний в матрицах
|
||||
│ │ ├── schedule-view.js # Просмотр расписаний и запуск разовой правки из карточки
|
||||
│ │ ├── schedule-override-panel.js # Боковая панель и реестр разовых изменений
|
||||
│ │ ├── schedule.js # Конструктор правил расписания
|
||||
│ │ ├── academic-calendar.js # Календарные учебные графики
|
||||
│ │ └── auditorium-workload.js # Загруженность аудиторий, преподавателей и кафедр
|
||||
@@ -152,7 +155,7 @@ frontend/
|
||||
| `subjects` | Дисциплины | `/api/subjects` |
|
||||
| `university-structure` | Кафедры, специальности и профили обучения; профили доступны отдельной внутренней вкладкой и через кнопку специальности | `/api/departments`, `/api/specialties`, `/api/specialties/{id}/profiles` |
|
||||
| `department-workspace` | Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели, привязка преподавателей, заявки на новых преподавателей и нагрузка | `/api/department/*`, `/api/department/teacher-requests`, `/api/workload/teachers` |
|
||||
| `schedule-view` | Read-only просмотр расписаний: по одной выбранной дате строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а на экране отображается одна активная совмещённая таблица чётной/нечётной недели | `/api/schedule/search` |
|
||||
| `schedule-view` | Просмотр расписаний: семестр выбирается в дополнительных фильтрах, для учебного периода строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а `ADMIN` и `EDUCATION_OFFICE` редактируют конкретное занятие в боковой панели без изменения правила | `/api/schedule/semesters`, `/api/schedule/search`, `/api/edu-office/schedule/overrides`, `/api/admin/time-slots/effective` |
|
||||
| `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/academic-calendars/{id}/subjects`, `/api/admin/calendar/activity-types`, `/api/specialties`, `/api/specialties/{id}/profiles`, `/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` |
|
||||
@@ -169,7 +172,9 @@ frontend/
|
||||
- Вкладка `department-workspace` в блоке преподавателей объединяет данные `/api/department/teachers` и `/api/workload/teachers`: каждый преподаватель показывается одной карточкой с должностью и нагрузкой за выбранный период, преподаватели без занятий получают нулевую нагрузку, а преподаватели из расписания добавляются без дублей. Если дата начала периода выбрана позже даты окончания, поле окончания очищается, а расчёт нагрузки ждёт корректный период.
|
||||
- Вкладка `department-workspace` позволяет кафедре добавить существующего активного преподавателя на свою кафедру через `/api/department/teachers/{teacherId}/assignments`, отправить заявку на нового преподавателя через `/api/department/teacher-requests` и видеть статусы собственных заявок в таблице.
|
||||
- Компоновка `department-workspace` использует собственные CSS-сетки `department-workspace-filter-grid` и `department-workspace-actions-grid`: фильтры периода отделены от сеток расписания, загрузка дисциплин занимает широкую колонку, формы преподавателей выравниваются справа, а списки и таблицы идут полноширинными блоками ниже.
|
||||
- Вкладка `schedule-view` показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; frontend запрашивает двухнедельный диапазон от понедельника выбранной даты и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и роли `DEPARTMENT` расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Бейдж диапазона недель скрывается для занятий на весь семестр, а для занятий до конца семестра показывает только неделю начала в формате `(с 5 нед.)`. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
|
||||
- Вкладка `schedule-view` показывает найденные занятия в режиме одной активной таблицы. Пользователь выбирает, что смотреть: группу, преподавателя, аудиторию или кафедру; в дополнительных фильтрах доступен семестр из справочника `/api/schedule/semesters`, предназначенного только для чтения. Для текущего семестра сохраняется текущая двухнедельная точка просмотра, а при выборе другого семестра диапазон начинается с понедельника его первой недели. Frontend запрашивает две недели и собирает найденные расписания в переключатель результатов. На странице не выводится стек таблиц: виден один выбранный результат, а остальные доступны через чипы и кнопки предыдущего/следующего расписания; чипы результатов переносятся и отделены от счётчика стабильным отступом. Для режима кафедры и роли `DEPARTMENT` расписание ограничивается кафедрой пользователя; преподавательские и студенческие отдельные страницы пока остаются самостоятельными. Таблица строится как строки пар и столбцы дней недели. Нечётная неделя отображается в верхней половине ячейки, чётная — в нижней, а одинаковые занятия в обе недели схлопываются в цельную ячейку. Бейдж диапазона недель скрывается для занятий на весь семестр, а для занятий до конца семестра показывает только неделю начала в формате `(с 5 нед.)`. На мобильной ширине вместо широкой недельной матрицы показывается один день активного расписания с переключателем дней.
|
||||
- Для `ADMIN` и `EDUCATION_OFFICE` карточка занятия содержит кнопку `Изменить`, а уже изменённая пара — индикатор разовой правки. Справа открывается полупрозрачная боковая панель с размытием содержимого под ней; внешний затемнённый слой также размывает страницу, а на мобильном устройстве панель занимает весь экран. Режим `Редактирование` сравнивает `Было по правилу / Станет`, позволяет изменить дату, эффективный временной слот, преподавателя, аудиторию, формат и комментарий, отменить занятие или удалить override через `Вернуть по правилу`. Селект аудитории получает записи из `/api/classrooms`, но показывает только поле `name`, без корпуса и этажа. По умолчанию выводятся семь дней исходной недели; кнопка `Выбрать другую дату` раскрывает календарь всего семестра, где неучебные даты отключены. После смены даты загружается эффективная сетка дня: сначала выбирается тот же ID слота, затем совпадающий интервал, иначе требуется ручной выбор. Дата или время формируют `MOVE`, а только преподаватель, аудитория или формат — `REPLACE`.
|
||||
- Режим `Изменения за период` загружает overrides, у которых в двухнедельный диапазон попала исходная или целевая дата. Поэтому в реестре остаются отменённые занятия и входящие/исходящие переносы, которых нет на исходном месте в таблице. После сохранения, отмены или возврата расписание и реестр обновляются без смены выбранного периода. Для `DEPARTMENT` и `SCHEDULE_VIEWER` вкладка остаётся полностью read-only: кнопка, панель и запросы управляющего API не создаются.
|
||||
- Вкладка `auditorium-workload` стала общей вкладкой `Загруженность`: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из `/api/admin/time-slots/effective`, занятость собирается из динамического расписания `/api/schedule` по группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из `/api/admin/calendar/years`.
|
||||
- Вкладка `university-structure` содержит внутренние разделы `Кафедры`, `Специальности` и `Профили`, оформленные тем же визуальным паттерном вкладок, что и `academic-calendar`: администратор создаёт профили как из общего списка, так и через кнопку `Профили` у конкретной специальности.
|
||||
- Вкладка `schedule` не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате `(с 5 нед.)`, а ограниченный диапазон — как `(с 1 по 3 нед.)`. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними.
|
||||
@@ -282,7 +287,7 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
|
||||
|
||||
- расширенный поиск `/api/schedule/search` по дате в периоде, преподавателю, аудитории и кафедре;
|
||||
- конструктор расписания через `/api/admin/schedule-rules`;
|
||||
- создание точечного изменения пары через `/api/edu-office/schedule/overrides` на уровне API;
|
||||
- разовая правка конкретного занятия через боковую панель: перенос даты/времени, замена ресурсов, отмена и возврат по правилу;
|
||||
- матричный просмотр загруженности аудиторий, преподавателей и кафедр через общую вкладку `Загруженность`;
|
||||
- аудитории, оборудование, календарный график и загруженность.
|
||||
|
||||
@@ -298,7 +303,7 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
|
||||
|
||||
### Просмотр расписаний (`/admin/#schedule-view`)
|
||||
|
||||
Роль `SCHEDULE_VIEWER` видит только read-only вкладку просмотра расписаний. Доступны фильтры по дате в периоде, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия, чётности и разрезу таблиц. Найденные занятия отображаются отдельными совмещёнными таблицами чётной/нечётной недели с парами по строкам и днями недели по столбцам.
|
||||
Роль `SCHEDULE_VIEWER` видит только доступную для чтения вкладку просмотра расписаний. Доступны фильтры по семестру, дате в периоде, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия, чётности и разрезу таблиц. Найденные занятия отображаются отдельными совмещёнными таблицами чётной/нечётной недели с парами по строкам и днями недели по столбцам.
|
||||
|
||||
### Преподаватель (`/teacher/`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user