# Жизненный цикл статусов плана ТГУ

Справочник по статусам business-plan в расписании ТГУ: когда наступает каждый статус, что он
означает и какой переход его порождает. Источник правды — enum
[`PlannedPlanStatus`](../../services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/entity/PlannedPlanStatus.kt);
переходы — в `service/IssueDuePlanWorker`, `service/HandlePlanDecisionUseCase`,
`service/CreateDraftUseCase`, `service/CreateChainPlanUseCase`, `dto/PlanResponse`.

Эти же статусы (кроме «зарезервированных», см. ниже) показаны в легенде вкладки «Планы»
([`tguStatus.ts`](../../services/pcp-tgu-ops-ui/src/features/tgu-planning/tguStatus.ts)) и задают
цвет плашек на таймлайне.

## Диаграмма переходов (упрощённо)

```
                         создать черновик
   (нет плана) ───────────────────────────────►  DRAFT
                                                    │ promote («Применить») / авто-выдача
                                                    ▼
   проекция будущего  ····(виртуально)····►  PLANNED ──(подошёл срок, авто-выдача)──►  ISSUING
                                                                                        │ BPMN-процесс стартовал
                                                                                        ▼
                                                                                 WAITING_DECISION
                                            ACCEPTED-решение │           │ REJECTED-решение
                                                             ▼           ▼
                                                      AWAITING_LAYIN   REJECTED  (цепочка на паузе)
                                                             │
                            ┌────────────────────────────────┼────────────────────────────────┐
                  событие «заложен»              событие «не заложен»            событие «не принят»
                            ▼                                ▼                              ▼
                        LAID_IN                        LAYIN_FAILED                   NOT_ACCEPTED
                  (база цепочки; конкуренты         (срочная перевыдача)          (срочная перевыдача)
                   того же окна → SUPERSEDED)

   при сбое старта BPMN из ISSUING ─────────────────────────────────────────►  START_AMBIGUOUS
```

## Статусы

### DRAFT — черновик
- **Когда.** Оператор создаёт план через «Создать план» (`CreateChainPlanUseCase`) или «Добавить»/
  редактирование (`CreateDraftUseCase`).
- **Что значит.** План в стадии правки. Персистится, но **не** является головой цепочки, **не**
  попадает в проекцию/read-путь как запланированный слот и **не** выдаётся авто-воркером.
- **Дальше.** Становится активным только после promote («Применить») — переходит в `ISSUING`.
  Удаление черновика (abandon) убирает его и клон-миссию.
- В UI помечен как «ЧЕРНОВИК», виден только при включённом режиме «Черновики».

### PLANNED — проекция будущего (виртуальный)
- **Когда.** Вычисляется на лету `PlanProjectionService`/`PlanResponse.projected()` при чтении
  расписания. **Никогда не пишется в БД.**
- **Что значит.** Предполагаемый будущий слот цепочки (следующие ЗРВ) — «как пойдёт цепочка, если
  ничего не менять». Не несёт `basePlanId`, стартом цепочки не считается.
- **Дальше.** Когда подходит срок выдачи, по этому слоту авто-воркер материализует реальный план
  сразу в `ISSUING` (`materializeIssuingPlan` → `insertIssuingPlan`).
- В UI виден только при включённом тумблере «Возможные будущие планы».

### ISSUING — идёт выдача
- **Когда.** `IssueDuePlanWorker` начинает выдачу: promote черновика (`prepareDraftPromotion`),
  ручная (пере)выдача конкретного плана (`prepareSpecificPlan`) или авто-материализация головы
  проекции (`materializeIssuingPlan`).
- **Что значит.** Запущен BPMN-процесс выдачи плана на борт; создан attempt со статусом `STARTING`.
- **Дальше.** Успешный старт процесса → `WAITING_DECISION` (`markStarted`). Сбой старта BPMN →
  `START_AMBIGUOUS` (`markAmbiguous`).

### WAITING_DECISION — ожидает решения
- **Когда.** BPMN-процесс выдачи успешно стартовал, attempt → `STARTED` (`markStarted`).
- **Что значит.** План выдан, ждём решения оператора/борта: ACCEPTED либо REJECTED.
- **Дальше.**
  - ACCEPTED → `AWAITING_LAYIN` (`acceptPlan`).
  - REJECTED → `REJECTED` (`rejectPlan`).
  - **Grace-окно.** Если решения нет дольше `waitingDecisionGraceMinutes` после `startTime`, цепочка
    «молчаливо» едет дальше: якорь сдвигается на этот план, блокировка снимается, но **статус
    остаётся `WAITING_DECISION`** (`tryAdvancePastUnconfirmedPlan`). Запоздалое решение запишется на
    attempt и ушедшую вперёд цепочку не тронет. Поэтому в UI кнопки решения гасятся после grace.

### AWAITING_LAYIN — принят, ждёт подтверждения закладки
- **Когда.** Пришло решение ACCEPTED (`acceptPlan`).
- **Что значит.** План принят и ждёт закладки. **ACCEPT больше НЕ делает план базой и не двигает
  `chainVersion`** — это разъединено намеренно: после приёмки можно отправить и принять коррекцию
  того же окна, поэтому по одному окну может одновременно «лететь» несколько AWAITING_LAYIN/
  WAITING_DECISION. Базой и версией цепочки управляет только закладка (`LAID_IN`). На ACCEPT
  запускается джоб обновления слотов принятого плана (`acceptedPlanSlotsUpdateJobService.enqueue`).
