13 KiB
13 KiB
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.
Историческая оценка этапа 4; закрыто в identity
V11(access 15 минут, refresh 7 дней с отзывом) — см. 16. - Нет новых миграций. Используются таблицы
promotion_runs,promotion_run_items,membership_history,accounts.statusиз миграцийV7–V9этапа 1. JSONB пишется черезJdbcTemplateиз-за несовместимости Jackson 2/3 в Hibernate.