#2 - тест, #5 - готово. Разовое редактирование занятия без изменений правила

This commit is contained in:
Zuev
2026-07-22 00:15:35 +03:00
parent ee876f1acd
commit 92ff87a917
33 changed files with 3324 additions and 251 deletions

View File

@@ -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"]
}
```
## Загруженность