Files
dc-observatio/docs/architect/PCP_TGU_PLAN_STATUS_LIFECYCLE.md
T
Дмитрий Соловьев fd3603a086 pcp-tgu: статусы плана — актуальная легенда с тултипами + док жизненного цикла
- STATUS_STYLES в порядке жизненного цикла: убран легаси ACCEPTED, добавлены
  LAID_IN/NOT_ACCEPTED/LAYIN_FAILED, у каждого статуса человеческое описание.
- Легенда вкладки «Планы»: тултипы по наведению (title=description), убрана
  устаревшая приписка про стрелки.
- TimelineStatusStyle: добавлено обязательное поле description.
- HandlePlanDecisionUseCase: TODO(layin-confirmed) — обработчик приёма события
  «план заложен» (AWAITING_LAYIN → LAID_IN) ещё не реализован.
- docs/architect/PCP_TGU_PLAN_STATUS_LIFECYCLE.md: справочник по каждому статусу
  (когда наступает / что значит / какой переход), диаграмма, зарезервированный ACCEPTED.
2026-06-04 13:06:58 +03:00

13 KiB

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

Справочник по статусам business-plan в расписании ТГУ: когда наступает каждый статус, что он означает и какой переход его порождает. Источник правды — enum PlannedPlanStatus; переходы — в service/IssueDuePlanWorker, service/HandlePlanDecisionUseCase, service/CreateDraftUseCase, service/CreateChainPlanUseCase, dto/PlanResponse.

Эти же статусы (кроме «зарезервированных», см. ниже) показаны в легенде вкладки «Планы» (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 (materializeIssuingPlaninsertIssuingPlan).
  • В 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 (handleNotAcceptedapplyNegativeFeedback).
  • Что значит. Терминальный негатив по базовому плану. В отличие от REJECTED — это технический негатив, поэтому запускается срочная перевыдача на ближайшую ЗРВ (reissueOnNearestWindow).
  • Событие, пришедшее не по текущему базовому плану (устаревшее/дубль), игнорируется.

LAYIN_FAILED — не заложен (негативный фидбек)

  • Когда. Пришло внешнее событие «не заложен» по текущему базовому плану в AWAITING_LAYIN (handleLayinFailedapplyNegativeFeedback).
  • Что значит. Терминальный негатив: закладка не удалась. Поведение сейчас идентично NOT_ACCEPTEDсрочная перевыдача на ближайшую ЗРВ.

START_AMBIGUOUS — неопределённый старт

  • Когда. Из ISSUING не удалось достоверно стартовать BPMN-процесс выдачи (исключение в startPreparedAttemptmarkAmbiguous); 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 — за сколько до старта авто-воркер начинает выдачу головы цепочки.

См. также