# Жизненный цикл статусов плана ТГУ Справочник по статусам 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)