5049395911
Бэкенд (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.
80 lines
3.2 KiB
TypeScript
80 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
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);
|
|
expect(isOperationalPlatform(platform({ status: " operational " }))).toBe(true);
|
|
expect(isOperationalPlatform(platform({ status: "STANDBY" }))).toBe(false);
|
|
expect(isOperationalPlatform(platform({ status: "LOST" }))).toBe(false);
|
|
expect(isOperationalPlatform(platform({ status: null }))).toBe(false);
|
|
expect(isOperationalPlatform(platform({ status: undefined }))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("operationalSpacecraftIds", () => {
|
|
it("собирает norad-id в виде строк и пропускает платформы без norad-id", () => {
|
|
const ids = operationalSpacecraftIds([
|
|
platform({ noradId: 2 }),
|
|
platform({ noradId: 30 }),
|
|
platform({ noradId: null }),
|
|
platform({ noradId: undefined })
|
|
]);
|
|
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());
|
|
});
|
|
});
|