docs: этапы 3–4 — роли, профиль и жизненный цикл
This commit is contained in:
13
README.md
13
README.md
@@ -111,6 +111,19 @@ cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
|
||||
существующие лабораторные, главы и активные группы в курс «Промышленное
|
||||
программирование на языке C». Лабораторные привязаны к курсу: создание
|
||||
и импорт требуют выбора курса, лабу можно скопировать в другой курс.
|
||||
- **Преподаватели и доступ**: `/admin/teachers` создаёт аккаунты `TEACHER`
|
||||
с одноразовым паролем; преподаватель видит только назначенные курсы
|
||||
(`/teacher`), их группы, студентов и профили. Курсы и лабораторные
|
||||
фильтруются по роли, внутренние вызовы identity защищены `LMS_INTERNAL_TOKEN`.
|
||||
- **Профиль студента курса**: `/teacher/courses/{id}/students/{id}` и админский
|
||||
маршрут показывают сводку, оценки, комментарии с авторами, защиты и все
|
||||
попытки сдачи со скачиванием ZIP; студенту API отдаёт только его профиль
|
||||
и опубликованные данные.
|
||||
- **Повышение и жизненный цикл**: `/admin/promotion` — dry-run, применение
|
||||
и откат повышения (автоматический dry-run 31 августа МСК); на странице
|
||||
группы — академотпуск, возврат, отчисление, архив и восстановление;
|
||||
архив группы и подтверждение удаления логином/ФИО. Вход закрыт для
|
||||
выпущенных и архивных групп.
|
||||
- **Лабораторные**: импорт ZIP (`lab.yaml` + `README.md` + `variants/*.md` +
|
||||
`assets/`); создание через UI с произвольным числом уровней; единая форма
|
||||
настроек с одной кнопкой «Сохранить всё»; статусы draft/published
|
||||
|
||||
@@ -47,7 +47,15 @@ cp .env.prod.example .env.prod
|
||||
nano .env.prod
|
||||
```
|
||||
|
||||
Обязательно задайте надёжный `LMS_DB_PASSWORD`. Остальные значения по умолчанию подходят.
|
||||
Обязательно задайте `LMS_DB_PASSWORD` и `LMS_INTERNAL_TOKEN`. Остальные
|
||||
значения по умолчанию подходят.
|
||||
|
||||
`LMS_INTERNAL_TOKEN` — общий секрет для внутренних вызовов identity из
|
||||
academic/progress (профиль студента, состав группы). Сгенерируйте длинную
|
||||
случайную строку (например, `openssl rand -hex 32`) и укажите её в `.env.prod`.
|
||||
Compose передаёт одно и то же значение identity, academic и progress.
|
||||
Значение по умолчанию `dev-internal-token` для production не используйте;
|
||||
`.env.prod` не коммитьте.
|
||||
|
||||
## 4. Собрать и запустить
|
||||
|
||||
@@ -148,6 +156,9 @@ Backup на том же сервере — не единственная защ
|
||||
JWKS: должно быть `LMS_JWT_JWK_SET_URI=http://identity:8081/api/v1/auth/jwks.json`
|
||||
(уже прописано в `docker-compose.prod.yml` для academic/progress/gateway).
|
||||
Внутри контейнера `localhost:8081` недоступен, поэтому токен не проверяется.
|
||||
- **403/502 в профиле студента или составе группы** — проверьте, что
|
||||
`LMS_INTERNAL_TOKEN` задан в `.env.prod` и одинаков у identity, academic
|
||||
и progress. При неверном внутреннем токене identity отвечает `403`.
|
||||
- **Не проходят загрузки больших ZIP** — увеличьте `client_max_body_size` в nginx
|
||||
(сейчас 120 МБ) и `spring.servlet.multipart.max-request-size`.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 08. План реализации: группы, курсы, преподаватели
|
||||
|
||||
**Статус:** план согласован, реализация по этапам 0–6, один релиз.
|
||||
**Статус:** план согласован, этапы 0–4 выполнены, этапы 5–6 — в работе.
|
||||
**Основа:** [07-groups-courses.md](07-groups-courses.md).
|
||||
**Дата решений:** 2026-10-23.
|
||||
|
||||
@@ -303,10 +303,20 @@ identity V9, **academic V10**, progress V13 (+V14 при необходимос
|
||||
- **Этап 2 — выполнен.** Курсы и `course_no`, история преподавателей и групп,
|
||||
bootstrap курса C, привязка лабораторных к курсу, копирование лаб,
|
||||
админ-UI курсов и лабораторных. Отчёт: [12-stage2-report.md](12-stage2-report.md).
|
||||
- **Этап 3 — следующий.** Доступ и профиль: `TEACHER` в security-конфигах,
|
||||
внутренние access-эндпоинты, проверки курса/группы/студента, профиль студента
|
||||
курса, материалы курса и аудитории, активы, кабинет преподавателя.
|
||||
- Этапы 4–6 — по плану.
|
||||
- **Этап 3 — выполнен.** Аккаунты преподавателей, курсовая область видимости,
|
||||
внутренние API identity с `LMS_INTERNAL_TOKEN`, кабинет и состав групп
|
||||
преподавателя, профиль студента курса и скачивание всех попыток.
|
||||
Отчёт: [13-stage3-report.md](13-stage3-report.md).
|
||||
- **Этап 4 — выполнен.** Повышение (dry-run/apply/rollback, планировщик
|
||||
31 августа МСК и догон), жизненный цикл студента, архив группы,
|
||||
усиленное подтверждение удаления, блокировка входа для архивных
|
||||
и выпущенных групп. Отчёт: [14-stage4-report.md](14-stage4-report.md).
|
||||
- **Этап 5 — остаётся.** UX и контент: студенческие страницы курсов,
|
||||
материалы по `/learn/course/{course_no}/...` и аудитории, наследуемая
|
||||
видимость активов, публичные материалы `ALL`.
|
||||
- **Этап 6 — остаётся.** Hardening: полная security-матрица,
|
||||
upgrade-тест миграций, аудит, индексы/пагинация, backup/restore,
|
||||
документация и smoke.
|
||||
|
||||
## 10. Definition of Done
|
||||
|
||||
|
||||
172
docs/13-stage3-report.md
Normal file
172
docs/13-stage3-report.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 13. Отчёт этапа 3: доступ и профиль студента курса
|
||||
|
||||
**Дата:** 2026-10-27.
|
||||
**Статус:** этап 3 выполнен; этап 4 (повышение и жизненный цикл) — следующий.
|
||||
|
||||
## 1. Что сделано
|
||||
|
||||
### Аккаунты преподавателей
|
||||
|
||||
- Добавлен административный API `/api/v1/teachers`: список, создание,
|
||||
сброс пароля. Создаётся только аккаунт роли `TEACHER`.
|
||||
- Логин проверяется на длину (минимум 3 символа) и уникальность; пустой пароль
|
||||
заменяется сгенерированным. Новый пароль помечается как временный
|
||||
(`mustChangePassword`), показывается один раз.
|
||||
- В админке появилась страница `/admin/teachers` с созданием преподавателя
|
||||
и сбросом пароля; одноразовый пароль выводится во всплывающем уведомлении.
|
||||
|
||||
### Курсовая область видимости
|
||||
|
||||
- `CourseAccessService` в academic-service определяет доступные курсы по роли:
|
||||
- `ADMIN` — все курсы, включая архивные;
|
||||
- `TEACHER` — только активные курсы с активным назначением
|
||||
`course_teachers` (`removed_at IS NULL`);
|
||||
- `STUDENT` — только активные курсы активных групп, в которых студент
|
||||
состоит сейчас (группы запрашиваются у identity-service);
|
||||
- анонимный пользователь до сервиса не доходит — gateway требует JWT.
|
||||
- `GET /api/v1/courses/mine` возвращает курсы текущего пользователя.
|
||||
- `GET /api/v1/courses/{id}` проверяет доступ к курсу. Админские операции
|
||||
создания, переименования, архивирования, назначения преподавателей
|
||||
и привязки групп остались только для `ADMIN`.
|
||||
- Список лабораторных без `courseId` фильтруется по доступным курсам;
|
||||
с `courseId` доступ проверяется явно. Карточка лабораторной тоже проверяет
|
||||
доступ через её курс. Лабораторная без курса доступна только админу.
|
||||
- Недоступный курс/лабораторная дают `403`, несуществующая — `404`.
|
||||
Недоступность identity при проверке групп даёт `502`, а не `403`.
|
||||
|
||||
### Внутренние API identity
|
||||
|
||||
Добавлены read-only эндпоинты для межсервисных вызовов:
|
||||
|
||||
| Метод | Путь | Назначение |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/internal/students/{studentId}/context` | карточка студента: ФИО, №, статус, год набора, текущая группа |
|
||||
| `POST` | `/api/v1/internal/accounts/lookup` | пакетные логины авторов комментариев и оценок |
|
||||
| `GET` | `/api/v1/internal/groups/{groupId}` | группа и текущий состав без учётных данных |
|
||||
|
||||
Эндпоинты защищены заголовком `X-Internal-Token`; сравнение токена —
|
||||
constant-time. Gateway не маршрутизирует `/api/v1/internal/**`, поэтому снаружи
|
||||
эти пути недоступны.
|
||||
|
||||
### Кабинет преподавателя
|
||||
|
||||
- `/teacher` — список назначенных курсов с количеством групп и лабораторных.
|
||||
- `/teacher/courses/{id}` — карточка курса: привязанные группы и лабораторные
|
||||
в режиме просмотра. Редактирование курса и лабораторных остаётся у админа.
|
||||
- `/teacher/courses/{courseId}/groups/{groupId}` — состав группы, привязанной
|
||||
к курсу, со ссылками на профиль студента.
|
||||
- Доступ к `/teacher/**` разрешён роли `TEACHER` и админу; студент
|
||||
перенаправляется в свой кабинет, аноним — на вход. После входа преподаватель
|
||||
попадает на `/teacher`.
|
||||
|
||||
### Профиль студента курса
|
||||
|
||||
- Progress-service собирает агрегированный профиль одним запросом:
|
||||
`GET /api/v1/courses/{courseId}/students/{studentId}/profile`.
|
||||
- В ответе: курс, карточка студента, сводка и полный список лабораторных курса.
|
||||
- Для каждой лабораторной: вариант и уровень, число попыток, дата последней
|
||||
сдачи, оценка и её публикация, общий комментарий, комментарии преподавателей,
|
||||
защита (статус, код, задание, остаток времени), все попытки с файлами
|
||||
и ссылкой на скачивание.
|
||||
- Скачивание любой попытки:
|
||||
`GET /api/v1/courses/{courseId}/submissions/{submissionId}/download`
|
||||
(ZIP). Проверяется, что сдача относится к лабораторной этого курса;
|
||||
чужой `submissionId` и чужой курс отклоняются.
|
||||
- BFF-страницы профиля:
|
||||
- `/teacher/courses/{courseId}/students/{studentId}`;
|
||||
- `/admin/courses/{courseId}/students/{studentId}`.
|
||||
- Авторство: миграция `V13__profile_authors.sql` добавляет
|
||||
`grade_comments.author_account_id` и `lab_grades.updated_by_account_id`;
|
||||
новые комментарии и оценки сохраняют автора. Старые записи показываются
|
||||
без автора. Индекс `lab_grades(lab_id)` ускоряет выборку по списку лаб.
|
||||
- В ведомости группы ФИО студента и кнопка «Профиль» ведут на профиль, если
|
||||
группа открыта из карточки курса. На странице группы преподавателя те же
|
||||
ссылки доступны из состава группы.
|
||||
|
||||
## 2. Правила доступа
|
||||
|
||||
| Объект | ADMIN | TEACHER | STUDENT |
|
||||
|---|---|---|---|
|
||||
| Курс | любой, включая архивный | только активный назначенный | только активный курс текущей группы |
|
||||
| Лабораторные курса | все | все курса, включая черновики | все лабораторные доступного курса |
|
||||
| Состав группы курса | любая привязанная группа | группа должна быть активной привязкой курса | доступ к курсу по своей группе |
|
||||
| Профиль студента | любой студент любого курса | студент должен состоять в активной группе курса | только свой профиль |
|
||||
| Оценки и комментарии | все | все, включая неопубликованные | только опубликованные |
|
||||
| Скачивание попытки | любая попытка курса | только студент из активной группы курса | отдельный download-эндпоинт недоступен |
|
||||
| Внутренние API identity | по сервисному токену | по сервисному токену | по сервисному токену |
|
||||
|
||||
Дополнительно:
|
||||
|
||||
- Преподаватель чужого курса получает `403`; студент чужого профиля — `403`.
|
||||
- Студент видит только опубликованные оценки и комментарии. Черновики
|
||||
и неопубликованные комментарии скрыты.
|
||||
- Профиль не даёт доступа к другим курсам студента.
|
||||
- Автор комментария/оценки показывается логином; старые записи без автора —
|
||||
«—». Логины подтягиваются одним пакетным запросом.
|
||||
|
||||
## 3. Токен и конфигурация
|
||||
|
||||
- Общий секрет: `LMS_INTERNAL_TOKEN`.
|
||||
- Где задаётся: `lms.internal.token` в `application.yml` identity, academic
|
||||
и progress. Локально допускается значение по умолчанию
|
||||
`dev-internal-token`; в production оно обязательно заменяется.
|
||||
- `docker-compose.prod.yml` передаёт токен в identity, academic и progress;
|
||||
`.env.prod.example` содержит заготовку без секрета.
|
||||
- Заголовок: `X-Internal-Token`. Пустой или неверный токен — `403`.
|
||||
- Gateway не публикует внутренние пути; вызовы идут напрямую между сервисами
|
||||
по внутренней сети Docker.
|
||||
|
||||
## 4. UI
|
||||
|
||||
- `/admin/teachers` — создание преподавателя, список, сброс пароля,
|
||||
отображение временного пароля один раз.
|
||||
- `/teacher` — «Мои курсы».
|
||||
- `/teacher/courses/{id}` — группы и лабораторные курса, переход к составу
|
||||
группы.
|
||||
- `/teacher/courses/{courseId}/groups/{groupId}` — студенты группы, ФИО
|
||||
и кнопка «Открыть» ведут в профиль.
|
||||
- `/teacher/courses/{courseId}/students/{studentId}` и
|
||||
`/admin/courses/{courseId}/students/{studentId}` — единый шаблон профиля
|
||||
с разными маршрутами скачивания и возврата.
|
||||
- В навигации: «Преподаватели» у админа, «Мои курсы» у преподавателя.
|
||||
- Профиль показывает сводку (лабораторных, сдано, оценено, средний балл,
|
||||
защиты), каждую лабораторную, комментарии, защиты и все попытки.
|
||||
|
||||
## 5. Тесты
|
||||
|
||||
Добавлены и расширены наборы:
|
||||
|
||||
- identity: `InternalControllerIntegrationTest` (12) — контекст студента,
|
||||
пакетные логины, состав группы, неверный/пустой токен, неизвестные id;
|
||||
`TeacherIntegrationTest` (6) — создание, генерация пароля, список,
|
||||
сброс пароля, запрет для не-админа.
|
||||
- academic: `CourseAccessServiceTest` (16) — роли, чужие/архивные курсы,
|
||||
снятые назначения, студенты без групп, `mine`, доступ к лабораторным;
|
||||
`CourseRosterServiceTest` (6) — состав группы, чужая группа/преподаватель,
|
||||
недоступность identity.
|
||||
- progress: `StudentProfileIntegrationTest` (5) — неопубликованные данные
|
||||
для преподавателя, только опубликованное для студента, чужой курс/студент,
|
||||
скачивание чужой сдачи.
|
||||
- web: `StudentProfileTemplateTest`, `ProfileDtoJsonTest`,
|
||||
`TeacherGroupTemplateTest`, `TeacherInterceptorTest`,
|
||||
`CourseGroupRosterDtoTest` — контракты, шаблоны, роли и ссылки.
|
||||
|
||||
По результатам последнего прогона (2026-10-27) перечисленные наборы прошли
|
||||
с `0 failures / 0 errors`.
|
||||
|
||||
## 6. Известные пробелы
|
||||
|
||||
- **Контент курса.** URL материалов вида `/learn/course/{course_no}/...`,
|
||||
проверка `chapters.course_id/audience`, наследуемая видимость активов
|
||||
и выдача файлов по курсу ещё не реализованы. Схема подготовлена на этапе 2.
|
||||
- **Права преподавателя.** Кабинет преподавателя — просмотр: курс, группы,
|
||||
состав, лабораторные и профили. Редактирование лабораторных, оценок
|
||||
и раздачи вариантов из кабинета не добавлялось.
|
||||
- **Названия групп в кабинете.** На странице курса преподавателя группа
|
||||
показывается UUID: academic отдаёт `groupId`, а имя группы не входит
|
||||
в контракт карточки курса. Нужен batch-запрос имён или расширение DTO.
|
||||
- **Собственный профиль студента.** API допускает `STUDENT` только для своего
|
||||
профиля, но отдельной студенческой BFF-страницы профиля нет; текущие
|
||||
страницы профиля — преподавательская и админская.
|
||||
- **Аудит скачиваний.** Скачивание попыток пока не пишется в аудит;
|
||||
это этап 6.
|
||||
174
docs/14-stage4-report.md
Normal file
174
docs/14-stage4-report.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 14. Отчёт этапа 4: повышение и жизненный цикл
|
||||
|
||||
**Дата:** 2026-10-27.
|
||||
**Статус:** этап 4 выполнен; этап 5 (UX и контент курса) — следующий.
|
||||
|
||||
## 1. Повышение
|
||||
|
||||
### Алгоритм
|
||||
|
||||
План строится по активным группам и студентам:
|
||||
|
||||
- Активная группа с курсом меньше предела (`БО` — 4, `МО` — 2) переводится
|
||||
на следующий курс: меняются `course` и имя по шаблону
|
||||
`поток-курсномерстепень`.
|
||||
- Активная группа на последнем курсе получает статус `GRADUATED`.
|
||||
- Активные студенты выпускаемой группы получают `GRADUATED`.
|
||||
- Студенты в академическом отпуске переводятся в параллель следующего набора
|
||||
того же курса: `enrollmentYear = год повышения - (курс - 1)`.
|
||||
Если такой группы нет, она создаётся в плане; при применении — в БД.
|
||||
- Студенты в статусах `EXPELLED`, `GRADUATED`, `ARCHIVED` в план не попадают.
|
||||
- Группы без курса/степени/потока/номера и студенты без зачисления
|
||||
пропускаются с предупреждением; план при этом не блокируется.
|
||||
|
||||
Порядок применения: сначала группы (от старших курсов), затем студенты.
|
||||
Каждый элемент хранит `before`/`after` в JSONB. Перед применением все `before`
|
||||
сверяются с текущим состоянием; при расхождении вся транзакция отменяется
|
||||
с `409`, частичного применения нет.
|
||||
|
||||
### API
|
||||
|
||||
Все эндпоинты требуют роль `ADMIN`:
|
||||
|
||||
| Метод | Путь | Назначение |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/promotion` | список запусков |
|
||||
| `GET` | `/api/v1/promotion/{id}` | план/результат с элементами |
|
||||
| `POST` | `/api/v1/promotion/dry-run` | построить план; тело `{ "year": 2026 }` необязательно |
|
||||
| `POST` | `/api/v1/promotion/{id}/apply` | применить запланированный dry-run |
|
||||
| `POST` | `/api/v1/promotion/{id}/rollback` | откатить завершённое применение |
|
||||
|
||||
Правила:
|
||||
|
||||
- `year` — 2000–2100; без года берётся текущий.
|
||||
- Повторный dry-run за год возвращает существующий план в статусе `PLANNED`.
|
||||
- Применять можно только `DRY_RUN` в статусе `PLANNED`. Запуск переходит
|
||||
в `APPLY/RUNNING`, затем `COMPLETED`.
|
||||
- Один успешный `APPLY` на год защищён частичным уникальным индексом
|
||||
`promotion_runs(year) WHERE mode = 'APPLY' AND status IN ('RUNNING','COMPLETED')`;
|
||||
конкурентное применение даёт `409`.
|
||||
- Повторное применение уже завершённого запуска идемпотентно возвращает
|
||||
результат и не создаёт новых записей истории.
|
||||
- Откат доступен только для `COMPLETED`. Перед откатом проверяется, что
|
||||
текущее состояние совпадает с `after`; ручные изменения дают `409`.
|
||||
Восстанавливаются курс, имя и статус групп, статусы/группы студентов,
|
||||
удаляются созданные планом пустые целевые группы. История не стирается:
|
||||
добавляются записи `RETURN` с причиной «Откат повышения».
|
||||
|
||||
### Планировщик
|
||||
|
||||
- `PromotionScheduler` строит dry-run 31 августа в `00:00` по
|
||||
`Europe/Moscow` (`cron = "0 0 0 31 8 *"`).
|
||||
- При старте сервиса выполняется догон: если сегодня 31 августа или позже
|
||||
и за год ещё нет запусков, dry-run строится сразу.
|
||||
- Догон идемпотентен: advisory lock не даёт двум инстансам создать два плана.
|
||||
- Планировщик включён по умолчанию; отключается
|
||||
`lms.promotion.scheduler-enabled=false` (используется в тестах).
|
||||
- Автоматически применяется только dry-run. Применение — всегда вручную
|
||||
админом после проверки плана.
|
||||
|
||||
## 2. Жизненный цикл студента
|
||||
|
||||
Эндпоинты требуют роль `ADMIN`:
|
||||
|
||||
| Метод | Путь | Тело | Результат |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/api/v1/students/{id}/academic-leave` | `reason?` | перевод в параллель следующего набора, статус `ACADEMIC_LEAVE` |
|
||||
| `POST` | `/api/v1/students/{id}/return` | `groupId`, `reason?` | возврат в указанную активную группу, статус `ACTIVE` |
|
||||
| `POST` | `/api/v1/students/{id}/expel` | `reason?` | статус `EXPELLED`, зачисление сохраняется |
|
||||
| `POST` | `/api/v1/students/{id}/archive` | — | статус студента `ARCHIVED`, аккаунт `ARCHIVED` |
|
||||
| `POST` | `/api/v1/students/{id}/restore` | `groupId`, `reason?` | восстановление из архива/отчисления в активную группу |
|
||||
|
||||
Правила:
|
||||
|
||||
- Академотпуск доступен только `ACTIVE`; группа должна иметь заполненные
|
||||
поток, курс, номер и степень. Целевая параллель ищется по когорте
|
||||
и создаётся при отсутствии.
|
||||
- Возврат доступен только из `ACADEMIC_LEAVE`; целевая группа должна быть
|
||||
активной.
|
||||
- Отчислить нельзя уже отчисленного, выпущенного или архивного студента.
|
||||
Зачисление остаётся для истории, вход закрывает статус.
|
||||
- Архив закрывает и студента, и аккаунт (`archived_at`); зачисление
|
||||
сохраняется. Восстановление возвращает аккаунт в `ACTIVE` и переводит
|
||||
студента в выбранную группу.
|
||||
- Каждое действие пишет `membership_history` с типом, причиной и актором.
|
||||
|
||||
## 3. Архив группы, удаление и вход
|
||||
|
||||
- `POST /api/v1/groups/{id}/archive` переводит группу в `ARCHIVED`,
|
||||
не меняя статусы студентов.
|
||||
- Вход студента блокируется, если его текущая группа `ARCHIVED` или
|
||||
`GRADUATED` (`AuthService.ensureGroupOpen`). Студент без группы может
|
||||
войти, если его собственный статус и аккаунт активны.
|
||||
- Удаление пользователя требует параметр `confirm`, совпадающий с логином
|
||||
или ФИО студента (trim, точное совпадение). Иначе — `400`, данные
|
||||
не удаляются. В админской форме появилось обязательное поле подтверждения.
|
||||
- «Убрать из группы» осталось отдельным действием: оно удаляет зачисление,
|
||||
но не является отчислением. Для отчисления с историей используется
|
||||
«Отчислить».
|
||||
|
||||
## 4. UI
|
||||
|
||||
- `/admin/promotion` — список запусков, построение dry-run за выбранный год.
|
||||
- `/admin/promotion/{id}` — сводка, предупреждения, таблица элементов
|
||||
«было/стало», кнопки «Применить план» и «Откатить» по статусу.
|
||||
- На странице группы:
|
||||
- «Академ» — для активного студента;
|
||||
- «В архив» — для активного студента;
|
||||
- «Отчислить» — для активного студента;
|
||||
- «Вернуть в группу» — для `ACADEMIC_LEAVE`;
|
||||
- «Восстановить в группу» — для `ARCHIVED`/`EXPELLED`;
|
||||
- «Убрать из группы» — отдельно от отчисления.
|
||||
- «В архив» для группы — на странице группы; кнопка скрыта для уже
|
||||
архивной группы.
|
||||
- В навигацию админа добавлен пункт «Повышение».
|
||||
- Шаблоны и представления покрыты тестами, включая подписи статусов,
|
||||
счётчики и состояния before/after.
|
||||
|
||||
## 5. Тесты
|
||||
|
||||
Добавлены наборы:
|
||||
|
||||
- `PromotionServiceIntegrationTest` (6): dry-run не меняет данные,
|
||||
применение переводит группы/выпускает студентов/двигает академ,
|
||||
повторное применение идемпотентно, откат восстанавливает данные
|
||||
и удаляет созданную группу, конфликт ручных изменений, guard даты
|
||||
и дедупликация планировщика.
|
||||
- `StudentLifecycleIntegrationTest` (11): академотпуск с созданием
|
||||
и переиспользованием параллели, возврат, отчисление с блокировкой входа,
|
||||
вход из архивной группы, активной группы и без группы, восстановление
|
||||
после отчисления и архива, архив группы без смены статусов студентов,
|
||||
подтверждение удаления.
|
||||
- `AdminStage4TemplateTest` (5) и `PromotionDtoTest` (2): журнал повышения,
|
||||
кнопки apply/rollback, lifecycle-кнопки по статусам, поле подтверждения
|
||||
удаления, JSON-контракты.
|
||||
- `AuthServiceLoginBackoffTest` обновлён под новую зависимость проверки
|
||||
группы.
|
||||
|
||||
По результатам последнего прогона (2026-10-27) перечисленные наборы прошли
|
||||
с `0 failures / 0 errors`.
|
||||
|
||||
## 6. Известные пробелы и forward-only
|
||||
|
||||
- **Только ручное применение.** Планировщик создаёт только dry-run;
|
||||
автоматического применения и уведомлений нет.
|
||||
- **Один план на год.** Повторный dry-run за год возвращает уже сохранённый
|
||||
`PLANNED`-план; пересборки или удаления плана в UI нет. После завершённого
|
||||
применения новый dry-run за тот же год запрещён до отката.
|
||||
- **Откат целиком.** Поэлементного отката нет; при конфликте откатывается
|
||||
весь запуск. После ручных изменений сначала нужно вернуть данные
|
||||
к состоянию `after`.
|
||||
- **История не удаляется.** Откат добавляет `RETURN`, а не стирает
|
||||
исходные записи; журнал остаётся аудиторским следом.
|
||||
- **Групповой архив односторонний.** Отдельного endpoint/UI для возврата
|
||||
группы из архива нет; студенты архивной группы не входят.
|
||||
- **Lifecycle из ведомости.** Возврат и восстановление в UI выполняются
|
||||
в текущую группу; выбор другой группы доступен только через API.
|
||||
Массовых операций по студентам нет.
|
||||
- **JWT и блокировка.** Вход закрывается при следующей аутентификации;
|
||||
уже выданный JWT действует до истечения (до 30 дней). Это остаточный
|
||||
риск, зафиксированный в плане; аудит и hardening — этап 6.
|
||||
- **Нет новых миграций.** Используются таблицы `promotion_runs`,
|
||||
`promotion_run_items`, `membership_history`, `accounts.status`
|
||||
из миграций `V7`–`V9` этапа 1. JSONB пишется через `JdbcTemplate`
|
||||
из-за несовместимости Jackson 2/3 в Hibernate.
|
||||
@@ -20,6 +20,8 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
|
||||
| [10-stage0-report.md](10-stage0-report.md) | Отчёт этапа 0: миграции, preflight и проверка на копии БД |
|
||||
| [11-stage1-report.md](11-stage1-report.md) | Отчёт этапа 1: роли, группы-когорты, статусы, история, импорт v2 |
|
||||
| [12-stage2-report.md](12-stage2-report.md) | Отчёт этапа 2: курсы, bootstrap курса C, привязка и копирование лабораторных, миграция V10 |
|
||||
| [13-stage3-report.md](13-stage3-report.md) | Отчёт этапа 3: преподаватели, доступ к курсам, внутренние API, кабинет и профиль студента курса |
|
||||
| [14-stage4-report.md](14-stage4-report.md) | Отчёт этапа 4: повышение, академотпуск, отчисление, архив, восстановление и удаление |
|
||||
| [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |
|
||||
|
||||
## Кратко о системе
|
||||
|
||||
Reference in New Issue
Block a user