fix(tgu-ui): гасить «Создать черновик» для стартовавшего плана (replace→422)

Кнопка предлагала replace-черновик для уже стартовавшего плана, хотя бэк
(CreateDraftUseCase.createReplaceDraft) возвращает 422 «окно выдачи заморожено».
Гард canCreateDraftFromPlan теперь требует startInFuture (зеркалит редактор);
для стартовавших eligible-планов вместо кнопки — amber-подсказка про fork-forward.
This commit is contained in:
Дмитрий Соловьев
2026-06-10 15:46:57 +03:00
parent 3765efb4fe
commit 11380fced5
@@ -82,14 +82,20 @@ export function TguPlanDetails({
const duration = formatDuration(plan.endMs - plan.startMs); const duration = formatDuration(plan.endMs - plan.startMs);
// Окно решения закрылось, если с момента старта плана прошло больше grace-периода. // Окно решения закрылось, если с момента старта плана прошло больше grace-периода.
const decisionWindowClosed = Date.now() > plan.startMs + DECISION_GRACE_MINUTES * 60_000; const decisionWindowClosed = Date.now() > plan.startMs + DECISION_GRACE_MINUTES * 60_000;
// Закладку нельзя подтверждать, пока начало плана ещё в будущем: план закладывается на КА // Начало плана ещё в будущем. Нужно и для закладки (план закладывается по факту старта,
// по факту наступления своего старта, а не заранее. // не заранее), и для replace-черновика (после старта окно выдачи заморожено — бэк вернёт 422).
const layinStartInFuture = plan.startMs > Date.now(); const startInFuture = plan.startMs > Date.now();
// «Создать черновик на основе плана» (replace, окно 1:1, все включения) доступно для // «Создать черновик на основе плана» (replace, окно 1:1, все включения) доступно для
// персистентных правимых баз. DRAFT уже имеет свои действия; PLANNED — виртуальная проекция, // персистентных правимых баз, у которых старт ещё не наступил. DRAFT уже имеет свои действия;
// не база; LAID_IN — replace 1:1 запрещён (слот зафиксирован, только fork-forward). // PLANNED — виртуальная проекция, не база; LAID_IN — replace 1:1 запрещён (слот зафиксирован);
const canCreateDraftFromPlan = // стартовавший план — окно выдачи заморожено (CreateDraftUseCase.createReplaceDraft → 422),
// продолжение возможно только fork-forward (отдельный поток). Гард зеркалит редактор, чтобы
// не предлагать кнопку, которая заведомо упадёт «Unprocessable Content».
const draftableStatus =
plan.status !== "DRAFT" && plan.status !== "PLANNED" && plan.status !== "LAID_IN"; plan.status !== "DRAFT" && plan.status !== "PLANNED" && plan.status !== "LAID_IN";
const canCreateDraftFromPlan = draftableStatus && startInFuture;
// Статус позволял бы replace-черновик, но план уже стартовал — объясняем, почему кнопки нет.
const draftBlockedByStart = draftableStatus && !startInFuture;
const reject = () => { const reject = () => {
const reason = window.prompt("Reason для REJECTED", "UI_TEST_REJECTED"); const reason = window.prompt("Reason для REJECTED", "UI_TEST_REJECTED");
@@ -229,15 +235,23 @@ export function TguPlanDetails({
</div> </div>
)} )}
{draftBlockedByStart && (
<div className="tgu-alert tgu-alert--warning">
Черновик-копию (replace, то же окно) можно создать только до старта плана окно
выдачи уже заморожено. Продолжение цепочки на будущую ЗРВ (fork-forward) выполняется
отдельно.
</div>
)}
{plan.status === "AWAITING_LAYIN" && onConfirmLayin && ( {plan.status === "AWAITING_LAYIN" && onConfirmLayin && (
<div className="tgu-details__test-block"> <div className="tgu-details__test-block">
<div className="tgu-details__test-label">ТЕСТОВЫЙ ФУНКЦИОНАЛ</div> <div className="tgu-details__test-label">ТЕСТОВЫЙ ФУНКЦИОНАЛ</div>
<button <button
className="tgu-button tgu-button--accept" className="tgu-button tgu-button--accept"
type="button" type="button"
disabled={decisionInFlight || layinConflict || layinStartInFuture} disabled={decisionInFlight || layinConflict || startInFuture}
title={ title={
layinStartInFuture startInFuture
? "Начало плана ещё не наступило — подтвердить закладку можно только после его старта" ? "Начало плана ещё не наступило — подтвердить закладку можно только после его старта"
: layinConflict : layinConflict
? "По КА уже заложен план со стартом в пределах ±10 минут — закладка недоступна" ? "По КА уже заложен план со стартом в пределах ±10 минут — закладка недоступна"
@@ -247,12 +261,12 @@ export function TguPlanDetails({
> >
Подтвердить закладку Подтвердить закладку
</button> </button>
{layinStartInFuture && ( {startInFuture && (
<div className="tgu-details__test-hint"> <div className="tgu-details__test-hint">
Начало плана ещё в будущем подтвердить закладку можно только после наступления его старта. Начало плана ещё в будущем подтвердить закладку можно только после наступления его старта.
</div> </div>
)} )}
{!layinStartInFuture && layinConflict && ( {!startInFuture && layinConflict && (
<div className="tgu-details__test-hint"> <div className="tgu-details__test-hint">
По КА уже есть заложенный план со стартом в пределах ±10 минут подтвердить нельзя. По КА уже есть заложенный план со стартом в пределах ±10 минут подтвердить нельзя.
</div> </div>