Files
dc-observatio/services/pcp-tgu-ops-ui/src/features/tgu-planning/createChainWindows.ts
T
Дмитрий Соловьев 294b17757d pcp: время — сквозной naive LocalDateTime как локальное настенное время (без UTC)
Отказ от остаточной UTC-семантики: naive LocalDateTime трактуется как
локальное время единой зоны всех сервисов.

Backend (границы внешних систем):
- VisibilityPayloadParser, SyncSpacecraftFromNsiUseCase: ZoneOffset.UTC →
  ZoneId.systemDefault() для проекции внешних Instant/ISO-с-Z в naive.
- тесты границ перевёрнуты на systemDefault (под Europe/Moscow ждут +3).
- чистка UTC-формулировок в DTO-комментариях, мёртвых ссылок на
  docs/standards/DATETIME_UTC.md.

Frontend (pcp-tgu-ops-ui):
- utils/utcTime.ts → utils/localTime.ts: parseLocal/formatLocal*/toLocalIso/
  toLocalInputValue с локальной семантикой (без дописывания Z и timeZone:UTC).
- замена getUTC*/Date.UTC/toISOString-сериализации на локальные аналоги.
- убраны пользовательские подписи «UTC» в редакторе/таймлайне.
- удалён utcTime.guard.test.ts (форсил обратный UTC-инвариант); тест-фикстуры
  сделаны зоно-независимыми. tsc + vitest зелёные под TZ=UTC и TZ=Europe/Moscow.

Включает также прочие незакоммиченные правки рабочего дерева.
2026-06-04 09:50:39 +03:00

66 lines
2.6 KiB
TypeScript

import { parseLocal, toLocalIso } from "../../utils/localTime";
import type { CreateChainPlanRequest, ZrvWindowResponse } from "../../api/tguPlanApi";
/**
* Опция выбора ЗРВ в диалоге «Создать план». Из стартовой зоны берутся начало окна и КПП, из
* конечной — конец окна. `start`/`stop` — naive (ISO без зоны, локальное), как ждёт `LocalDateTime` бэка;
* `startMs`/`endMs` — для сортировки/сравнения и подсветки на таймлайне.
*/
export type ZrvOption = {
/** Детерминированный id зоны (для value в select и key). */
id: string;
startMs: number;
endMs: number;
/** Naive (локальное) начала зоны. */
start: string;
/** Naive (локальное) конца зоны. */
stop: string;
kppId: string;
};
/** Будущие ЗРВ-зоны (строго позже nowMs), по возрастанию начала. */
export function futureZrvOptions(windows: ZrvWindowResponse[], nowMs: number): ZrvOption[] {
return windows
.map((zrv) => {
const startMs = parseLocal(zrv.startTime);
const endMs = parseLocal(zrv.endTime);
return {
id: `${zrv.kppId}|${zrv.startTime}|${zrv.endTime}`,
startMs,
endMs,
start: toNaive(startMs),
stop: toNaive(endMs),
kppId: zrv.kppId
};
})
.filter((option) => option.startMs > nowMs)
.sort((a, b) => a.startMs - b.startMs);
}
/**
* Собирает тело запроса создания плана новой цепочки из выбора оператора. Окно — от начала стартовой
* зоны до конца конечной; `basePlanId` — предыстория (или null для цепочки без истории). Бросает,
* если конец окна не позже начала (конечная зона раньше стартовой).
*/
export function buildChainPlanRequest(
spacecraftId: string,
basePlanId: string | null,
start: ZrvOption,
end: ZrvOption
): CreateChainPlanRequest {
if (end.endMs <= start.startMs) {
throw new Error("Конец окна должен быть позже начала: выберите конечную ЗРВ не раньше стартовой.");
}
return {
spacecraftId,
basePlanId,
windowStart: start.start,
windowStop: end.stop,
windowKppId: start.kppId
};
}
function toNaive(ms: number): string {
return toLocalIso(ms);
}