pcp-tgu: якорь цепочки на LAID_IN, параллельные выдачи, SUPERSEDED
Базой цепочки план становится на ЗАКЛАДКЕ (LAID_IN), а не на приёмке.
После принятого плана можно отправить коррекцию — она тоже примется;
несколько выдач «в полёте» по одному КА теперь норма, а не конфликт.
- ACCEPT больше не двигает currentBasePlanId/chainVersion и не запирает
слот — только переводит план в AWAITING_LAYIN.
- Новый позитивный топик layin-confirmed + LayinConfirmedConsumer →
handleLayinConfirmed: AWAITING_LAYIN → LAID_IN, план становится базой
(chainVersion++), конкуренты на пересекающемся окне гасятся в новый
терминальный статус SUPERSEDED. Гард ±10 мин (LAYIN_MIN_SEPARATION_MINUTES):
нельзя заложить план со стартом в пределах 10 мин от уже заложенного (409).
- «В полёте» считается из статусов планов (ISSUING/WAITING_DECISION), а не
из единственного active-слота; снят гард «идёт выдача другого плана».
IssueDuePlanWorker гейтится hasBlockingInFlight, базу нового плана берёт
как latestCommittedPlanId; grace больше не двигает базу.
- PlanProjectionService якорит по самому дальнему committed-окну
{ISSUING, WAITING_DECISION, AWAITING_LAYIN, LAID_IN}.
- REJECT ставит паузу только если по КА не осталось живых кандидатов.
- CreateDraftUseCase: replace-черновик наследует ПРЕДШЕСТВЕННИКА исходного
плана (base.basePlanId), а не сам план — коррекция занимает тот же слот;
fork-forward по-прежнему ссылается на базу. Идемпотентность черновиков —
по слоту (КА + окно), а не по basePlanId.
- Тест-контроллер: POST /test/tgu/plans/{id}/confirm-layin (через BFF
pcp-ui-service) — имитация события «план заложен».
- Док PCP_TGU_PLAN_STATUS_LIFECYCLE обновлён (LAID_IN-переход, SUPERSEDED).
This commit is contained in:
@@ -27,9 +27,10 @@
|
||||
│
|
||||
┌────────────────────────────────┼────────────────────────────────┐
|
||||
событие «заложен» событие «не заложен» событие «не принят»
|
||||
(пока не реализовано) ▼ ▼
|
||||
▼ LAYIN_FAILED NOT_ACCEPTED
|
||||
LAID_IN (срочная перевыдача) (срочная перевыдача)
|
||||
▼ ▼ ▼
|
||||
LAID_IN LAYIN_FAILED NOT_ACCEPTED
|
||||
(база цепочки; конкуренты (срочная перевыдача) (срочная перевыдача)
|
||||
того же окна → SUPERSEDED)
|
||||
|
||||
при сбое старта BPMN из ISSUING ─────────────────────────────────────────► START_AMBIGUOUS
|
||||
```
|
||||
@@ -75,21 +76,25 @@
|
||||
|
||||
### AWAITING_LAYIN — принят, ждёт подтверждения закладки
|
||||
- **Когда.** Пришло решение ACCEPTED (`acceptPlan`).
|
||||
- **Что значит.** План принят и **оптимистично считается заложенным**; это текущий **базовый план
|
||||
цепочки** (`state.currentBasePlanId = plan.planId`, `chainVersion += 1`). Запускается джоб
|
||||
обновления слотов принятого плана (`acceptedPlanSlotsUpdateJobService.enqueue`).
|
||||
- **Дальше.** При подтверждении закладки → `LAID_IN` (терминальный успех; **событие пока не
|
||||
реализовано**, см. ниже). При негативе — `LAYIN_FAILED`/`NOT_ACCEPTED`.
|
||||
- В легенде это де-факто «принятый/заложенный» статус (зелёный). Перевыдача в свой слот ещё
|
||||
- **Что значит.** План принят и ждёт закладки. **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`) — проекция становится пустой, воркер ничего не выдаёт. Базовый план не
|
||||
трогается.
|
||||
бессмысленно). Цепочка ставится **на паузу** (`pausedAfterReject = true`) — **только если по КА не
|
||||
осталось других живых кандидатов** (в полёте/приняты, но не заложены): при нескольких выдачах
|
||||
отказ одной не глушит цепочку, пока жив другой кандидат. На паузе проекция пуста, воркер ничего не
|
||||
выдаёт. Базовый план не трогается.
|
||||
- **Дальше.** Оператор вручную создаёт исправленный план; его promote/ручная выдача снимают паузу.
|
||||
- В UI у КА с паузой рисуется пометка «⏸ на паузе».
|
||||
|
||||
@@ -112,18 +117,23 @@
|
||||
- **Что значит.** Неизвестно, ушёл план на борт или нет — требуется ручной разбор оператором.
|
||||
Опасный статус, в 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`) — заранее, чтобы при появлении события
|
||||
цвет уже был.
|
||||
### 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). Базовый план не трогается.
|
||||
|
||||
## Зарезервированные / не возникающие сейчас статусы
|
||||
|
||||
|
||||
Reference in New Issue
Block a user