ролевая моделю доступа + загруженность по кафедрам и преподавателям + просморт расписания
This commit is contained in:
164
docs/API.md
164
docs/API.md
@@ -46,6 +46,32 @@
|
||||
|
||||
> После получения токена клиент должен передавать его в заголовке: `Authorization: Bearer <token>`
|
||||
|
||||
Поддерживаемые роли: `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER`, `TEACHER`, `STUDENT`.
|
||||
|
||||
Redirect по ролям:
|
||||
|
||||
| Роль | Redirect |
|
||||
|------|----------|
|
||||
| `ADMIN` | `/admin/` |
|
||||
| `EDUCATION_OFFICE` | `/admin/#schedule-view` |
|
||||
| `DEPARTMENT` | `/admin/#department-workspace` |
|
||||
| `SCHEDULE_VIEWER` | `/admin/#schedule-view` |
|
||||
| `TEACHER` | `/teacher/` |
|
||||
| `STUDENT` | `/student/` |
|
||||
|
||||
### `GET /api/auth/me`
|
||||
|
||||
Возвращает текущего пользователя по bearer-токену.
|
||||
|
||||
```json
|
||||
{
|
||||
"userId": 1,
|
||||
"username": "admin",
|
||||
"role": "ADMIN",
|
||||
"departmentId": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Пользователи
|
||||
@@ -89,13 +115,50 @@
|
||||
**Валидация:**
|
||||
- `username` — обязателен и уникален
|
||||
- `password` — минимум 4 символа
|
||||
- `role` — `ADMIN`, `TEACHER` или `STUDENT`
|
||||
- `role` — `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER`, `TEACHER` или `STUDENT`
|
||||
- `fullName` — обязателен
|
||||
- `departmentId` — обязателен
|
||||
|
||||
### `DELETE /api/users/{id}`
|
||||
|
||||
Удаление пользователя.
|
||||
Архивирование пользователя. Исторические связи и расписание остаются в БД, но пользователь больше не может войти.
|
||||
|
||||
### `POST /api/users/{id}/restore`
|
||||
|
||||
Восстановление архивного пользователя.
|
||||
|
||||
### `GET /api/users/{id}/department-history`
|
||||
|
||||
История переводов преподавателя между кафедрами.
|
||||
|
||||
### `POST /api/users/{id}/department-transfer`
|
||||
|
||||
Перевод преподавателя на другую кафедру без потери прошлых связей.
|
||||
|
||||
```json
|
||||
{
|
||||
"departmentId": 2,
|
||||
"validFrom": "2026-06-01",
|
||||
"comment": "Перевод на кафедру ВТ"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/users/teachers/by-department/{departmentId}?date=2026-06-01`
|
||||
|
||||
Список преподавателей кафедры на конкретную дату по таблице истории.
|
||||
|
||||
---
|
||||
|
||||
## Права ролей на API
|
||||
|
||||
Скрытие вкладок во frontend не является защитой. Все `/api/**` запросы, кроме `POST /api/auth/login`, проходят через bearer-токен и `@RequireRoles`.
|
||||
|
||||
Важные ограничения:
|
||||
|
||||
- `DEPARTMENT` не может создавать аудитории, кафедры, специальности, группы, пользователей или правила расписания через API;
|
||||
- `DEPARTMENT` создаёт и комментирует дисциплины через `/api/department/*`, где кафедра берётся из текущего пользователя;
|
||||
- `/api/teacher-subjects` для `DEPARTMENT` разрешает связывать только преподавателей и дисциплины своей кафедры;
|
||||
- `SCHEDULE_VIEWER` имеет read-only доступ к просмотру расписаний, справочникам-фильтрам и загруженности.
|
||||
|
||||
---
|
||||
|
||||
@@ -334,6 +397,97 @@ CRUD доступен по:
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/schedule/search`
|
||||
|
||||
Расширенный поиск расписания. В отличие от `GET /api/schedule`, принимает несколько фильтров одновременно.
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `startDate` / `endDate` | Обязательный период |
|
||||
| `groupId` | Учебная группа |
|
||||
| `teacherId` | Преподаватель |
|
||||
| `classroomId` | Аудитория |
|
||||
| `departmentId` | Кафедра |
|
||||
| `subjectId` | Дисциплина |
|
||||
| `lessonTypeId` | Тип занятия |
|
||||
| `timeSlotId` | Временной слот |
|
||||
| `parity` | `BOTH`, `ODD`, `EVEN` |
|
||||
|
||||
Пример:
|
||||
|
||||
```http
|
||||
GET /api/schedule/search?classroomId=1&startDate=2026-05-20&endDate=2026-05-27
|
||||
```
|
||||
|
||||
Ответ совпадает со структурой `RenderedLessonDto` из `GET /api/schedule`.
|
||||
|
||||
### Точечные изменения расписания учебного отдела
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/edu-office/schedule/overrides` | Список точечных изменений |
|
||||
| `POST` | `/api/edu-office/schedule/overrides` | Создать перенос, отмену или замену |
|
||||
| `PUT` | `/api/edu-office/schedule/overrides/{id}` | Обновить изменение |
|
||||
| `DELETE` | `/api/edu-office/schedule/overrides/{id}` | Удалить изменение |
|
||||
|
||||
```json
|
||||
{
|
||||
"baseRuleSlotId": 31,
|
||||
"lessonDate": "2026-05-21",
|
||||
"action": "REPLACE",
|
||||
"newClassroomId": 2,
|
||||
"newTeacherId": 5,
|
||||
"comment": "Замена аудитории и преподавателя"
|
||||
}
|
||||
```
|
||||
|
||||
`action=CANCEL` отменяет конкретную пару. `MOVE` и `REPLACE` могут менять аудиторию, преподавателя, формат и временной слот.
|
||||
|
||||
## Загруженность
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/workload/teachers` | Загруженность преподавателей |
|
||||
| `GET` | `/api/workload/classrooms` | Загруженность аудиторий |
|
||||
| `GET` | `/api/workload/departments` | Загруженность кафедр |
|
||||
| `GET` | `/api/workload/time-slots` | Загруженность по парам |
|
||||
| `GET` | `/api/workload/free-classrooms` | Свободные аудитории на дату и пару |
|
||||
|
||||
Общие параметры для отчётов: `startDate`, `endDate`, опционально `departmentId`.
|
||||
|
||||
Пример:
|
||||
|
||||
```http
|
||||
GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-01
|
||||
```
|
||||
|
||||
Ответ:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Петров Препод Петрович",
|
||||
"departmentId": 1,
|
||||
"departmentName": "Кафедра ИБ",
|
||||
"lessonCount": 8,
|
||||
"academicHours": 16,
|
||||
"occupiedSlotCount": 8
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Кабинет кафедры
|
||||
|
||||
| Метод | URL | Назначение |
|
||||
|-------|-----|------------|
|
||||
| `GET` | `/api/department/subjects` | Дисциплины текущей кафедры |
|
||||
| `POST` | `/api/department/subjects/import` | Загрузка списка дисциплин |
|
||||
| `GET` | `/api/department/subjects/{subjectId}/comments` | Комментарии дисциплины |
|
||||
| `POST` | `/api/department/subjects/{subjectId}/comments` | Добавить комментарий |
|
||||
| `GET` | `/api/department/teachers` | Преподаватели кафедры |
|
||||
| `GET` | `/api/department/schedule` | Расписание кафедры |
|
||||
|
||||
---
|
||||
|
||||
## Кафедры и специальности
|
||||
@@ -557,7 +711,11 @@ CRUD доступен по:
|
||||
|
||||
### `DELETE /api/classrooms/{id}`
|
||||
|
||||
Удаление аудитории.
|
||||
Архивирование аудитории. Архивная аудитория остаётся в историческом расписании, но не выбирается в новых назначениях.
|
||||
|
||||
### `POST /api/classrooms/{id}/restore`
|
||||
|
||||
Восстановление архивной аудитории.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ graph TD
|
||||
- **Порт:** 8080 (внутренний)
|
||||
- **ORM:** Hibernate (JPA), `ddl-auto=none`
|
||||
- **Миграции:** Flyway (программный запуск при подключении тенанта)
|
||||
- **Аутентификация:** bcrypt (через `BCryptPasswordEncoder`), UUID-токены
|
||||
- **Аутентификация:** bcrypt (через `BCryptPasswordEncoder`), in-memory UUID-сессии, bearer-токены и backend-проверка ролей
|
||||
|
||||
### PostgreSQL
|
||||
- **Версия:** `postgres:alpine3.23`
|
||||
@@ -131,12 +131,16 @@ sequenceDiagram
|
||||
|
||||
## Аутентификация
|
||||
|
||||
Система использует **простую модель аутентификации** без JWT или Spring Security фильтров:
|
||||
Система использует простую модель аутентификации без JWT и без полноценного Spring Security:
|
||||
|
||||
1. Клиент отправляет `POST /api/auth/login` с `username` и `password`
|
||||
2. Backend проверяет пароль через `BCryptPasswordEncoder`
|
||||
3. При успехе возвращается:
|
||||
- UUID-токен (для заголовка `Authorization: Bearer`)
|
||||
- Роль пользователя (`ADMIN`, `TEACHER`, `STUDENT`)
|
||||
- Redirect URL (`/admin/`, `/teacher/`, `/student/`)
|
||||
- Роль пользователя (`ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT`, `SCHEDULE_VIEWER`, `TEACHER`, `STUDENT`)
|
||||
- Redirect URL (`/admin/`, `/admin/#schedule-view`, `/admin/#department-workspace`, `/teacher/`, `/student/`)
|
||||
4. Токен хранится в `localStorage` на клиенте
|
||||
|
||||
Токены хранятся в `AuthSessionService` в памяти процесса backend. `AuthorizationInterceptor` проверяет bearer-токен для `/api/**`, кроме `POST /api/auth/login`, и применяет аннотацию `@RequireRoles` на контроллерах и методах. Это означает, что UI-роль в `localStorage` больше не является единственной защитой: backend возвращает `401`, если токена нет, и `403`, если роли недостаточно.
|
||||
|
||||
`TenantInterceptor` по-прежнему отвечает за выбор БД тенанта по домену. Проверка авторизации выполняется отдельным интерцептором после tenant-resolution.
|
||||
|
||||
@@ -2,19 +2,33 @@
|
||||
|
||||
## Ролевая модель
|
||||
|
||||
Система поддерживает три роли пользователей:
|
||||
Система поддерживает шесть ролей пользователей:
|
||||
|
||||
| Роль | Enum | Возможности |
|
||||
|------|------|------------|
|
||||
| **Администратор** (Деканат) | `ADMIN` | Полный доступ: CRUD пользователей, групп, аудиторий, дисциплин, расписания. Управление тенантами (БД). |
|
||||
| **Администратор** | `ADMIN` | Полный доступ: пользователи, справочники, тенанты, роли, архивирование и восстановление. |
|
||||
| **Учебный отдел** | `EDUCATION_OFFICE` | Редактирование расписания, точечные переносы/замены/отмены, временные слоты, аудитории, загруженность. |
|
||||
| **Кафедра** | `DEPARTMENT` | Дисциплины своей кафедры, загрузка дисциплин, привязки преподавателей, комментарии, расписание и нагрузка кафедры. |
|
||||
| **Просмотр расписаний** | `SCHEDULE_VIEWER` | Read-only просмотр расписаний по группам, преподавателям, аудиториям и кафедрам в недельных матричных таблицах. |
|
||||
| **Преподаватель** | `TEACHER` | Просмотр своего расписания. В перспективе — подача заявок на перенос. |
|
||||
| **Студент** | `STUDENT` | Только просмотр расписания (Read-only). |
|
||||
|
||||
После авторизации пользователь перенаправляется на свой интерфейс:
|
||||
- `ADMIN` → `/admin/`
|
||||
- `EDUCATION_OFFICE` → `/admin/#schedule-view`
|
||||
- `DEPARTMENT` → `/admin/#department-workspace`
|
||||
- `SCHEDULE_VIEWER` → `/admin/#schedule-view`
|
||||
- `TEACHER` → `/teacher/`
|
||||
- `STUDENT` → `/student/`
|
||||
|
||||
Bearer-токен проверяется на backend. Frontend-скрытие пунктов меню является только удобством, а не источником прав.
|
||||
|
||||
Для роли `DEPARTMENT` backend дополнительно ограничивает изменения рамками кафедры текущего пользователя:
|
||||
|
||||
- загрузка и комментарии дисциплин идут через `/api/department/*`;
|
||||
- общий `/api/subjects` доступен кафедре только на чтение;
|
||||
- привязки `/api/teacher-subjects` разрешены только если преподаватель и дисциплина относятся к кафедре текущего пользователя.
|
||||
|
||||
---
|
||||
|
||||
## Управление ресурсами
|
||||
@@ -52,6 +66,7 @@
|
||||
- **Поля:** Название (уникальное), вместимость (> 0), корпус, этаж, доступность
|
||||
- **Оборудование:** К каждой аудитории привязывается список оборудования (Many-to-Many) с указанием количества
|
||||
- **Статус:** Флаг `is_available` для блокирования назначения пар
|
||||
- **Жизненный цикл:** `status=ARCHIVED` означает вывод из эксплуатации. Такая аудитория отображается в прошлом расписании, но запрещена для новых назначений.
|
||||
|
||||
### Оборудование (Equipments)
|
||||
|
||||
@@ -64,6 +79,26 @@
|
||||
|
||||
- **Поля:** Название (уникальное), код, кафедра, описание
|
||||
- Привязка преподавателей через `teacher_subjects` (Many-to-Many)
|
||||
- Кафедра может добавлять комментарии к дисциплине и загружать список дисциплин через кабинет кафедры
|
||||
|
||||
### Жизненный цикл справочников
|
||||
|
||||
Справочники, которые участвуют в расписании и отчётах, не удаляются физически. Для них используется архивирование:
|
||||
|
||||
- `ACTIVE` — запись доступна для выбора;
|
||||
- `ARCHIVED` — запись остаётся в истории, но не используется в будущих назначениях.
|
||||
|
||||
Архивирование уже применяется к пользователям, аудиториям, оборудованию, кафедрам, специальностям, группам, подгруппам, дисциплинам и профилям обучения. Исторические отчёты используют записи, действовавшие на дату занятия.
|
||||
|
||||
### Перевод преподавателей между кафедрами
|
||||
|
||||
Текущая кафедра преподавателя хранится в `users.department_id`, но история переводов фиксируется в `teacher_department_assignments`.
|
||||
|
||||
Правила:
|
||||
|
||||
- у преподавателя должна быть одна открытая основная кафедра;
|
||||
- при переводе старая запись закрывается датой `valid_to`, новая открывается с `valid_from`;
|
||||
- расписание и отчёты за прошлые периоды не теряют связь с прежней кафедрой.
|
||||
|
||||
---
|
||||
|
||||
@@ -86,6 +121,7 @@
|
||||
| `schedule_rule_groups` | Группы правила, включая потоковые лекции |
|
||||
| `schedule_rule_slots` | День, чётность, слот, преподаватель, аудитория, тип и формат занятия |
|
||||
| `schedule_rule_slot_subgroups` | Подгруппы лабораторного слота |
|
||||
| `schedule_overrides` | Точечные переносы, отмены и замены конкретных сгенерированных пар |
|
||||
|
||||
Генератор `ScheduleGeneratorService` рендерит расписание по запросу:
|
||||
1. Определяет семестр для каждой даты диапазона.
|
||||
@@ -98,6 +134,7 @@
|
||||
8. Подставляет эффективную сетку времени даты: ручную, субботнюю или базовую.
|
||||
9. Считает уже проведённые часы отдельно для лекций, практик и каждой лабораторной подгруппы.
|
||||
10. Останавливает вывод слотов конкретного типа, когда достигнут его лимит часов.
|
||||
11. Применяет точечные изменения из `schedule_overrides` в расширенном поиске и отчётах.
|
||||
|
||||
Лабораторные работы могут делиться на подгруппы через `schedule_rule_slot_subgroups`. Если подгруппы выбраны, занятие выводится только для родительских групп этих подгрупп, а лимит лабораторных часов списывается отдельно по каждой подгруппе. Если лабораторная проводится у нескольких групп одновременно, один слот может содержать разные подгруппы разных групп. Лекции и практики не делятся на подгруппы.
|
||||
|
||||
@@ -129,6 +166,18 @@
|
||||
- **Связанные сущности:** базовый временной слот, преподаватель, аудитория и тип занятия должны существовать в БД.
|
||||
- **Подгруппы:** `subgroupIds` разрешены только для лабораторных слотов, должны относиться к группам правила, и в одном слоте можно выбрать не больше одной подгруппы каждой группы.
|
||||
- **Формат:** `lessonFormat` обязателен и хранится в слоте правила.
|
||||
- **Жизненный цикл:** архивные преподаватели, аудитории, группы и дисциплины не принимаются в новых правилах.
|
||||
- **Доступность аудитории:** `is_available=false` запрещает новые назначения, но не удаляет историю.
|
||||
|
||||
### Точечные изменения расписания
|
||||
|
||||
Учебный отдел может создать изменение конкретной пары:
|
||||
|
||||
- `CANCEL` — отменить пару;
|
||||
- `MOVE` — перенести пару на другой временной слот или в другую аудиторию;
|
||||
- `REPLACE` — заменить преподавателя, аудиторию или формат.
|
||||
|
||||
Изменения не переписывают базовое правило, а накладываются поверх сгенерированного расписания на конкретную дату.
|
||||
|
||||
---
|
||||
|
||||
|
||||
116
docs/DATABASE.md
116
docs/DATABASE.md
@@ -41,6 +41,10 @@ erDiagram
|
||||
VARCHAR full_name
|
||||
VARCHAR job_title
|
||||
BIGINT department_id FK
|
||||
VARCHAR status
|
||||
DATE active_from
|
||||
DATE active_to
|
||||
TIMESTAMP archived_at
|
||||
TIMESTAMP created_at
|
||||
TIMESTAMP updated_at
|
||||
}
|
||||
@@ -101,6 +105,10 @@ erDiagram
|
||||
VARCHAR building
|
||||
INT floor
|
||||
BOOLEAN is_available
|
||||
VARCHAR status
|
||||
DATE active_from
|
||||
DATE active_to
|
||||
TIMESTAMP archived_at
|
||||
TEXT description
|
||||
TIMESTAMP created_at
|
||||
}
|
||||
@@ -118,6 +126,24 @@ erDiagram
|
||||
VARCHAR qualification_level
|
||||
INT experience_years
|
||||
}
|
||||
|
||||
teacher_department_assignments {
|
||||
BIGSERIAL id PK
|
||||
BIGINT teacher_id FK
|
||||
BIGINT department_id FK
|
||||
DATE valid_from
|
||||
DATE valid_to
|
||||
BOOLEAN is_primary
|
||||
TEXT comment
|
||||
}
|
||||
|
||||
subject_comments {
|
||||
BIGSERIAL id PK
|
||||
BIGINT subject_id FK
|
||||
BIGINT author_id FK
|
||||
TEXT comment
|
||||
TIMESTAMP created_at
|
||||
}
|
||||
|
||||
teacher_lesson_types {
|
||||
BIGINT user_id FK,PK
|
||||
@@ -207,6 +233,9 @@ erDiagram
|
||||
BIGSERIAL id PK
|
||||
BIGINT subject_id FK
|
||||
BIGINT semester_id FK
|
||||
VARCHAR status
|
||||
DATE valid_from
|
||||
DATE valid_to
|
||||
INT lecture_academic_hours
|
||||
INT laboratory_academic_hours
|
||||
INT practice_academic_hours
|
||||
@@ -230,6 +259,21 @@ erDiagram
|
||||
BIGINT classroom_id FK
|
||||
BIGINT lesson_type_id FK
|
||||
VARCHAR lesson_format
|
||||
BOOLEAN time_locked
|
||||
BOOLEAN classroom_locked
|
||||
BOOLEAN teacher_locked
|
||||
}
|
||||
|
||||
schedule_overrides {
|
||||
BIGSERIAL id PK
|
||||
BIGINT base_rule_slot_id FK
|
||||
DATE lesson_date
|
||||
VARCHAR action
|
||||
BIGINT new_time_slot_id FK
|
||||
BIGINT new_classroom_id FK
|
||||
BIGINT new_teacher_id FK
|
||||
VARCHAR new_lesson_format
|
||||
TEXT comment
|
||||
}
|
||||
|
||||
schedule_rule_slot_subgroups {
|
||||
@@ -248,8 +292,12 @@ erDiagram
|
||||
student_groups ||--o{ schedule_rule_groups : "group_id"
|
||||
student_groups ||--o{ student_group_calendar_assignments : "group_id"
|
||||
users ||--o{ teacher_subjects : "user_id"
|
||||
users ||--o{ teacher_department_assignments : "teacher_id"
|
||||
departments ||--o{ teacher_department_assignments : "department_id"
|
||||
users ||--o{ teacher_lesson_types : "user_id"
|
||||
users ||--o{ subject_comments : "author_id"
|
||||
subjects ||--o{ teacher_subjects : "subject_id"
|
||||
subjects ||--o{ subject_comments : "subject_id"
|
||||
subjects ||--o{ teacher_lesson_types : "subject_id"
|
||||
subjects ||--o{ schedule_rules : "subject_id"
|
||||
lesson_types ||--o{ teacher_lesson_types : "lesson_type_id"
|
||||
@@ -270,6 +318,10 @@ erDiagram
|
||||
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"
|
||||
schedule_rule_slots ||--o{ schedule_overrides : "base_rule_slot_id"
|
||||
time_slots ||--o{ schedule_overrides : "new_time_slot_id"
|
||||
classrooms ||--o{ schedule_overrides : "new_classroom_id"
|
||||
users ||--o{ schedule_overrides : "new_teacher_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"
|
||||
@@ -317,6 +369,11 @@ erDiagram
|
||||
| `full_name` | VARCHAR(255) | ФИО |
|
||||
| `job_title` | VARCHAR(255) | Должность |
|
||||
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||
| `status` | VARCHAR(20) | `ACTIVE` или `ARCHIVED`; архивный пользователь не может войти |
|
||||
| `active_from` | DATE | Дата начала действия записи |
|
||||
| `active_to` | DATE | Дата окончания действия записи |
|
||||
| `archived_at` | TIMESTAMP | Когда пользователь архивирован |
|
||||
| `archive_reason` | TEXT | Причина архивирования |
|
||||
| `created_at` | TIMESTAMP | Дата создания |
|
||||
| `updated_at` | TIMESTAMP | Дата обновления (авто-триггер) |
|
||||
|
||||
@@ -375,6 +432,11 @@ erDiagram
|
||||
| `building` | VARCHAR(50) | Корпус |
|
||||
| `floor` | INT | Этаж |
|
||||
| `is_available` | BOOLEAN | Доступна для назначения пар |
|
||||
| `status` | VARCHAR(20) | `ACTIVE` или `ARCHIVED`; архивные аудитории не выбираются в новых назначениях |
|
||||
| `active_from` | DATE | Дата начала действия записи |
|
||||
| `active_to` | DATE | Дата окончания действия записи |
|
||||
| `archived_at` | TIMESTAMP | Когда аудитория выведена из эксплуатации |
|
||||
| `archive_reason` | TEXT | Причина архивирования |
|
||||
| `description` | TEXT | Описание |
|
||||
|
||||
#### `equipments` — Оборудование
|
||||
@@ -420,6 +482,30 @@ erDiagram
|
||||
| `subject_id` | BIGINT PK, FK → subjects (CASCADE) | Дисциплина |
|
||||
| `lesson_type_id` | BIGINT PK, FK → lesson_types (CASCADE) | Тип занятия |
|
||||
|
||||
#### `teacher_department_assignments` — История кафедр преподавателя
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID исторической записи |
|
||||
| `teacher_id` | BIGINT FK → users | Преподаватель |
|
||||
| `department_id` | BIGINT FK → departments | Кафедра |
|
||||
| `valid_from` | DATE | Дата начала принадлежности |
|
||||
| `valid_to` | DATE | Дата окончания принадлежности, `NULL` для текущей кафедры |
|
||||
| `is_primary` | BOOLEAN | Основная кафедра преподавателя |
|
||||
| `comment` | TEXT | Комментарий к переводу |
|
||||
| `created_at` | TIMESTAMP | Дата создания записи |
|
||||
| `created_by` | BIGINT FK → users | Кто оформил перевод |
|
||||
|
||||
Индекс `uq_teacher_department_open_primary` гарантирует не больше одной открытой основной кафедры у преподавателя.
|
||||
|
||||
#### `subject_comments` — Комментарии к дисциплинам
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID комментария |
|
||||
| `subject_id` | BIGINT FK → subjects | Дисциплина |
|
||||
| `author_id` | BIGINT FK → users | Автор комментария |
|
||||
| `comment` | TEXT | Текст комментария |
|
||||
| `created_at` | TIMESTAMP | Дата создания |
|
||||
|
||||
#### `time_slot_scopes` — Сетки времени
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
@@ -524,6 +610,11 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
| `lecture_start_week` | INT | Неделя семестра, с которой начинаются лекции |
|
||||
| `laboratory_start_week` | INT | Неделя семестра, с которой начинаются лабораторные |
|
||||
| `practice_start_week` | INT | Неделя семестра, с которой начинаются практики |
|
||||
| `status` | VARCHAR(20) | `ACTIVE` или `ARCHIVED` |
|
||||
| `valid_from` | DATE | Начало действия версии правила |
|
||||
| `valid_to` | DATE | Окончание действия версии правила |
|
||||
| `version_group_id` | BIGINT | Группа версий одного правила |
|
||||
| `change_reason` | TEXT | Причина изменения |
|
||||
|
||||
#### `schedule_rule_groups` — Группы правила
|
||||
| Колонка | Тип | Описание |
|
||||
@@ -543,6 +634,12 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
| `classroom_id` | BIGINT FK → classrooms | Аудитория |
|
||||
| `lesson_type_id` | BIGINT FK → lesson_types | Тип занятия |
|
||||
| `lesson_format` | VARCHAR(30) | `Очно` или `Онлайн` |
|
||||
| `time_locked` | BOOLEAN | Время закреплено учебным отделом |
|
||||
| `classroom_locked` | BOOLEAN | Аудитория закреплена учебным отделом |
|
||||
| `teacher_locked` | BOOLEAN | Преподаватель закреплён учебным отделом |
|
||||
| `locked_by` | BIGINT FK → users | Кто выполнил закрепление |
|
||||
| `locked_at` | TIMESTAMP | Когда выполнено закрепление |
|
||||
| `lock_comment` | TEXT | Комментарий к закреплению |
|
||||
|
||||
#### `schedule_rule_slot_subgroups` — Подгруппы лабораторного слота
|
||||
| Колонка | Тип | Описание |
|
||||
@@ -552,6 +649,23 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
|
||||
Связь заполняется только для лабораторных слотов. Для потоковой лабораторной можно выбрать разные подгруппы разных групп в одном слоте. Лекции и практики не делятся на подгруппы и не имеют записей в этой таблице. Триггер `validate_schedule_rule_slot_subgroups` проверяет тип занятия, принадлежность подгруппы к группам правила и запрет на две подгруппы одной группы в одном слоте; для быстрых выборок есть индекс `idx_schedule_rule_slot_subgroups_subgroup`.
|
||||
|
||||
#### `schedule_overrides` — Точечные изменения пар
|
||||
| Колонка | Тип | Описание |
|
||||
|---------|-----|----------|
|
||||
| `id` | BIGSERIAL PK | ID изменения |
|
||||
| `base_rule_slot_id` | BIGINT FK → schedule_rule_slots | Базовый слот правила |
|
||||
| `lesson_date` | DATE | Дата конкретной пары |
|
||||
| `action` | VARCHAR(20) | `MOVE`, `CANCEL`, `REPLACE` |
|
||||
| `new_time_slot_id` | BIGINT FK → time_slots | Новый временной слот |
|
||||
| `new_classroom_id` | BIGINT FK → classrooms | Новая аудитория |
|
||||
| `new_teacher_id` | BIGINT FK → users | Новый преподаватель |
|
||||
| `new_lesson_format` | VARCHAR(30) | Новый формат занятия |
|
||||
| `comment` | TEXT | Причина изменения |
|
||||
| `created_by` | BIGINT FK → users | Автор изменения |
|
||||
| `created_at` | TIMESTAMP | Дата создания |
|
||||
|
||||
Ограничение `uq_schedule_overrides_slot_date` не позволяет создать две разные правки для одной и той же пары.
|
||||
|
||||
---
|
||||
|
||||
## Flyway миграции
|
||||
@@ -570,7 +684,7 @@ Seed создаёт `Базовая сетка` (`DEFAULT`) и `Субботня
|
||||
|
||||
| Файл | Описание |
|
||||
|------|----------|
|
||||
| `V1__init.sql` | Инициализация: справочники, профили специальностей, календарные учебные графики, динамическое расписание с раздельными часами и стартовыми неделями по типам занятий, тестовые правила, триггеры, комментарии |
|
||||
| `V1__init.sql` | Инициализация: справочники, роли, lifecycle-поля, история кафедр преподавателей, комментарии дисциплин, календарные учебные графики, динамическое расписание, версии/закрепления правил, точечные изменения расписания, тестовые правила, триггеры, комментарии |
|
||||
|
||||
### Накатывание на существующих тенантов
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ frontend/
|
||||
│ │ ├── layout.css # Раскладка (sidebar, topbar, content)
|
||||
│ │ ├── components.css # Кнопки, таблицы, карточки, формы
|
||||
│ │ ├── modals.css # Модальные окна
|
||||
│ │ ├── auditorium-workload.css # Таблицы загруженности аудиторий
|
||||
│ │ ├── auditorium-workload.css # Таблицы расписаний и загруженности
|
||||
│ │ └── departments-data.css # Стили создания кафедры/специальности
|
||||
│ ├── js/
|
||||
│ │ ├── main.js # Инициализация, маршрутизация, навигация
|
||||
@@ -44,9 +44,11 @@ frontend/
|
||||
│ │ ├── equipments.js # Управление оборудованием
|
||||
│ │ ├── edu-forms.js # Формы обучения
|
||||
│ │ ├── profiles.js # Профили обучения специальностей
|
||||
│ │ ├── department-workspace.js # Кабинет кафедры в общей панели
|
||||
│ │ ├── schedule-view.js # Read-only просмотр расписаний в матрицах
|
||||
│ │ ├── schedule.js # Конструктор правил расписания
|
||||
│ │ ├── academic-calendar.js # Календарные учебные графики
|
||||
│ │ ├── auditorium-workload.js # Загруженность аудиторий
|
||||
│ │ ├── auditorium-workload.js # Загруженность аудиторий, преподавателей и кафедр
|
||||
│ │ ├── database.js # Управление тенантами
|
||||
│ │ └── departments-data.js # Создание кафедры/специальности
|
||||
│ ├── views/ # HTML-шаблоны представлений
|
||||
@@ -57,6 +59,8 @@ frontend/
|
||||
│ │ ├── equipments.html
|
||||
│ │ ├── edu-forms.html
|
||||
│ │ ├── profiles.html
|
||||
│ │ ├── department-workspace.html
|
||||
│ │ ├── schedule-view.html
|
||||
│ │ ├── schedule.html
|
||||
│ │ ├── academic-calendar.html
|
||||
│ │ ├── auditorium-workload.html
|
||||
@@ -78,6 +82,10 @@ frontend/
|
||||
│
|
||||
├── teacher/ # 👩🏫 Интерфейс преподавателя
|
||||
│ └── index.html # Недельный просмотр динамического расписания преподавателя
|
||||
├── department/ # 🏛 Кабинет кафедры
|
||||
│ └── index.html # Redirect в `/admin/#department-workspace`
|
||||
├── edu-office/ # 🗓 Кабинет учебного отдела
|
||||
│ └── index.html # Redirect в `/admin/#schedule-view`
|
||||
│
|
||||
└── student/ # 🎓 Интерфейс студента
|
||||
└── index.html # Недельный просмотр динамического расписания группы
|
||||
@@ -104,6 +112,19 @@ frontend/
|
||||
3. Подключает соответствующий JS-модуль из `js/views/{tab}.js`
|
||||
4. Обновляет заголовок страницы (`#page-title`)
|
||||
|
||||
`main.js` также фильтрует вкладки по роли:
|
||||
|
||||
| Роль | Доступные вкладки |
|
||||
|------|-------------------|
|
||||
| `ADMIN` | Все вкладки |
|
||||
| `EDUCATION_OFFICE` | Просмотр расписаний, конструктор расписания, календарный график, загруженность, аудитории, оборудование |
|
||||
| `DEPARTMENT` | Кабинет кафедры, просмотр расписаний |
|
||||
| `SCHEDULE_VIEWER` | Только просмотр расписаний |
|
||||
|
||||
Пути `/department/` и `/edu-office/` оставлены как входные redirect-страницы в общую панель. Отдельные кабинеты не дублируют UI админ-панели.
|
||||
|
||||
Пункты меню, недоступные роли, не отображаются в sidebar: `main.js` выставляет `hidden`, а `layout.css` явно скрывает такие элементы, чтобы базовый `display: flex` у `.nav-item` не возвращал их на экран.
|
||||
|
||||
### Разделы админ-панели
|
||||
|
||||
| Tab | Описание | API |
|
||||
@@ -115,30 +136,35 @@ frontend/
|
||||
| `equipments` | Оборудование | `/api/equipments` |
|
||||
| `classrooms` | Аудитории | `/api/classrooms` |
|
||||
| `subjects` | Дисциплины | `/api/subjects` |
|
||||
| `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` |
|
||||
| `auditorium-workload` | Динамическая загруженность аудиторий: сводная матрица по дате или совмещённая таблица выбранной аудитории по чётной/нечётной неделе | `/api/classrooms`, `/api/admin/time-slots`, `/api/equipments`, `/api/groups`, `/api/schedule`, `/api/admin/calendar/years` |
|
||||
| `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` с `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. В поле «Отображение» можно выбрать конкретную аудиторию; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной аудитории растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из `/api/admin/calendar/years`.
|
||||
- Вкладка `schedule-view` показывает найденные занятия только в табличной матрице: период разбивается по учебным неделям, строки — даты, столбцы — пары, а внутри ячеек используются те же карточки занятий, что и в таблицах загруженности.
|
||||
- Вкладка `auditorium-workload` стала общей вкладкой `Загруженность`: в поле «Что смотреть» выбираются аудитории, преподаватели или кафедры. Сводная матрица по выбранной дате использует одинаковую структуру: строки — выбранный тип сущности, столбцы — эффективные временные слоты дня из `/api/admin/time-slots/effective`, занятость собирается из динамического расписания `/api/schedule` по группам. Кафедральная матрица группирует занятия по кафедре преподавателя. Для аудиторий доступны фильтры корпуса, вместимости и оборудования. В поле «Отображение» можно выбрать конкретную аудиторию, преподавателя или кафедру; тогда сводная матрица заменяется одной таблицей по дням недели и времени для двухнедельного периода от выбранной даты. Таблица выбранной сущности растягивается до нижней части экрана. Ячейка делится вертикально только если верхняя и нижняя недели отличаются: нечётная неделя отображается сверху, чётная — снизу. Если состояние или занятие одинаковое, ячейка остаётся цельной. Чётность берётся из расписания, а для свободных дней рассчитывается по семестрам из `/api/admin/calendar/years`.
|
||||
- Вкладка `profiles` выделена под профили обучения: администратор выбирает специальность, создаёт профиль, редактирует описание и удаляет неиспользуемые профили.
|
||||
- Вкладка `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}`.
|
||||
- Вкладка `classrooms` теперь показывает архивные аудитории через `includeArchived=true`. Кнопка удаления заменена на архивирование: аудитория выводится из эксплуатации, но остаётся в историческом расписании. Для архивных аудиторий доступно восстановление.
|
||||
- Вкладка `users` поддерживает роли `EDUCATION_OFFICE`, `DEPARTMENT` и `SCHEDULE_VIEWER`; удаление пользователя работает как архивирование.
|
||||
|
||||
### Страница настроек (`/admin/settings/`)
|
||||
|
||||
Настройки — это **отдельный SPA** со своей боковой панелью и вкладками, не связанными с основной админ-панелью.
|
||||
|
||||
- Доступ: через dropdown «Настройки» в footer боковой панели админки
|
||||
- Доступ: через dropdown «Настройки» в footer боковой панели админки для `ADMIN` и `EDUCATION_OFFICE`
|
||||
- Кнопка «Назад в панель» для возврата в `/admin/`
|
||||
- Текущие вкладки:
|
||||
- **Общие настройки** — заглушка (в разработке)
|
||||
- **Общие настройки** — заглушка (только `ADMIN`)
|
||||
- **Временные слоты** — выбор сетки через выпадающий список, CRUD слотов выбранной сетки, добавление ручных сеток через модальное окно и удаление выбранной ручной сетки. Базовая сетка применяется по умолчанию, субботняя — автоматически по субботам; ручное применение выполняется в сетке календарного графика.
|
||||
|
||||
---
|
||||
@@ -191,6 +217,9 @@ export const api = {
|
||||
- `userId` — ID пользователя для личного расписания преподавателя
|
||||
4. Перенаправляет на соответствующий интерфейс:
|
||||
- `ADMIN` → `/admin/`
|
||||
- `EDUCATION_OFFICE` → `/admin/#schedule-view`
|
||||
- `DEPARTMENT` → `/admin/#department-workspace`
|
||||
- `SCHEDULE_VIEWER` → `/admin/#schedule-view`
|
||||
- `TEACHER` → `/teacher/`
|
||||
- `STUDENT` → `/student/`
|
||||
|
||||
@@ -213,6 +242,30 @@ export function isAuthenticatedAsAdmin() {
|
||||
|
||||
## Кабинеты расписания
|
||||
|
||||
### Учебный отдел (`/admin/#schedule-view`)
|
||||
|
||||
Учебный отдел работает в общей админ-панели с ограниченным набором вкладок:
|
||||
|
||||
- расширенный поиск `/api/schedule/search` по периоду, преподавателю, аудитории и кафедре;
|
||||
- конструктор расписания через `/api/admin/schedule-rules`;
|
||||
- создание точечного изменения пары через `/api/edu-office/schedule/overrides` на уровне API;
|
||||
- матричный просмотр загруженности аудиторий, преподавателей и кафедр через общую вкладку `Загруженность`;
|
||||
- аудитории, оборудование, календарный график и загруженность.
|
||||
|
||||
### Кафедра (`/admin/#department-workspace`)
|
||||
|
||||
Кабинет кафедры встроен в общую панель и работает в контексте `departmentId` текущего пользователя:
|
||||
|
||||
- список дисциплин кафедры через `/api/department/subjects`;
|
||||
- загрузка дисциплин из текстового списка в формате `код; название`;
|
||||
- комментарии к дисциплинам;
|
||||
- просмотр расписания кафедры через вкладку `schedule-view`;
|
||||
- загруженность преподавателей кафедры.
|
||||
|
||||
### Просмотр расписаний (`/admin/#schedule-view`)
|
||||
|
||||
Роль `SCHEDULE_VIEWER` видит только read-only вкладку просмотра расписаний. Доступны фильтры по периоду, группе, преподавателю, аудитории, кафедре, дисциплине, типу занятия и чётности. Найденные занятия отображаются недельными таблицами с датами по строкам и парами по столбцам.
|
||||
|
||||
### Преподаватель (`/teacher/`)
|
||||
|
||||
Страница показывает недельную сетку занятий преподавателя. ID преподавателя берётся из `localStorage.userId`, который сохраняется после `POST /api/auth/login`.
|
||||
|
||||
@@ -90,6 +90,8 @@ magistr/
|
||||
│ ├── admin/ # Админ-панель (деканат)
|
||||
│ │ ├── js/views/ # Модули представлений
|
||||
│ │ └── css/ # Стили
|
||||
│ ├── department/ # Redirect в кабинет кафедры внутри admin SPA
|
||||
│ ├── edu-office/ # Redirect в кабинет учебного отдела внутри admin SPA
|
||||
│ ├── teacher/ # Интерфейс преподавателя
|
||||
│ └── student/ # Интерфейс студента
|
||||
├── docs/ # 📖 Документация (вы здесь)
|
||||
|
||||
Reference in New Issue
Block a user