# Жизненный цикл статусов плана ТГУ Справочник по статусам 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 (цепочка на паузе) │ ┌────────────────────────────────┼────────────────────────────────┐ событие «заложен» событие «не заложен» событие «не принят» (пока не реализовано) ▼ ▼ ▼ LAYIN_FAILED NOT_ACCEPTED LAID_IN (срочная перевыдача) (срочная перевыдача) при сбое старта 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`). - **Что значит.** План принят и **оптимистично считается заложенным**; это текущий **базовый план цепочки** (`state.currentBasePlanId = plan.planId`, `chainVersion += 1`). Запускается джоб обновления слотов принятого плана (`acceptedPlanSlotsUpdateJobService.enqueue`). - **Дальше.** При подтверждении закладки → `LAID_IN` (терминальный успех; **событие пока не реализовано**, см. ниже). При негативе — `LAYIN_FAILED`/`NOT_ACCEPTED`. - В легенде это де-факто «принятый/заложенный» статус (зелёный). Перевыдача в свой слот ещё возможна, пока до старта больше `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`. - **Что значит.** Подтверждённая закладка, терминальный успех; «точка невозврата» — дальше план меняется только через fork-forward (используется в гардах `CreateDraftUseCase`, `assertReissuableInSlot`). - **⚠️ TODO — не реализовано.** Потребителя события «план заложен» сейчас **нет**, поэтому план в `LAID_IN` не переходит и остаётся в `AWAITING_LAYIN`. Нужно: Kafka-consumer (по аналогии с `LayinFailedConsumer`/`PlanNotAcceptedConsumer`) + `handleLayinConfirmed` в `HandlePlanDecisionUseCase` (`AWAITING_LAYIN → LAID_IN` по текущему базовому плану). Маркер-TODO стоит в коде `HandlePlanDecisionUseCase` рядом с обработчиками негативного фидбека. - В легенде статус показан (зелёный, темнее `AWAITING_LAYIN`) — заранее, чтобы при появлении события цвет уже был. ## Зарезервированные / не возникающие сейчас статусы - **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)