pcp: создание плана новой цепочки из вкладки «Планы» (база/без истории, окно из двух ЗРВ)
Оператор берёт за основу прошлый план (предыстория) или начинает цепочку с нуля,
набирает окно из двух реальных ЗРВ и создаёт план-кандидат; сразу показывается прогноз
будущей цепочки от него. Рабочим (якорем) план становится после ручной отправки (promote)
и приёмки — отдельного механизма якорения не вводим.
pcp-tgu-service:
- сырые ЗРВ: PlanCalculationService.rawWindows + PlanProjectionService.rawZrvWindows,
GET /api/plans/{spacecraftId}/zrv (исходные окна снапшота, не cooked-проекция);
- CreateChainPlanUseCase + POST /api/plans/new-chain: опциональная база (клон включений
в окно) либо пустая миссия без истории; прогноз цепочки через projectAfterWindow;
- ZrvWindowMatcher (старт+КПП одной зоны, конец — другой); assertDraftSlotStillValid
переведён на него — единая проверка для replace/fork-forward и окна из двух ЗРВ;
- тесты: CreateChainPlanUseCaseTest (8), ZrvWindowMatcherTest (7), правки promote-тестов.
pcp-mission-planing-service:
- POST /api/missions/blank — пустая миссия для сценария «без истории» (+ 2 теста).
pcp-tgu-ops-ui:
- API fetchZrvWindows/createChainPlan; диалог «Создать план» во вкладке «Планы»
(выбор базы, стартовой и конечной ЗРВ) с показом спрогнозированной цепочки и ручной
отправкой; подсветка ЗРВ выбранного КА на таймлайне;
- «Целевая ЗРВ» убрана из редактора (он остаётся под replace-правку); удалён ставший
мёртвым editorForkForward; createChainWindows + тесты.
This commit is contained in:
+10
@@ -19,3 +19,13 @@ data class MissionCloneRequest(
|
||||
)
|
||||
|
||||
data class MissionCloneResponse(val newMissionId: UUID)
|
||||
|
||||
/** Запрос создания пустой миссии (план новой цепочки «без истории»). */
|
||||
data class MissionBlankRequest(
|
||||
/** КА (= noradId). */
|
||||
val satelliteId: Long,
|
||||
/** Начало окна миссии (UTC naive). */
|
||||
val missionStart: LocalDateTime,
|
||||
/** Конец окна миссии (UTC naive). */
|
||||
val missionStop: LocalDateTime
|
||||
)
|
||||
|
||||
+9
@@ -300,4 +300,13 @@ class MissionController(
|
||||
@PathVariable("mission_id") id: UUID,
|
||||
@RequestBody body: MissionCloneRequest
|
||||
): MissionCloneResponse = missionPlaningService.cloneMission(id, body)
|
||||
|
||||
@Operation(
|
||||
summary = "Создать пустую миссию",
|
||||
description = "Создаёт миссию без режимов на заданное окно — для плана новой цепочки «без истории». " +
|
||||
"Возвращает newMissionId, который tgu-service использует как planId черновика."
|
||||
)
|
||||
@PostMapping("/blank")
|
||||
fun createBlankMission(@RequestBody body: MissionBlankRequest): MissionCloneResponse =
|
||||
missionPlaningService.createBlankMission(body)
|
||||
}
|
||||
|
||||
+36
@@ -30,6 +30,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteAnchor
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildResponseDTO
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.CloneMode
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionBlankRequest
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneRequest
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneResponse
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionValidationResponse
|
||||
@@ -245,6 +246,41 @@ class MissionPlaningService(
|
||||
return MissionCloneResponse(newMissionId = newMission.missionId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт пустую миссию (без режимов) на заданное окно — для плана новой цепочки «без истории».
|
||||
* Возвращает новый missionId, который tgu использует как planId черновика; включения оператор
|
||||
* добавляет сам в редакторе. Создаётся миссия (status=NEW) и её основной план (planNumber=1).
|
||||
*/
|
||||
fun createBlankMission(req: MissionBlankRequest): MissionCloneResponse {
|
||||
if (!req.missionStop.isAfter(req.missionStart)) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "missionStop must be after missionStart")
|
||||
}
|
||||
|
||||
val mission = missionRepository.save(
|
||||
MissionEntity(
|
||||
satelliteId = req.satelliteId,
|
||||
station = null,
|
||||
missionStart = req.missionStart,
|
||||
missionStop = req.missionStop,
|
||||
status = "NEW"
|
||||
)
|
||||
)
|
||||
planRepository.save(
|
||||
PlanEntity(
|
||||
mission = mission,
|
||||
planStart = req.missionStart,
|
||||
planStop = req.missionStop,
|
||||
planNumber = 1
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Создана пустая миссия {}: satelliteId={}, окно [{}–{}]",
|
||||
mission.missionId, req.satelliteId, req.missionStart, req.missionStop
|
||||
)
|
||||
return MissionCloneResponse(newMissionId = mission.missionId)
|
||||
}
|
||||
|
||||
fun modesByMission(id: UUID): List<ModeResponseDTO> {
|
||||
if (!missionRepository.existsById(id)) {
|
||||
throw notFound(id)
|
||||
|
||||
+39
@@ -218,6 +218,45 @@ class MissionEditModesTest {
|
||||
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
||||
}
|
||||
|
||||
// --- createBlankMission (план новой цепочки «без истории») ---
|
||||
|
||||
@Test
|
||||
fun `createBlankMission persists a mission and its plan with the given window`() {
|
||||
val saved = arrayOfNulls<space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity>(1)
|
||||
`when`(missionRepository.save(anyNonNull())).thenAnswer { inv ->
|
||||
val m = inv.arguments[0] as space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
|
||||
saved[0] = m
|
||||
m
|
||||
}
|
||||
val start = LocalDateTime.of(2026, 6, 10, 12, 0)
|
||||
val stop = LocalDateTime.of(2026, 6, 10, 12, 10)
|
||||
|
||||
val response = service.createBlankMission(
|
||||
space.nstart.pcp.pcp_mission_planing_service.controller.MissionBlankRequest(
|
||||
satelliteId = 25544, missionStart = start, missionStop = stop
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(saved[0]!!.missionId, response.newMissionId)
|
||||
assertEquals(25544L, saved[0]!!.satelliteId)
|
||||
assertEquals(start, saved[0]!!.missionStart)
|
||||
assertEquals(stop, saved[0]!!.missionStop)
|
||||
assertEquals("NEW", saved[0]!!.status)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createBlankMission with stop not after start yields 400`() {
|
||||
val t = LocalDateTime.of(2026, 6, 10, 12, 0)
|
||||
val ex = assertThrows<ResponseStatusException> {
|
||||
service.createBlankMission(
|
||||
space.nstart.pcp.pcp_mission_planing_service.controller.MissionBlankRequest(
|
||||
satelliteId = 25544, missionStart = t, missionStop = t
|
||||
)
|
||||
)
|
||||
}
|
||||
assertEquals(HttpStatus.BAD_REQUEST, ex.statusCode)
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private fun survey(): SurveyModeEntity {
|
||||
|
||||
Reference in New Issue
Block a user