3 Задача
This commit is contained in:
78
docs/API.md
78
docs/API.md
@@ -912,6 +912,84 @@ API возвращает `409 Conflict`; соседние интервалы и
|
||||
выбранного действия возвращает `409` и откатывает остальные. Незаполненные занятия не
|
||||
меняются. Когда обработаны все оставшиеся занятия, отсутствие получает статус `RESOLVED`.
|
||||
|
||||
### Пожелания преподавателей на семестр
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/teacher-preferences/meta` | Семестры и базовая сетка времени для календаря доступности |
|
||||
| `GET` | `/api/teacher-preferences?semesterId=&status=` | Доступные текущей роли пожелания с фильтрами по семестру и статусу |
|
||||
| `POST` | `/api/teacher-preferences` | Создать ограничение или мягкое пожелание |
|
||||
| `POST` | `/api/teacher-preferences/{id}/review` | Согласовать или отклонить ожидающее пожелание |
|
||||
| `DELETE` | `/api/teacher-preferences/{id}` | Отозвать ожидающее либо отменить действующее пожелание |
|
||||
|
||||
Типы пожеланий: `HARD_UNAVAILABLE` — строгая недоступность по паре недели или целой дате,
|
||||
`SOFT_PREFERRED` — предпочтительный интервал, `SOFT_UNWANTED` — нежелательный интервал,
|
||||
`CONSECUTIVE` — пары подряд, `NO_GAPS` — расписание без окон. Интервальные пожелания
|
||||
используют `dayOfWeek` от 1 до 7 и `timeSlotId` базовой сетки; полная недоступность
|
||||
использует только `preferenceDate` внутри выбранного семестра.
|
||||
|
||||
```json
|
||||
{
|
||||
"semesterId": 4,
|
||||
"preferenceType": "HARD_UNAVAILABLE",
|
||||
"dayOfWeek": 5,
|
||||
"timeSlotId": 3,
|
||||
"comment": "Методический день"
|
||||
}
|
||||
```
|
||||
|
||||
Преподаватель создаёт и видит только собственные пожелания; новая запись получает статус
|
||||
`PENDING`. `ADMIN` и `EDUCATION_OFFICE` работают со всеми преподавателями, а `DEPARTMENT` —
|
||||
только с преподавателями своей кафедры по истории назначений на период семестра. Запись,
|
||||
добавленная ответственным сотрудником, сразу получает статус `APPROVED`. При отклонении
|
||||
поле `comment` в запросе согласования обязательно:
|
||||
|
||||
```json
|
||||
{
|
||||
"approved": false,
|
||||
"comment": "Уточните день полной недоступности"
|
||||
}
|
||||
```
|
||||
|
||||
Согласованная строгая недоступность блокирует сохранение конфликтующего правила и разовой
|
||||
правки. Мягкие пожелания и требования компактности показываются в конструкторе правил как
|
||||
подсказки и не изменяют опубликованное расписание автоматически.
|
||||
|
||||
### Заявки преподавателей на изменение занятия
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/teacher-change-requests?status=` | Доступный текущей роли журнал заявок и история решений |
|
||||
| `GET` | `/api/teacher-change-requests/candidates?baseRuleSlotId=&lessonDate=&targetDate=` | Предварительно проверенные даты, пары и аудитории |
|
||||
| `POST` | `/api/teacher-change-requests` | Отправить заявку по собственному занятию |
|
||||
| `POST` | `/api/teacher-change-requests/{id}/review` | Применить или отклонить заявку |
|
||||
| `DELETE` | `/api/teacher-change-requests/{id}` | Отозвать собственную ожидающую заявку |
|
||||
|
||||
Преподаватель может выбрать только фактически существующее собственное занятие без уже
|
||||
созданного override. Поддерживаются `MOVE`, `CHANGE_CLASSROOM` и `CANCEL`; обоснование
|
||||
обязательно. Для `MOVE` передаются новая пара и, при необходимости, новая дата, для
|
||||
`CHANGE_CLASSROOM` — только новая аудитория. Кандидаты заранее проходят тот же валидатор
|
||||
ресурсных конфликтов, календаря, эффективной сетки времени, отсутствий и строгой
|
||||
недоступности, который используется при применении разовой правки.
|
||||
|
||||
```json
|
||||
{
|
||||
"baseRuleSlotId": 31,
|
||||
"lessonDate": "2026-09-10",
|
||||
"requestType": "MOVE",
|
||||
"targetLessonDate": "2026-09-11",
|
||||
"requestedTimeSlotId": 4,
|
||||
"reason": "Участие в конференции"
|
||||
}
|
||||
```
|
||||
|
||||
`ADMIN` и `EDUCATION_OFFICE` могут принять заявку; backend повторно валидирует её и
|
||||
транзакционно создаёт обычный `schedule_override`, записывает `appliedOverrideId` и событие
|
||||
`APPROVED` в историю. Кафедра видит заявки своих преподавателей, но не применяет изменения
|
||||
расписания. При отклонении комментарий обязателен. Статусы: `PENDING`, `APPROVED`,
|
||||
`REJECTED`, `CANCELLED`; по одному занятию одновременно допускается только одна ожидающая
|
||||
заявка.
|
||||
|
||||
## Загруженность
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|
||||
@@ -343,6 +343,49 @@ constraint. При чтении API разворачивает периоды о
|
||||
|
||||
---
|
||||
|
||||
### Пожелания преподавателей на семестр
|
||||
|
||||
Преподаватель формирует набор пожеланий отдельно для каждого семестра. Интервальные записи
|
||||
привязаны к дню недели и паре базовой сетки времени, полная строгая недоступность — к
|
||||
конкретной дате семестра. Поддерживаются:
|
||||
|
||||
- `HARD_UNAVAILABLE` — строго запрещённый интервал либо полностью недоступная дата;
|
||||
- `SOFT_PREFERRED` и `SOFT_UNWANTED` — предпочтительный и нежелательный интервалы;
|
||||
- `CONSECUTIVE` и `NO_GAPS` — пожелания к компактности расписания.
|
||||
|
||||
Запись преподавателя сначала имеет статус `PENDING`. Кафедра может рассматривать только
|
||||
пожелания преподавателей, относившихся к ней в период семестра; учебный отдел и
|
||||
администратор работают со всеми записями. Ответственный сотрудник также может сразу создать
|
||||
согласованную запись. Отклонение требует комментария, а преподаватель может отозвать только
|
||||
собственную ожидающую запись.
|
||||
|
||||
Только согласованные строгие ограничения влияют на валидацию. `ScheduleRuleService`
|
||||
проверяет каждую активную неделю нового или изменённого правила, а `ScheduleOverrideService`
|
||||
— фактическую дату результата разовой правки. Поэтому строгая недоступность одинаково
|
||||
учитывается конструктором правил, мастером замены и заявками преподавателей. Мягкие
|
||||
пожелания и компактность подсвечиваются в конструкторе, но не меняют опубликованное
|
||||
расписание автоматически.
|
||||
|
||||
### Заявки преподавателей на изменение занятия
|
||||
|
||||
Заявку можно создать только по собственному фактическому занятию, для которого ещё нет
|
||||
разовой правки. Поддерживаются перенос даты/времени (`MOVE`), смена аудитории
|
||||
(`CHANGE_CLASSROOM`) и отмена (`CANCEL`). Одновременно по одной паре допускается только одна
|
||||
заявка `PENDING`.
|
||||
|
||||
До отправки интерфейс получает список ближайших учебных дат, временных слотов и аудиторий.
|
||||
Каждый вариант проходит `ScheduleOverrideService`: проверяются принадлежность семестру,
|
||||
календарный график групп, эффективная сетка времени, жизненный цикл ресурсов, пересечения
|
||||
преподавателя, аудитории, групп и подгрупп, подтверждённые отсутствия и строгая
|
||||
недоступность преподавателя. При создании заявки проверка выполняется повторно.
|
||||
|
||||
Кафедра видит заявки своих преподавателей, но применять изменение вправе только
|
||||
`ADMIN` или `EDUCATION_OFFICE`. При одобрении в одной транзакции повторно проверяется и
|
||||
создаётся обычный `schedule_override`, его ID сохраняется в заявке, а в неизменяемую
|
||||
историю добавляется статус `APPROVED`. Отклонение требует комментария; преподаватель может
|
||||
отозвать только собственную ожидающую заявку. История содержит автора, время, статус и
|
||||
комментарий каждого перехода.
|
||||
|
||||
## Привязка преподаватель ↔ дисциплина
|
||||
|
||||
Связь Many-to-Many через таблицу `teacher_subjects`:
|
||||
|
||||
123
docs/DATABASE.md
123
docs/DATABASE.md
@@ -362,6 +362,50 @@ erDiagram
|
||||
TIMESTAMPTZ decided_at
|
||||
}
|
||||
|
||||
teacher_preferences {
|
||||
BIGSERIAL id PK
|
||||
BIGINT teacher_id FK
|
||||
BIGINT semester_id FK
|
||||
VARCHAR preference_type
|
||||
INT day_of_week
|
||||
BIGINT time_slot_id FK
|
||||
DATE preference_date
|
||||
TEXT comment
|
||||
VARCHAR status
|
||||
BIGINT requested_by FK
|
||||
BIGINT reviewed_by FK
|
||||
TEXT review_comment
|
||||
TIMESTAMPTZ reviewed_at
|
||||
TIMESTAMPTZ created_at
|
||||
TIMESTAMPTZ updated_at
|
||||
}
|
||||
|
||||
teacher_change_requests {
|
||||
BIGSERIAL id PK
|
||||
BIGINT teacher_id FK
|
||||
BIGINT base_rule_slot_id FK
|
||||
DATE lesson_date
|
||||
VARCHAR request_type
|
||||
DATE target_lesson_date
|
||||
BIGINT requested_time_slot_id FK
|
||||
BIGINT requested_classroom_id FK
|
||||
TEXT reason
|
||||
VARCHAR status
|
||||
BIGINT reviewed_by FK
|
||||
BIGINT applied_override_id FK
|
||||
TIMESTAMPTZ created_at
|
||||
TIMESTAMPTZ updated_at
|
||||
}
|
||||
|
||||
teacher_change_request_history {
|
||||
BIGSERIAL id PK
|
||||
BIGINT request_id FK
|
||||
VARCHAR status
|
||||
TEXT comment
|
||||
BIGINT actor_id FK
|
||||
TIMESTAMPTZ created_at
|
||||
}
|
||||
|
||||
schedule_rule_slot_subgroups {
|
||||
BIGINT schedule_rule_slot_id FK,PK
|
||||
BIGINT subgroup_id FK,PK
|
||||
@@ -418,6 +462,16 @@ erDiagram
|
||||
schedule_rule_slots ||--o{ teacher_absence_decisions : "base_rule_slot_id"
|
||||
schedule_overrides ||--o{ teacher_absence_decisions : "schedule_override_id"
|
||||
users ||--o{ teacher_absence_decisions : "decided_by"
|
||||
users ||--o{ teacher_preferences : "teacher_id/requested_by/reviewed_by"
|
||||
semesters ||--o{ teacher_preferences : "semester_id"
|
||||
time_slots ||--o{ teacher_preferences : "time_slot_id"
|
||||
users ||--o{ teacher_change_requests : "teacher_id/reviewed_by"
|
||||
schedule_rule_slots ||--o{ teacher_change_requests : "base_rule_slot_id"
|
||||
time_slots ||--o{ teacher_change_requests : "requested_time_slot_id"
|
||||
classrooms ||--o{ teacher_change_requests : "requested_classroom_id"
|
||||
schedule_overrides ||--o{ teacher_change_requests : "applied_override_id"
|
||||
teacher_change_requests ||--o{ teacher_change_request_history : "request_id"
|
||||
users ||--o{ teacher_change_request_history : "actor_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"
|
||||
@@ -976,6 +1030,69 @@ CHECK запрещает обратный период, пустую или сл
|
||||
одно занятие одного инцидента. При удалении override ссылка обнуляется, но аудиторская запись
|
||||
сохраняется.
|
||||
|
||||
#### `teacher_preferences` — Пожелания преподавателей на семестр
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID пожелания |
|
||||
| `teacher_id` | BIGINT FK → users | Преподаватель |
|
||||
| `semester_id` | BIGINT FK → semesters | Семестр действия |
|
||||
| `preference_type` | VARCHAR(30) | `HARD_UNAVAILABLE`, `SOFT_PREFERRED`, `SOFT_UNWANTED`, `CONSECUTIVE` или `NO_GAPS` |
|
||||
| `day_of_week` | SMALLINT NULL | День недели 1–7 для интервального пожелания |
|
||||
| `time_slot_id` | BIGINT FK → time_slots NULL | Пара базовой сетки времени |
|
||||
| `preference_date` | DATE NULL | Полностью недоступная дата при `HARD_UNAVAILABLE` |
|
||||
| `comment` | TEXT | Комментарий преподавателя |
|
||||
| `status` | VARCHAR(20) | `PENDING`, `APPROVED`, `REJECTED`, `CANCELLED` |
|
||||
| `requested_by` | BIGINT FK → users | Автор записи |
|
||||
| `reviewed_by` | BIGINT FK → users | Автор решения |
|
||||
| `review_comment` | TEXT | Комментарий согласования |
|
||||
| `reviewed_at` | TIMESTAMPTZ | Время решения |
|
||||
| `created_at`, `updated_at` | TIMESTAMPTZ | Аудит жизненного цикла |
|
||||
|
||||
CHECK-ограничение разделяет три допустимые формы записи: строгая недоступность по целой
|
||||
дате, строгое или мягкое пожелание по паре недели, либо пожелание компактности без даты и
|
||||
пары. Частичные индексы ускоряют проверку согласованной недоступности по интервалу и дате.
|
||||
Семантическая уникальность активных пожеланий проверяется сервисом, поскольку PostgreSQL
|
||||
считает значения `NULL` различными.
|
||||
|
||||
#### `teacher_change_requests` — Заявки на изменение занятия
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID заявки |
|
||||
| `teacher_id` | BIGINT FK → users | Преподаватель — автор заявки |
|
||||
| `base_rule_slot_id` | BIGINT FK → schedule_rule_slots | Базовый слот занятия |
|
||||
| `lesson_date` | DATE | Исходная дата занятия |
|
||||
| `request_type` | VARCHAR(30) | `MOVE`, `CHANGE_CLASSROOM` или `CANCEL` |
|
||||
| `target_lesson_date` | DATE NULL | Целевая дата переноса |
|
||||
| `requested_time_slot_id` | BIGINT FK → time_slots NULL | Запрошенная пара при переносе |
|
||||
| `requested_classroom_id` | BIGINT FK → classrooms NULL | Запрошенная аудитория |
|
||||
| `reason` | TEXT | Обязательное обоснование длиной 1–2000 символов |
|
||||
| `status` | VARCHAR(20) | `PENDING`, `APPROVED`, `REJECTED`, `CANCELLED` |
|
||||
| `reviewed_by` | BIGINT FK → users | Сотрудник учебного отдела |
|
||||
| `review_comment` | TEXT | Комментарий решения |
|
||||
| `reviewed_at` | TIMESTAMPTZ | Время решения |
|
||||
| `applied_override_id` | BIGINT FK → schedule_overrides NULL | Разовая правка одобренной заявки |
|
||||
| `created_at`, `updated_at` | TIMESTAMPTZ | Аудит жизненного цикла |
|
||||
|
||||
CHECK фиксирует допустимую форму каждого типа заявки. Частичный уникальный индекс по
|
||||
`(base_rule_slot_id, lesson_date)` запрещает две одновременно ожидающие заявки на одну пару.
|
||||
При удалении применённого override ссылка обнуляется, но заявка и история сохраняются.
|
||||
|
||||
#### `teacher_change_request_history` — История решений по заявке
|
||||
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID события |
|
||||
| `request_id` | BIGINT FK → teacher_change_requests (CASCADE) | Заявка |
|
||||
| `status` | VARCHAR(20) | Зафиксированный статус заявки |
|
||||
| `comment` | TEXT | Обоснование или комментарий решения |
|
||||
| `actor_id` | BIGINT FK → users | Автор перехода |
|
||||
| `created_at` | TIMESTAMPTZ | Время события |
|
||||
|
||||
Записи добавляются при создании, принятии, отклонении и отзыве заявки. Индекс
|
||||
`(request_id, created_at, id)` обеспечивает стабильный хронологический вывод журнала.
|
||||
|
||||
---
|
||||
|
||||
## Flyway миграции
|
||||
@@ -996,12 +1113,14 @@ CHECK запрещает обратный период, пустую или сл
|
||||
|------|----------|
|
||||
| `V1__init.sql` | Полная baseline-схема: справочники, роли, refresh-сессии JWT, PostgreSQL rate limit и аудит входа, lifecycle-поля, история кафедр, календарные графики с интервальным хранением активностей и нумерацией недель `понедельник–воскресенье`, динамическое расписание, точечные изменения с переносом даты, seed, CHECK/UNIQUE/GiST-ограничения, конкурентно безопасные триггеры и комментарии |
|
||||
| `V2__teacher_absences_and_replacement_wizard.sql` | Реестр отсутствий преподавателей, статусы согласования и журнал применённых/отклонённых решений со ссылками на обычные `schedule_overrides` |
|
||||
| `V3__teacher_preferences_and_change_requests.sql` | Пожелания преподавателей на семестр, строгая и мягкая доступность, заявки на изменение занятия и неизменяемая история решений |
|
||||
|
||||
### Этап разработки
|
||||
|
||||
Исторические разработческие миграции V2–V7 по прямому решению владельца проекта были
|
||||
объединены в baseline `V1`. После фиксации baseline нумерация начата заново: текущая `V2`
|
||||
добавляет отсутствия и мастер замены, не изменяя контрольную сумму `V1`.
|
||||
объединены в baseline `V1`. После фиксации baseline нумерация начата заново: `V2`
|
||||
добавляет отсутствия и мастер замены, а `V3` — пожелания преподавателей и заявки на
|
||||
изменение занятий, не изменяя контрольную сумму `V1`.
|
||||
Интервальное хранение активностей и правильная нумерация недель календарного графика входят
|
||||
непосредственно в V1.
|
||||
Перед применением этой редакции требуется полностью пустая tenant-схема.
|
||||
|
||||
@@ -162,6 +162,11 @@
|
||||
|
||||
## 3. Пожелания преподавателей и заявки на перенос
|
||||
|
||||
**Статус: реализовано в MVP.** Добавлены согласуемый календарь доступности, строгие и мягкие
|
||||
пожелания, требования «пары подряд» и «без окон», заявки на перенос, смену аудитории и отмену
|
||||
с предварительной проверкой конфликтов, историей решений и применением через
|
||||
`schedule_overrides`.
|
||||
|
||||
### Проблема и пользователи
|
||||
|
||||
Кабинет преподавателя сейчас предназначен только для просмотра недельного расписания.
|
||||
|
||||
@@ -35,6 +35,7 @@ frontend/
|
||||
│ ├── schedule-overrides.test.mjs # Действия, роли, недельный выбор и подбор времени разовой правки
|
||||
│ ├── schedule-view-semesters.test.mjs # Выбор семестра и расчёт двухнедельного диапазона просмотра
|
||||
│ ├── teacher-absences.test.mjs # Payload мастера замены и доступность вкладки по ролям
|
||||
│ ├── teacher-preferences.test.mjs # Календарь пожеланий, заявки и подсказки конструктора
|
||||
│ └── security-policy.test.mjs # Web Storage, XSS, пароли, язык, CSP и Dockerfile
|
||||
├── index.html # 🔐 Страница авторизации (общая)
|
||||
├── script.js # Логика авторизации
|
||||
@@ -51,7 +52,7 @@ frontend/
|
||||
│ │ ├── modals.css # Модальные окна
|
||||
│ │ ├── auditorium-workload.css # Таблицы расписаний и загруженности
|
||||
│ │ ├── departments-data.css # Стили создания кафедры/специальности
|
||||
│ │ └── teacher-absences.css # Реестр инцидентов и полноэкранный мастер замены
|
||||
│ │ └── teacher-absences.css # Отсутствия, пожелания и очередь заявок преподавателей
|
||||
│ ├── js/
|
||||
│ │ ├── main.js # Инициализация, маршрутизация, навигация
|
||||
│ │ ├── role-capabilities.js # Единая матрица вкладок admin/settings по ролям
|
||||
@@ -70,8 +71,8 @@ frontend/
|
||||
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
|
||||
│ │ ├── schedule-view.js # Просмотр расписаний и запуск разовой правки из карточки
|
||||
│ │ ├── schedule-override-panel.js # Боковая панель и реестр разовых изменений
|
||||
│ │ ├── teacher-absences.js # Реестр, согласование и групповой мастер замены
|
||||
│ │ ├── schedule.js # Конструктор правил расписания
|
||||
│ │ ├── teacher-absences.js # Отсутствия, согласование пожеланий и заявок на изменение
|
||||
│ │ ├── schedule.js # Конструктор правил и подсказки пожеланий преподавателей
|
||||
│ │ ├── academic-calendar-grid.js # Расчёт ISO-недели дневной сетки
|
||||
│ │ ├── academic-calendar-title.js # Название из кода, профиля, формы и года
|
||||
│ │ ├── academic-calendar.js # Календарные учебные графики
|
||||
@@ -105,9 +106,9 @@ frontend/
|
||||
│ └── time-slots.html # Базовая, субботняя и ручные сетки времени
|
||||
│
|
||||
├── teacher/ # 👩🏫 Интерфейс преподавателя
|
||||
│ ├── index.html # CSP-совместимая HTML-оболочка
|
||||
│ ├── app.js # Недельный просмотр и общий auth-session
|
||||
│ └── style.css # Стили кабинета без inline-блока
|
||||
│ ├── index.html # Расписание, календарь доступности и журнал заявок
|
||||
│ ├── app.js # Пожелания, переносы и недельный просмотр через общий auth-session
|
||||
│ └── style.css # Адаптивный кабинет в стиле диспетчерского журнала
|
||||
├── department/ # 🏛 Кабинет кафедры
|
||||
│ └── index.html # Redirect в `/admin/#department-workspace`
|
||||
├── edu-office/ # 🗓 Кабинет учебного отдела
|
||||
@@ -170,8 +171,8 @@ frontend/
|
||||
| `university-structure` | Кафедры, специальности и профили обучения; профили доступны отдельной внутренней вкладкой и через кнопку специальности | `/api/departments`, `/api/specialties`, `/api/specialties/{id}/profiles` |
|
||||
| `department-workspace` | Кабинет кафедры: дисциплины, импорт, комментарии, преподаватели, привязка преподавателей, заявки на новых преподавателей и нагрузка | `/api/department/*`, `/api/department/teacher-requests`, `/api/workload/teachers` |
|
||||
| `schedule-view` | Просмотр расписаний: семестр выбирается в дополнительных фильтрах, для учебного периода строится двухнедельный диапазон, найденные расписания выбираются в переключателе, а `ADMIN` и `EDUCATION_OFFICE` редактируют конкретное занятие в боковой панели без изменения правила | `/api/schedule/semesters`, `/api/schedule/search`, `/api/edu-office/schedule/overrides`, `/api/admin/time-slots/effective` |
|
||||
| `teacher-absences` | Реестр отсутствий, подтверждение преподавательских заявок, проверенные варианты замен/переносов и журнал решений | `/api/teacher-absences`, `/api/users/teachers` |
|
||||
| `schedule` | Конструктор правил динамического расписания с выезжающей визуальной матрицей групп по дням и времени | `/api/admin/schedule-rules`, `/api/admin/time-slots`, `/api/admin/calendar/years`, `/api/lesson-types`, `/api/subgroups` |
|
||||
| `teacher-absences` | Запросы преподавателей: отсутствия и мастер замены, согласование семестровых пожеланий, заявки на перенос, аудиторию или отмену | `/api/teacher-absences`, `/api/teacher-preferences`, `/api/teacher-change-requests`, `/api/users/teachers` |
|
||||
| `schedule` | Конструктор правил динамического расписания с подсказками согласованных пожеланий и выезжающей визуальной матрицей групп | `/api/admin/schedule-rules`, `/api/teacher-preferences`, `/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` |
|
||||
|
||||
@@ -190,14 +191,19 @@ frontend/
|
||||
командный блок показывает очередь инцидентов, форма регистрирует преподавателя, период и
|
||||
причину, а реестр разделяет статусы согласования. Полноэкранный мастер выводит каждое
|
||||
затронутое занятие отдельной строкой и предлагает только кандидатов, уже проверенных
|
||||
backend. Пустая строка не отправляется; выбранные решения применяются одним пакетом.
|
||||
backend. Пустая строка не отправляется; выбранные решения применяются одним пакетом. Ниже
|
||||
расположены реестр пожеланий на семестр и очередь заявок на изменение опубликованных пар.
|
||||
Пожелание можно принять или отклонить с комментарием. Заявку применяют только
|
||||
`ADMIN` и `EDUCATION_OFFICE`; кафедра видит записи своих преподавателей без управляющих
|
||||
действий. Каждая карточка показывает исходное и запрошенное состояние, результат
|
||||
предварительной проверки и хронологию решения.
|
||||
- Компоновка `department-workspace` использует собственные CSS-сетки `department-workspace-filter-grid` и `department-workspace-actions-grid`: фильтры периода отделены от сеток расписания, загрузка дисциплин занимает широкую колонку, формы преподавателей выравниваются справа, а списки и таблицы идут полноширинными блоками ниже.
|
||||
- Вкладка `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`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Единственный слот имеет действие `Очистить`, которое сбрасывает его поля; при наличии нескольких слотов у каждой строки показывается действие `Удалить`. Оба действия используют размер формы, минимальную ширину `125px` и высоту `44px`, поэтому совпадают по масштабу с соседними селектами. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате `(с 5 нед.)`, а ограниченный диапазон — как `(с 1 по 3 нед.)`. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними.
|
||||
- Вкладка `schedule` имеет заголовок `Конструктор правил` и не обращается к старым `lessons` API. Создание и редактирование расписания выполняется через правила `/api/admin/schedule-rules`, где каждое правило содержит группы, отдельные часы и недели начала для лекций, лабораторных и практик, а также набор базовых слотов. Для выбранных преподавателя и семестра frontend загружает согласованные `/api/teacher-preferences` и выводит под строкой слота компактные маркеры строгой недоступности, предпочтительного или нежелательного времени и пожеланий «пары подряд»/«без окон»; количество недоступных дат показывается отдельным маркером. Группы выбираются через выпадающий мультиселект. Поле подгруппы появляется только при выборе лабораторной работы; для лекций и практик оно не отображается. Если в правиле выбрана одна группа, селект подгруппы содержит пункт `Вся группа`; если выбрано несколько групп, лабораторный слот показывает мультиселект подгрупп, чтобы выбрать разные подгруппы разных групп. Типы занятий в слоте сортируются в порядке: лекция, лабораторная работа, практика. Единственный слот имеет действие `Очистить`, которое сбрасывает его поля; при наличии нескольких слотов у каждой строки показывается действие `Удалить`. Оба действия используют размер формы, минимальную ширину `125px` и высоту `44px`, поэтому совпадают по масштабу с соседними селектами. Список слотов отображается без внутреннего вертикального скролла: при добавлении строк форма расширяется вниз, а кнопка сохранения остаётся отдельным блоком под слотами. Из календарной системы здесь используется список семестров для выбора периода действия правила. Справа доступна сворачиваемая визуальная матрица: пользователь выбирает учебный год, семестр и группы, после чего матрица строится только по правилам выбранного семестра. Столбцы — выбранные в фильтре группы, строки — только день и время, где есть активные пары, ячейки показывают дисциплину, диапазон недель, тип, формат, преподавателя, аудиторию и подгруппы. Период недель не показывается для занятия на весь семестр; если занятие идёт до конца семестра не с первой недели, выводится только неделя начала в формате `(с 5 нед.)`, а ограниченный диапазон — как `(с 1 по 3 нед.)`. Если нечётная и чётная недели отличаются, ячейка делится на две половины; одинаковые занятия схлопываются в цельную ячейку. Кнопка с тремя точками в правой части карточки пары открывает контекстное меню: можно открыть полное правило в форме, изменить только день и базовую пару выбранного слота через компактную модалку или удалить правило целиком. В списке правил действия отображаются едиными кнопками одинакового размера с отступами между ними.
|
||||
- Вкладка `academic-calendar` полностью отделяет календарную систему от расписания занятий и внутри себя разделена на три вкладки: `Графики` для учебных годов, семестров и карточек календарных графиков, `Сетки` для редактора дневной сетки, `Дисциплины` для ручной привязки дисциплин из `/api/subjects` к номерам учебных семестров графика. У календарного графика нет поля ввода и отдельного столбца названия; отображаемая подпись и сохраняемое backend название автоматически собираются как `код специальности — профиль обучения — форма обучения — учебный год`. Frontend не отправляет `title` в payload. Администратор выбирает форму обучения из общего справочника `/api/education-forms`, заполняет дневную сетку по курсам и кодам активностей, назначает ручную временную сетку на конкретную дату, а сохранение сетки идёт через `/api/admin/academic-calendars/{id}/grid`. REST-контракт остаётся дневным: backend объединяет соседние даты с одинаковой активностью в периоды при записи и разворачивает их при чтении. Все шесть полей дат принимают до восьми цифр, автоматически показывают их в формате `ДД.ММ.ГГГГ`, ограничивают год четырьмя цифрами и перед отправкой преобразуют значение в ISO `ГГГГ-ММ-ДД`.
|
||||
- При сохранении правила во вкладке `schedule` frontend различает `409 Conflict` от остальных ошибок API. Если backend возвращает `conflictRule`, открывается широкое модальное окно разрешения конфликта: пользователь видит новое и ранее созданное правило, чипы причины конфликта из `conflictReasons` и подсветку соответствующих полей (`teacher`, `classroom`, `group`). Пользователь меняет у конфликтующего правила день, чётность, пару, преподавателя или аудиторию, после чего frontend сохраняет конфликтующее правило через `PUT /api/admin/schedule-rules/{id}` и автоматически повторяет исходный `POST` или `PUT`. Если после переноса появляется следующий конфликт, показывается следующее конфликтующее правило без потери исходного черновика.
|
||||
- Редактор годового графика во вкладке `academic-calendar` показывает компактную табличную сетку: курсы раскрываются отдельными секциями со стрелкой, семестры внутри курса на широком экране идут рядом в две равные колонки одинаковой высоты, столбцы подписаны номерами недель учебного года, строки — днями недели, а ячейки содержат буквенный код активности. Недели выровнены по интервалам `понедельник–воскресенье`: если учебный год начинается не в понедельник, предшествующие позиции первой недели остаются пустыми, а следующий понедельник открывает неделю 2. В рамках одного курса семестровые таблицы получают одинаковое число недельных колонок: недостающие колонки заполняются пустыми ячейками, поэтому левая и правая части занимают всю ширину секции курса без внешней пустоты. Подробная расшифровка и изменение кода открываются в компактном модальном окне по клику на ячейку; при наведении возле курсора отображается кастомная подсказка с датой, курсом, кодом активности и временной сеткой. У границ viewport подсказка автоматически раскрывается в противоположную сторону и остаётся полностью видимой, а код активности в ней центрируется внутри квадратного индикатора. Ячейки можно выделять протяжкой мышью и менять код активности через ту же модалку, выбранные ячейки подсвечиваются мягкой заливкой, а кнопка «Применить» закрывает окно.
|
||||
@@ -327,14 +333,23 @@ if (!session || !AUTHORIZED_ROLES.includes(session.role)) {
|
||||
|
||||
### Преподаватель (`/teacher/`)
|
||||
|
||||
Страница показывает недельную сетку занятий преподавателя. ID преподавателя берётся из
|
||||
восстановленного в памяти профиля сессии.
|
||||
Страница объединяет недельную сетку занятий, семестровый календарь доступности и журнал
|
||||
заявок на изменение пар. ID преподавателя берётся из восстановленного в памяти профиля
|
||||
сессии.
|
||||
|
||||
Основные элементы:
|
||||
- навигация по неделям: предыдущая, текущая, следующая;
|
||||
- выбор даты через `input[type="date"]`;
|
||||
- запрос `GET /api/schedule?teacherId={userId}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}`;
|
||||
- отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила.
|
||||
- отображение дисциплины, времени, типа занятия, лабораторных подгрупп, аудитории и всех групп правила;
|
||||
- вкладка «Мои пожелания» с выбором семестра, режимом отметки строгих, предпочтительных и
|
||||
нежелательных интервалов, полностью недоступных дат, пар подряд и расписания без окон;
|
||||
- цветовые статусы `PENDING`, `APPROVED`, `REJECTED`, `CANCELLED`; ожидающее пожелание можно
|
||||
отозвать кликом по отмеченной ячейке или кнопкой в списке дат;
|
||||
- кнопка «Заявка на изменение» в карточке фактического занятия и модальное окно переноса,
|
||||
смены аудитории или отмены. Недоступные кандидаты отфильтрованы после проверки backend;
|
||||
- вкладка журнала заявок с причиной, решением, ссылкой на применённый override и историей
|
||||
статусов; ожидающую заявку можно отозвать;
|
||||
- форма собственной заявки на отсутствие через `POST /api/teacher-absences`;
|
||||
- список статусов заявок и отмена ещё не подтверждённой записи через
|
||||
`DELETE /api/teacher-absences/{id}`.
|
||||
|
||||
Reference in New Issue
Block a user