исправление багов
This commit is contained in:
86
docs/API.md
86
docs/API.md
@@ -93,6 +93,9 @@ Redirect по ролям:
|
||||
```
|
||||
|
||||
Refresh-токен ротируется при каждом успешном обновлении, а старый refresh-токен отзывается.
|
||||
Один refresh-токен можно успешно использовать только один раз, в том числе при параллельных
|
||||
запросах: первый запрос получает новую пару токенов, остальные получают `401`, а их cookie
|
||||
очищается.
|
||||
|
||||
### `POST /api/auth/logout`
|
||||
|
||||
@@ -426,6 +429,14 @@ GET /api/schedule?groupId=1&startDate=2026-04-27&endDate=2026-05-03
|
||||
}
|
||||
```
|
||||
|
||||
`PUT /api/admin/academic-calendars/{id}/grid` выполняет атомарную полную замену. До
|
||||
удаления прежних строк backend проверяет весь список: он должен быть непустым и не
|
||||
содержать `null`, курс должен входить в `1..courseCount`, дата — в учебный год,
|
||||
`dayOfWeek` — совпадать с ISO-днём даты, а `weekNumber` — с номером семидневного периода
|
||||
от начала учебного года. Ключ `(courseNumber, date)` не должен повторяться, каждый
|
||||
`activityTypeId` или `activityCode` должен существовать. При любом `400` старая сетка
|
||||
остаётся без изменений; `calendarId` из строки не переопределяет ID в URL.
|
||||
|
||||
**Привязка дисциплин к графику:**
|
||||
```json
|
||||
[
|
||||
@@ -476,15 +487,32 @@ CRUD доступен по:
|
||||
| `GET` | `/api/admin/schedule-rules/{id}` | Одно правило |
|
||||
| `POST` | `/api/admin/schedule-rules` | Создать правило |
|
||||
| `PUT` | `/api/admin/schedule-rules/{id}` | Обновить правило |
|
||||
| `DELETE` | `/api/admin/schedule-rules/{id}` | Удалить правило |
|
||||
| `DELETE` | `/api/admin/schedule-rules/{id}` | Архивировать правило |
|
||||
|
||||
`timeSlotId` должен ссылаться на базовый слот (`scopeApplyMode = DEFAULT`). Субботняя и ручные сетки не выбираются в правиле напрямую.
|
||||
|
||||
`subgroupIds` можно передавать только для лабораторного слота. Каждая подгруппа должна относиться к одной из групп правила. Если лабораторная проводится у нескольких групп одновременно, в одном слоте можно передать разные подгруппы этих групп, например `[10, 22]`. Для совместимости одиночный `subgroupId` тоже принимается, но новый формат — `subgroupIds`. Для лекций и практик оба поля должны быть пустыми, иначе API вернёт ошибку валидации. В одном слоте нельзя выбрать больше одной подгруппы одной и той же группы.
|
||||
|
||||
Часы и недели начала задаются отдельно для лекций, лабораторных и практик. Если для типа занятий указан ненулевой лимит часов, в правиле должен быть хотя бы один слот этого типа; если слот типа есть, его лимит часов должен быть больше нуля.
|
||||
Часы и недели начала задаются отдельно для лекций, лабораторных и практик. Каждый лимит
|
||||
часов обязателен, неотрицателен и кратен двум; ноль разрешён для неиспользуемого типа, но
|
||||
суммарно хотя бы один тип должен иметь положительный лимит. Если лимит типа ненулевой, в
|
||||
правиле должен быть хотя бы один слот этого типа; слот типа не принимается при нулевом
|
||||
лимите. Вложенные идентификаторы, которых нет в БД, считаются ошибкой payload и дают `400`.
|
||||
|
||||
`parity` принимает только `BOTH`, `ODD` или `EVEN`. При создании и обновлении правила backend проверяет занятость в рамках семестра: конфликтом считается пересечение дня, базового временного слота, чётности и активных недель слотов, если совпадает преподаватель, аудитория или учебная группа. Активные недели рассчитываются по лимиту часов типа занятия, неделе начала, чётности и порядку слотов правила, поэтому правило, которое фактически идёт с 1 по 3 неделю, не блокирует тот же слот с 4 недели. Для лабораторных слотов подгруппы учитываются отдельно: разные подгруппы одной группы могут занимать один слот, но слот для всей группы конфликтует с любой её подгруппой. При конфликте API возвращает `409 Conflict`:
|
||||
`teacherId` должен ссылаться на активного пользователя с ролью `TEACHER`, а
|
||||
`lessonFormat` принимает только `Очно` или `Онлайн`. `parity` принимает только `BOTH`,
|
||||
`ODD` или `EVEN`.
|
||||
|
||||
До сохранения backend попарно проверяет все слоты нового payload: точные дубли и
|
||||
пересечения преподавателя, аудитории или аудитории обучающихся отклоняются. `ODD` и `EVEN`
|
||||
не пересекаются; `BOTH` пересекается с обеими чётностями. Затем выполняется та же проверка
|
||||
с активными правилами семестра. Активные недели рассчитываются по лимиту часов типа
|
||||
занятия, неделе начала, чётности и порядку слотов правила, поэтому правило, которое
|
||||
фактически идёт с 1 по 3 неделю, не блокирует тот же слот с 4 недели. Для лабораторных
|
||||
слотов подгруппы учитываются отдельно: разные подгруппы одной группы могут занимать один
|
||||
слот, но слот для всей группы конфликтует с любой её подгруппой. Создание правил одного
|
||||
семестра сериализуется блокировкой строки семестра в PostgreSQL. Конфликт с уже сохранённым
|
||||
правилом возвращает `409 Conflict`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -501,7 +529,19 @@ CRUD доступен по:
|
||||
}
|
||||
```
|
||||
|
||||
`conflictFields` содержит технические причины пересечения: `teacher`, `classroom` и/или `group`. `conflictReasons` содержит те же причины в русских подписях для интерфейса.
|
||||
`conflictFields` содержит технические причины пересечения: `teacher`, `classroom` и/или
|
||||
`group`. Для точного дубля используется `slot`. При конфликте внутри нового payload
|
||||
`conflictRule` отсутствует, потому что конфликтующей сохранённой записи ещё нет:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Невозможно сохранить правило: слоты внутри правила конфликтуют",
|
||||
"conflictFields": ["teacher", "group"],
|
||||
"conflictReasons": ["Преподаватель", "Группа"]
|
||||
}
|
||||
```
|
||||
|
||||
`conflictReasons` содержит те же причины в русских подписях для интерфейса.
|
||||
|
||||
### `GET /api/lesson-types`
|
||||
|
||||
@@ -532,7 +572,17 @@ CRUD доступен по:
|
||||
| `timeSlotId` | Временной слот |
|
||||
| `parity` | `BOTH`, `ODD`, `EVEN` |
|
||||
|
||||
Если указан только `teacherId` без `groupId` и `departmentId`, поиск строит расписание преподавателя напрямую и не обходит все группы. Широкий поиск без `groupId` и `departmentId` разрешён только до 50 активных групп; при большем количестве групп API вернёт `400` с просьбой уточнить группу или кафедру.
|
||||
Если указан только `teacherId` без `groupId` и `departmentId`, базовое расписание
|
||||
преподавателя строится напрямую. Затем поиск учитывает точечные изменения, где преподаватель
|
||||
назначен через `newTeacherId`: для каждой уникальной даты такой замены один раз строится
|
||||
базовый день, из него добавляются только указанные `baseRuleSlotId`, после чего применяются
|
||||
все overrides и выполняется окончательный фильтр преподавателя. Поэтому новый преподаватель
|
||||
видит назначенную замену, а исходный больше её не видит. Если релевантных замен нет, обход
|
||||
всех групп не выполняется.
|
||||
|
||||
Широкий пользовательский поиск без `groupId`, `departmentId` и teacher-only режима
|
||||
разрешён только до 50 активных групп; при большем количестве групп API вернёт `400` с
|
||||
просьбой уточнить группу или кафедру.
|
||||
|
||||
Пример:
|
||||
|
||||
@@ -562,7 +612,24 @@ GET /api/schedule/search?classroomId=1&startDate=2026-05-20&endDate=2026-05-27
|
||||
}
|
||||
```
|
||||
|
||||
`action=CANCEL` отменяет конкретную пару. `MOVE` и `REPLACE` могут менять аудиторию, преподавателя, формат и временной слот.
|
||||
Правила payload:
|
||||
|
||||
- `CANCEL` отменяет конкретную пару; поля `newTimeSlotId`, `newClassroomId`,
|
||||
`newTeacherId` и `newLessonFormat` должны отсутствовать;
|
||||
- `MOVE` требует новый временной слот или аудиторию; преподавателя и формат можно изменить
|
||||
в том же запросе;
|
||||
- `REPLACE` требует нового преподавателя, аудиторию или формат; временной слот можно
|
||||
изменить в том же запросе;
|
||||
- формат принимает только `Очно` или `Онлайн`;
|
||||
- `MOVE` и `REPLACE` должны фактически менять основные параметры действия. Другой ID
|
||||
временного слота с тем же интервалом не считается переносом.
|
||||
|
||||
До сохранения backend строит базовую пару на `lessonDate` по тем же правилам, что и обычное
|
||||
расписание: семестр, календарный график, чётность, активность сущностей и остаток часов.
|
||||
Если пара не формируется или нарушена матрица действия, API возвращает `400` с русским
|
||||
сообщением. Если результирующее время пересекается с занятым преподавателем, аудиторией,
|
||||
группой или той же подгруппой, API возвращает `409 Conflict`; соседние интервалы и разные
|
||||
подгруппы одной группы не конфликтуют.
|
||||
|
||||
## Загруженность
|
||||
|
||||
@@ -1059,7 +1126,12 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
|
||||
**Логика:**
|
||||
1. Создаёт HikariCP пул для нового тенанта
|
||||
2. Запускает Flyway миграции на его БД
|
||||
3. Обновляет Kubernetes ConfigMap
|
||||
3. Обновляет внешний Kubernetes Secret `tenants-secret`
|
||||
|
||||
Backend соединяется с Kubernetes API только через проверенный service-account CA и
|
||||
hostname verification. Если безопасно сохранить tenant-конфигурацию не удалось, операция
|
||||
не возвращается как успешная. Значения credentials никогда не включаются в ответ или лог
|
||||
Kubernetes updater.
|
||||
|
||||
### `DELETE /api/database/tenants/{domain}`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user