pcp-tgu: пауза цепочки после REJECTED + режим «Черновики» + UI цепочек/ЗРВ
Бэкенд (pcp-tgu-service): - REJECTED трактуется как «план неправильный»: авто-перевыдача больше НЕ взводится, цепочка встаёт на паузу (новый флаг SpacecraftPlanningStateEntity.pausedAfterReject, миграция V13). Технические негативы («не заложен»/«не принят») по-прежнему дают срочную перевыдачу. - Пауза централизована в PlanProjectionService.project(): для отклонённой цепочки проекция пустая, воркер выдачи ничего не материализует. Флаг снимается при ручной выдаче/промоуте черновика/приёмке (оператор вмешался). - Режим «Черновики»: GET /plans/drafts (опц. ?spacecraftId) -> только DRAFT. - Гард дублей черновиков: (КА + начало + конец) уникальны среди DRAFT, повторное создание окна -> 409. - ZrvWindowMatcher: сравнение по истинной abs-длительности Duration (<=1с), устойчиво к усечению ISO на фронте до целых секунд. Фронт (pcp-tgu-ops-ui): вкладка «Планы» — таймлайн цепочек, детали плана, панель ЗРВ (TguZrvPanel), диалог создания цепочки, режим черновиков. Удалён устаревший docs/tasks/TASK_tgu-plan-versioning-manual-reissue.md.
This commit is contained in:
@@ -1,11 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { TguPlatform } from "../../model/tguTypes";
|
||||
import { isOperationalPlatform, operationalSpacecraftIds } from "./tguTimelineMapper";
|
||||
import type { TguPlatform, TguPlanStatus } from "../../model/tguTypes";
|
||||
import type { TguPlanUi } from "../../model/timelineTypes";
|
||||
import { isOperationalPlatform, operationalSpacecraftIds, pausedSpacecraftIds } from "./tguTimelineMapper";
|
||||
|
||||
function platform(overrides: Partial<TguPlatform>): TguPlatform {
|
||||
return { id: "x", noradId: 1, status: "OPERATIONAL", ...overrides };
|
||||
}
|
||||
|
||||
function uiPlan(spacecraftId: string, status: TguPlanStatus, startMs: number): TguPlanUi {
|
||||
return {
|
||||
planId: `${spacecraftId}-${status}-${startMs}`,
|
||||
spacecraftId,
|
||||
startTime: new Date(startMs).toISOString().slice(0, 19),
|
||||
endTime: new Date(startMs + 3_600_000).toISOString().slice(0, 19),
|
||||
kppId: "KPP-1",
|
||||
status,
|
||||
startMs,
|
||||
endMs: startMs + 3_600_000
|
||||
};
|
||||
}
|
||||
|
||||
describe("isOperationalPlatform", () => {
|
||||
it("принимает только статус OPERATIONAL без учёта регистра и пробелов", () => {
|
||||
expect(isOperationalPlatform(platform({ status: "OPERATIONAL" }))).toBe(true);
|
||||
@@ -28,3 +42,38 @@ describe("operationalSpacecraftIds", () => {
|
||||
expect(ids).toEqual(new Set(["2", "30"]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("pausedSpacecraftIds", () => {
|
||||
const t0 = Date.UTC(2026, 5, 4, 0, 0, 0);
|
||||
const hour = 3_600_000;
|
||||
|
||||
it("помечает КА паузой, если есть REJECTED и нет продвинувшего цепочку плана после него", () => {
|
||||
const paused = pausedSpacecraftIds([
|
||||
uiPlan("100", "REJECTED", t0),
|
||||
// проекция и черновик паузу не снимают
|
||||
uiPlan("100", "PLANNED", t0 + 2 * hour),
|
||||
uiPlan("100", "DRAFT", t0)
|
||||
]);
|
||||
expect(paused).toEqual(new Set(["100"]));
|
||||
});
|
||||
|
||||
it("снимает паузу, если оператор отправил исправленный план (в полёте) на том же/более позднем окне", () => {
|
||||
const paused = pausedSpacecraftIds([
|
||||
uiPlan("100", "REJECTED", t0),
|
||||
uiPlan("100", "WAITING_DECISION", t0)
|
||||
]);
|
||||
expect(paused.has("100")).toBe(false);
|
||||
});
|
||||
|
||||
it("не помечает паузой, если цепочка ушла вперёд (ACCEPTED после отказа)", () => {
|
||||
const paused = pausedSpacecraftIds([
|
||||
uiPlan("100", "REJECTED", t0),
|
||||
uiPlan("100", "ACCEPTED", t0 + 5 * hour)
|
||||
]);
|
||||
expect(paused.has("100")).toBe(false);
|
||||
});
|
||||
|
||||
it("без REJECTED никого не помечает", () => {
|
||||
expect(pausedSpacecraftIds([uiPlan("100", "WAITING_DECISION", t0)])).toEqual(new Set());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user