- **Дальше.** При подтверждении закладки → `LAID_IN` (становится базой). При негативе —
  `LAYIN_FAILED`/`NOT_ACCEPTED`. Если базой стала **другая** коррекция того же окна — этот план
  гасится в `SUPERSEDED`.
- В легенде это «принятый, ждёт закладки» статус (зелёный). Перевыдача/коррекция в свой слот ещё
  возможна, пока до старта больше `layinFreezeMinutes` (после — «точка невозврата», только
  fork-forward).

### REJECTED — отклонён
- **Когда.** Пришло решение REJECTED (`rejectPlan`).
- **Что значит.** План признан неправильным. Авто-перевыдача **не** взводится (повторять тот же план
  бессмысленно). Цепочка ставится **на паузу** (`pausedAfterReject = true`) — **только если по КА не
  осталось других живых кандидатов** (в полёте/приняты, но не заложены): при нескольких выдачах
  отказ одной не глушит цепочку, пока жив другой кандидат. На паузе проекция пуста, воркер ничего не
  выдаёт. Базовый план не трогается.
- **Дальше.** Оператор вручную создаёт исправленный план; его promote/ручная выдача снимают паузу.
- В UI у КА с паузой рисуется пометка «⏸ на паузе».

### NOT_ACCEPTED — не принят (негативный фидбек)
- **Когда.** Пришло внешнее событие «не принят» по текущему базовому плану в `AWAITING_LAYIN`
  (`handleNotAccepted` → `applyNegativeFeedback`).
- **Что значит.** Терминальный негатив по базовому плану. В отличие от REJECTED — это **технический**
  негатив, поэтому запускается **срочная перевыдача** на ближайшую ЗРВ (`reissueOnNearestWindow`).
- Событие, пришедшее не по текущему базовому плану (устаревшее/дубль), игнорируется.

### LAYIN_FAILED — не заложен (негативный фидбек)
- **Когда.** Пришло внешнее событие «не заложен» по текущему базовому плану в `AWAITING_LAYIN`
  (`handleLayinFailed` → `applyNegativeFeedback`).
- **Что значит.** Терминальный негатив: закладка не удалась. Поведение сейчас идентично
  `NOT_ACCEPTED` — **срочная перевыдача** на ближайшую ЗРВ.

### START_AMBIGUOUS — неопределённый старт
- **Когда.** Из `ISSUING` не удалось достоверно стартовать BPMN-процесс выдачи (исключение в
  `startPreparedAttempt` → `markAmbiguous`); attempt → `AMBIGUOUS`.
- **Что значит.** Неизвестно, ушёл план на борт или нет — требуется ручной разбор оператором.
  Опасный статус, в UI выделен пунктиром.

### LAID_IN — заложен (терминальный успех, база цепочки)
- **Когда.** Пришло внешнее событие «план заложен» по плану в `AWAITING_LAYIN`
  (`LayinConfirmedConsumer` → `handleLayinConfirmed`, топик `layin-confirmed`).
- **Что значит.** Подтверждённая закладка — **именно она делает план базой цепочки**
  (`state.currentBasePlanId = plan.planId`, `chainVersion += 1`, снимается пауза/срочная
  перевыдача). «Точка невозврата»: дальше план меняется только через fork-forward (гарды
  `CreateDraftUseCase`, `assertReissuableInSlot`).
- **Supersede конкурентов.** Все ещё не заложенные планы КА (ISSUING/WAITING_DECISION/AWAITING_LAYIN)
  с окном, пересекающим окно заложенного, переводятся в `SUPERSEDED` (`supersedeCompetingPlans`).
- Событие по плану не в `AWAITING_LAYIN` (устаревшее/дубль/уже вытеснён) — игнорируется.
- В легенде — зелёный, темнее `AWAITING_LAYIN`.

### SUPERSEDED — вытеснён заложенной коррекцией (терминал)
- **Когда.** По тому же окну КА другой план ушёл в `LAID_IN`, а этот был конкурирующей выдачей/
  коррекцией (`supersedeCompetingPlans`).
- **Что значит.** Терминал, цепочку не двигает. Запоздавшее решение по его attempt записывается, но
  игнорируется (план уже не in-flight). Базовый план не трогается.

## Зарезервированные / не возникающие сейчас статусы

- **ACCEPTED** — в enum присутствует, но приёмка (`acceptPlan`) переводит план **сразу** в
  `AWAITING_LAYIN`, минуя `ACCEPTED`. Фактически это легаси-значение, нигде не присваивается; в
  легенду вкладки «Планы» намеренно не вынесено.

## Связанные конфиги (`PlanningProperties`)
- `waitingDecisionGraceMinutes` — сколько ждём решение после старта, прежде чем «молчаливо» ехать
  дальше (см. `WAITING_DECISION`).
- `layinFreezeMinutes` — за сколько минут до старта окно выдачи замораживается («точка невозврата»):
  перевыдача в свой слот запрещена, только fork-forward.
- `notificationBeforeStartMinutes` — за сколько до старта авто-воркер начинает выдачу головы цепочки.

## См. также
- [PCP_ARCHITECTURE.md](PCP_ARCHITECTURE.md)
- [PCP_TGU_OPS_UI_CAPABILITY_MAP.md](PCP_TGU_OPS_UI_CAPABILITY_MAP.md)
