From 3fd702ca47f0bf6647a34a800c7eb71e50a1dd5f Mon Sep 17 00:00:00 2001 From: emelianov Date: Fri, 29 May 2026 12:41:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=9D=D0=A3=20=D0=B8=D0=B7=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SatelliteController.kt | 8 ++ .../entity/SatelliteInitialConditionEntity.kt | 94 +++++++++++++++++++ .../SatelliteInitialConditionRepository.kt | 26 +++++ .../SatelliteInitialConditionService.kt | 30 ++++++ .../service/SatelliteService.kt | 23 +++++ ...4__create_satellite_initial_conditions.sql | 21 +++++ .../repository/PDCMRepositoryTest.kt | 58 ++++++++++++ .../controller/CatalogRestController.kt | 10 ++ .../service/BallisticsService.kt | 13 +++ .../static/satellite_pdcm_scripts.js | 79 +++++++++++++--- .../controller/CatalogControllerTest.kt | 35 +++++++ .../service/BallisticsServiceTest.kt | 33 ++++++- 12 files changed, 418 insertions(+), 12 deletions(-) create mode 100644 services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/entity/SatelliteInitialConditionEntity.kt create mode 100644 services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/repository/SatelliteInitialConditionRepository.kt create mode 100644 services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteInitialConditionService.kt create mode 100644 services/pcp-ballistics-service/src/main/resources/db/migration/V4__create_satellite_initial_conditions.sql diff --git a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/controller/SatelliteController.kt b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/controller/SatelliteController.kt index e1c10bc..be2be88 100644 --- a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/controller/SatelliteController.kt +++ b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/controller/SatelliteController.kt @@ -42,6 +42,14 @@ class SatelliteController { return ResponseEntity.accepted().build() } + @GetMapping("/{satellite_id}/initial-conditions/current") + fun currentInitialConditions( + @PathVariable("satellite_id") satelliteId: Long + ): ResponseEntity = + satelliteService.currentInitialConditions(satelliteId) + ?.let { ResponseEntity.ok(it) } + ?: ResponseEntity.noContent().build() + @Operation( diff --git a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/entity/SatelliteInitialConditionEntity.kt b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/entity/SatelliteInitialConditionEntity.kt new file mode 100644 index 0000000..044f7de --- /dev/null +++ b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/entity/SatelliteInitialConditionEntity.kt @@ -0,0 +1,94 @@ +package space.nstart.pcp.pcp_request_service.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table +import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO +import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel +import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO +import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO +import java.time.LocalDateTime + +@Entity +@Table(name = "satellite_initial_conditions") +class SatelliteInitialConditionEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "satellite_initial_condition_id") + val id: Long? = null, + @Column(name = "satellite_id", nullable = false) + val satelliteId: Long = 0, + @Enumerated(EnumType.STRING) + @Column(name = "movement_model", nullable = false) + val movementModel: MovementModel = MovementModel.FOTO, + @Column(name = "orb_time", nullable = false) + val orbTime: LocalDateTime = LocalDateTime.now(), + @Column(name = "orb_revolution", nullable = false) + val orbRevolution: Long = 0, + @Column(nullable = false) + val x: Double = 0.0, + @Column(nullable = false) + val y: Double = 0.0, + @Column(nullable = false) + val z: Double = 0.0, + @Column(nullable = false) + val vx: Double = 0.0, + @Column(nullable = false) + val vy: Double = 0.0, + @Column(nullable = false) + val vz: Double = 0.0, + @Column(name = "s_ball", nullable = false) + val sBall: Double = 0.0, + @Column(nullable = false) + val f81: Double = 147.8, + @Column(name = "received_at", nullable = false) + val receivedAt: LocalDateTime = LocalDateTime.now(), + @Column(name = "calculated_at") + var calculatedAt: LocalDateTime? = null, + @Column(name = "last_calculated", nullable = false) + var lastCalculated: Boolean = false +) { + constructor(request: SatelliteICDTO) : this( + satelliteId = request.satelliteId, + movementModel = request.movementModel, + orbTime = request.ic.orbPoint.time, + orbRevolution = request.ic.orbPoint.revolution, + x = request.ic.orbPoint.x, + y = request.ic.orbPoint.y, + z = request.ic.orbPoint.z, + vx = request.ic.orbPoint.vx, + vy = request.ic.orbPoint.vy, + vz = request.ic.orbPoint.vz, + sBall = request.ic.sBall, + f81 = request.ic.f81 + ) + + fun markLastCalculated(calculatedAt: LocalDateTime = LocalDateTime.now()) { + this.calculatedAt = calculatedAt + this.lastCalculated = true + } + + fun toDto() = SatelliteICDTO( + satelliteId = satelliteId, + movementModel = movementModel, + ic = InitialConditionsDTO( + orbPoint = OrbPointDTO( + time = orbTime, + revolution = orbRevolution, + vx = vx, + vy = vy, + vz = vz, + x = x, + y = y, + z = z + ), + sBall = sBall, + f81 = f81 + ) + ) +} diff --git a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/repository/SatelliteInitialConditionRepository.kt b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/repository/SatelliteInitialConditionRepository.kt new file mode 100644 index 0000000..68b5e00 --- /dev/null +++ b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/repository/SatelliteInitialConditionRepository.kt @@ -0,0 +1,26 @@ +package space.nstart.pcp.pcp_request_service.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import org.springframework.transaction.annotation.Transactional +import space.nstart.pcp.pcp_request_service.entity.SatelliteInitialConditionEntity + +interface SatelliteInitialConditionRepository : JpaRepository { + fun findFirstBySatelliteIdAndLastCalculatedTrueOrderByCalculatedAtDescIdDesc( + satelliteId: Long + ): SatelliteInitialConditionEntity? + + @Modifying + @Transactional + @Query( + """ + update SatelliteInitialConditionEntity entity + set entity.lastCalculated = false + where entity.satelliteId = :satelliteId + and entity.lastCalculated = true + """ + ) + fun clearLastCalculated(@Param("satelliteId") satelliteId: Long): Int +} diff --git a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteInitialConditionService.kt b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteInitialConditionService.kt new file mode 100644 index 0000000..8d480e1 --- /dev/null +++ b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteInitialConditionService.kt @@ -0,0 +1,30 @@ +package space.nstart.pcp.pcp_request_service.service + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import space.nstart.pcp.pcp_request_service.entity.SatelliteInitialConditionEntity +import space.nstart.pcp.pcp_request_service.repository.SatelliteInitialConditionRepository +import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO + +@Service +class SatelliteInitialConditionService( + private val satelliteInitialConditionRepository: SatelliteInitialConditionRepository +) { + + @Transactional + fun saveReceived(request: SatelliteICDTO): SatelliteInitialConditionEntity = + satelliteInitialConditionRepository.save(SatelliteInitialConditionEntity(request)) + + @Transactional + fun markLastCalculated(initialCondition: SatelliteInitialConditionEntity) { + satelliteInitialConditionRepository.clearLastCalculated(initialCondition.satelliteId) + initialCondition.markLastCalculated() + satelliteInitialConditionRepository.save(initialCondition) + } + + @Transactional(readOnly = true) + fun current(satelliteId: Long): SatelliteICDTO? = + satelliteInitialConditionRepository + .findFirstBySatelliteIdAndLastCalculatedTrueOrderByCalculatedAtDescIdDesc(satelliteId) + ?.toDto() +} diff --git a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteService.kt b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteService.kt index 7a17ad1..228314a 100644 --- a/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteService.kt +++ b/services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/service/SatelliteService.kt @@ -89,6 +89,10 @@ class SatelliteService { @Autowired private lateinit var pdcmRepository: PDCMRepository + + @Autowired + private lateinit var satelliteInitialConditionService: SatelliteInitialConditionService + @Autowired private lateinit var ascNodeRepository: AscNodeRepository @Autowired @@ -185,6 +189,7 @@ class SatelliteService { ) suspend fun resieveRV(rv: SatelliteICDTO) { + val savedInitialCondition = satelliteInitialConditionService.saveReceived(rv) val bal = Ballistics() bal.modDVType = rv.movementModel.toModDVType() @@ -230,10 +235,15 @@ class SatelliteService { prepareEarthCovering(bal, rv.satelliteId) + satelliteInitialConditionService.markLastCalculated(savedInitialCondition) + bal.clear() logger.info("Расчет для КА ${rv.satelliteId} завершился успешно") } + fun currentInitialConditions(satelliteId: Long): SatelliteICDTO? = + satelliteInitialConditionService.current(satelliteId) + private fun MovementModel.toModDVType(): ModDVType = when (this) { MovementModel.FOTO -> ModDVType.FOTO @@ -893,3 +903,16 @@ data class SatelliteBallisticsDeletionSummary( val earthCoverageDeleted: Int, val earthCellViewsDeleted: Int ) + + + +//DT: 11.12.2028 01:24:24.257000000 MDT +//x= 2610612.004898679 +//y= -3507514.947081291 +//z= -5291534.374006776 +//Vx= -4935.515395115775 +//Vy= 3497.047795669036 +//Vz= -4742.501356855924 +//CS: GSK +//Sb= 0.06 +//kappa= 0 diff --git a/services/pcp-ballistics-service/src/main/resources/db/migration/V4__create_satellite_initial_conditions.sql b/services/pcp-ballistics-service/src/main/resources/db/migration/V4__create_satellite_initial_conditions.sql new file mode 100644 index 0000000..4918c9f --- /dev/null +++ b/services/pcp-ballistics-service/src/main/resources/db/migration/V4__create_satellite_initial_conditions.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS satellite_initial_conditions ( + satellite_initial_condition_id BIGSERIAL PRIMARY KEY, + satellite_id BIGINT NOT NULL, + movement_model VARCHAR(32) NOT NULL DEFAULT 'FOTO', + orb_time TIMESTAMP NOT NULL, + orb_revolution BIGINT NOT NULL, + x DOUBLE PRECISION NOT NULL, + y DOUBLE PRECISION NOT NULL, + z DOUBLE PRECISION NOT NULL, + vx DOUBLE PRECISION NOT NULL, + vy DOUBLE PRECISION NOT NULL, + vz DOUBLE PRECISION NOT NULL, + s_ball DOUBLE PRECISION NOT NULL, + f81 DOUBLE PRECISION NOT NULL, + received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + calculated_at TIMESTAMP, + last_calculated BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX idx_satellite_initial_conditions_satellite_id ON satellite_initial_conditions(satellite_id); +CREATE INDEX idx_satellite_initial_conditions_last_calculated ON satellite_initial_conditions(satellite_id, last_calculated); diff --git a/services/pcp-ballistics-service/src/test/kotlin/space/nstart/pcp/pcp_request_service/repository/PDCMRepositoryTest.kt b/services/pcp-ballistics-service/src/test/kotlin/space/nstart/pcp/pcp_request_service/repository/PDCMRepositoryTest.kt index f77a7c6..6e6d2fc 100644 --- a/services/pcp-ballistics-service/src/test/kotlin/space/nstart/pcp/pcp_request_service/repository/PDCMRepositoryTest.kt +++ b/services/pcp-ballistics-service/src/test/kotlin/space/nstart/pcp/pcp_request_service/repository/PDCMRepositoryTest.kt @@ -14,6 +14,11 @@ import org.springframework.kafka.core.DefaultKafkaConsumerFactory import org.springframework.kafka.listener.adapter.RecordFilterStrategy import org.springframework.transaction.annotation.Transactional import space.nstart.pcp.pcp_request_service.entity.PDCMEntity +import space.nstart.pcp.pcp_request_service.entity.SatelliteInitialConditionEntity +import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO +import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel +import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO +import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO import java.time.LocalDateTime import kotlin.test.assertEquals @@ -41,6 +46,9 @@ class PDCMRepositoryTest { @Autowired private lateinit var repository: PDCMRepository + @Autowired + private lateinit var satelliteInitialConditionRepository: SatelliteInitialConditionRepository + @Test fun `find satellite orbit availability returns all satellites with points when time is null`() { persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0)) @@ -74,6 +82,34 @@ class PDCMRepositoryTest { assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual.single().timeStop) } + @Test + fun `satellite initial conditions repository returns last calculated condition`() { + val oldCondition = satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 1L)) + oldCondition.markLastCalculated(LocalDateTime.of(2026, 4, 14, 10, 0)) + satelliteInitialConditionRepository.save(oldCondition) + + val newCondition = satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 2L)) + satelliteInitialConditionRepository.clearLastCalculated(11L) + newCondition.markLastCalculated(LocalDateTime.of(2026, 4, 14, 12, 0)) + satelliteInitialConditionRepository.save(newCondition) + satelliteInitialConditionRepository.save(sampleInitialCondition(11L, 3L)) + + entityManager.flush() + entityManager.clear() + + val actual = satelliteInitialConditionRepository + .findFirstBySatelliteIdAndLastCalculatedTrueOrderByCalculatedAtDescIdDesc(11L) + + assertEquals(2L, actual?.toDto()?.ic?.orbPoint?.revolution) + assertEquals(MovementModel.KONDOR, actual?.movementModel) + assertEquals( + 1, + satelliteInitialConditionRepository.findAll().count { entity -> + entity.satelliteId == 11L && entity.lastCalculated + } + ) + } + private fun persistPoint(satelliteId: Long, time: LocalDateTime) { entityManager.persist( PDCMEntity( @@ -91,6 +127,28 @@ class PDCMRepositoryTest { entityManager.flush() } + private fun sampleInitialCondition(satelliteId: Long, revolution: Long) = + SatelliteInitialConditionEntity( + SatelliteICDTO( + satelliteId = satelliteId, + movementModel = MovementModel.KONDOR, + ic = InitialConditionsDTO( + orbPoint = OrbPointDTO( + time = LocalDateTime.of(2026, 4, 14, 10, 0).plusHours(revolution), + revolution = revolution, + vx = 1.0, + vy = 2.0, + vz = 3.0, + x = 4.0, + y = 5.0, + z = 6.0 + ), + sBall = 0.2, + f81 = 141.0 + ) + ) + ) + @TestConfiguration class KafkaFilterStubConfiguration { @Bean("kafkaListenerContainerFactory") diff --git a/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CatalogRestController.kt b/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CatalogRestController.kt index 643b469..fba882f 100644 --- a/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CatalogRestController.kt +++ b/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CatalogRestController.kt @@ -91,6 +91,16 @@ class CatalogRestController( return ResponseEntity.accepted().build() } + @GetMapping("/satellites/pdcm/{satelliteId}/initial-conditions") + fun currentPdcmInitialConditions(@PathVariable satelliteId: Long): ResponseEntity { + val response = ballisticsService.currentInitialConditions(satelliteId) + return if (response == null) { + ResponseEntity.noContent().build() + } else { + ResponseEntity.ok(response) + } + } + private fun parseSendInitialConditionsTime(time: String?): LocalDateTime { val normalizedTime = time?.trim()?.takeIf { it.isNotEmpty() } ?: return LocalDateTime.now() diff --git a/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/BallisticsService.kt b/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/BallisticsService.kt index 77e6ede..1812c3e 100644 --- a/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/BallisticsService.kt +++ b/services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/BallisticsService.kt @@ -82,6 +82,19 @@ class BallisticsService (webClientBuilderProvider: ObjectProvider + when { + response.statusCode().is2xxSuccessful -> response.bodyToMono(SatelliteICDTO::class.java) + response.statusCode().value() == 404 || response.statusCode().value() == 204 -> reactor.core.publisher.Mono.empty() + else -> response.createException().flatMap { reactor.core.publisher.Mono.error(it) } + } + } + .block() + fun parseTle(tle: TLEDTO): TleParsedDTO = webClientBuilder.build() .post() diff --git a/services/pcp-ui-service/src/main/resources/static/satellite_pdcm_scripts.js b/services/pcp-ui-service/src/main/resources/static/satellite_pdcm_scripts.js index 3f03313..6bcbf02 100644 --- a/services/pcp-ui-service/src/main/resources/static/satellite_pdcm_scripts.js +++ b/services/pcp-ui-service/src/main/resources/static/satellite_pdcm_scripts.js @@ -68,6 +68,21 @@ return `${pad(now.getDate())}.${pad(now.getMonth() + 1)}.${now.getFullYear()} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${pad(now.getMilliseconds(), 3)}`; } + function formatDateTimeInput(value) { + if (!value) { + return ''; + } + const match = String(value).match( + /^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?/ + ); + if (!match) { + return value; + } + + const [, year, month, day, hour, minute, second = '00', millis = '000'] = match; + return `${day}.${month}.${year} ${hour}:${minute}:${second}.${millis.slice(0, 3).padEnd(3, '0')}`; + } + function parseSendInitialConditionsTime() { const input = document.getElementById('satellite-pdcm-send-ic-time'); const value = input?.value.trim() || ''; @@ -194,9 +209,10 @@ function renderSetInitialConditionsState(details, loading = false) { const openButton = document.getElementById('satellite-pdcm-open-set-ic'); const submitButton = document.getElementById('satellite-pdcm-set-ic-submit'); + const satelliteSelected = !!state.selectedSatelliteId; if (openButton) { - openButton.disabled = !details || loading; - openButton.title = details ? '' : 'Выберите спутник'; + openButton.disabled = !satelliteSelected || loading; + openButton.title = satelliteSelected ? '' : 'Выберите спутник'; } if (submitButton) { submitButton.disabled = loading; @@ -429,14 +445,7 @@ } } - function openSetInitialConditionsModal() { - if (!state.selectedSatelliteId || !state.details) { - showAlert('Выберите спутник для задания начальных условий'); - return; - } - - document.getElementById('satellite-pdcm-set-ic-target').textContent = - `${state.details.name} · ID ${state.details.satelliteId}`; + function resetInitialConditionsForm() { document.getElementById('satellite-pdcm-ic-time').value = currentDateTimeInputValue(); document.getElementById('satellite-pdcm-ic-revolution').value = ''; document.getElementById('satellite-pdcm-ic-movement-model').value = 'FOTO'; @@ -445,10 +454,54 @@ ['x', 'y', 'z', 'vx', 'vy', 'vz'].forEach(field => { document.getElementById(`satellite-pdcm-ic-${field}`).value = ''; }); + } + + function fillInitialConditionsForm(initialConditions) { + document.getElementById('satellite-pdcm-ic-time').value = + formatDateTimeInput(initialConditions?.ic?.orbPoint?.time) || currentDateTimeInputValue(); + document.getElementById('satellite-pdcm-ic-revolution').value = + initialConditions?.ic?.orbPoint?.revolution ?? ''; + document.getElementById('satellite-pdcm-ic-movement-model').value = + initialConditions?.movementModel || 'FOTO'; + document.getElementById('satellite-pdcm-ic-s-ball').value = + initialConditions?.ic?.sBall ?? '0'; + document.getElementById('satellite-pdcm-ic-f81').value = + initialConditions?.ic?.f81 ?? '147.8'; + document.getElementById('satellite-pdcm-ic-x').value = initialConditions?.ic?.orbPoint?.x ?? ''; + document.getElementById('satellite-pdcm-ic-y').value = initialConditions?.ic?.orbPoint?.y ?? ''; + document.getElementById('satellite-pdcm-ic-z').value = initialConditions?.ic?.orbPoint?.z ?? ''; + document.getElementById('satellite-pdcm-ic-vx').value = initialConditions?.ic?.orbPoint?.vx ?? ''; + document.getElementById('satellite-pdcm-ic-vy').value = initialConditions?.ic?.orbPoint?.vy ?? ''; + document.getElementById('satellite-pdcm-ic-vz').value = initialConditions?.ic?.orbPoint?.vz ?? ''; + } + + async function openSetInitialConditionsModal() { + if (!state.selectedSatelliteId) { + showAlert('Выберите спутник для задания начальных условий'); + return; + } + + const selectedSatellite = state.details?.satelliteId === state.selectedSatelliteId + ? state.details + : state.satellites.find(satellite => satellite.satelliteId === state.selectedSatelliteId); + document.getElementById('satellite-pdcm-set-ic-target').textContent = + selectedSatellite + ? `${selectedSatellite.name} · ID ${selectedSatellite.satelliteId}` + : `ID ${state.selectedSatelliteId}`; + resetInitialConditionsForm(); if (window.bootstrap) { window.bootstrap.Modal.getOrCreateInstance(document.getElementById('satellite-pdcm-set-ic-modal')).show(); } + + try { + const currentInitialConditions = await requestJson(`${apiBase}/${state.selectedSatelliteId}/initial-conditions`); + if (currentInitialConditions) { + fillInitialConditionsForm(currentInitialConditions); + } + } catch (error) { + showAlert(error.message || 'Не удалось загрузить актуальные начальные условия'); + } } function buildInitialConditionsPayload() { @@ -475,7 +528,7 @@ async function submitInitialConditions(event) { event.preventDefault(); - if (!state.selectedSatelliteId || !state.details) { + if (!state.selectedSatelliteId) { showAlert('Выберите спутник для задания начальных условий'); return; } @@ -509,6 +562,7 @@ async function selectSatellite(satelliteId) { state.selectedSatelliteId = satelliteId; + state.details = null; renderList(); renderSendInitialConditionsState(null); renderSetInitialConditionsState(null); @@ -523,6 +577,7 @@ state.details = null; renderSummary(null); renderTable(null); + renderSetInitialConditionsState(null); showAlert(error.message || 'Не удалось загрузить ПДЦМ спутника'); } } @@ -538,12 +593,14 @@ if (nextSatelliteId) { await selectSatellite(nextSatelliteId); } else { + state.selectedSatelliteId = null; state.details = null; renderSummary(null); renderTable(null); } } catch (error) { state.satellites = []; + state.selectedSatelliteId = null; state.details = null; renderList(); renderSummary(null); diff --git a/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/controller/CatalogControllerTest.kt b/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/controller/CatalogControllerTest.kt index c303bfb..e65841d 100644 --- a/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/controller/CatalogControllerTest.kt +++ b/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/controller/CatalogControllerTest.kt @@ -389,6 +389,41 @@ class CatalogControllerTest { assertEquals(42L, requestCaptor.value.ic.orbPoint.revolution) } + @Test + fun `satellite pdcm current initial conditions endpoint proxies request to ballistics`() { + val response = SatelliteICDTO( + satelliteId = 501L, + movementModel = MovementModel.KONDOR, + ic = InitialConditionsDTO( + orbPoint = OrbPointDTO( + time = LocalDateTime.of(2026, 4, 24, 10, 15, 30, 123_000_000), + revolution = 42L, + vx = 1.0, + vy = 2.0, + vz = 3.0, + x = 4.0, + y = 5.0, + z = 6.0 + ), + sBall = 0.07, + f81 = 145.2 + ) + ) + doReturn(response).`when`(ballisticsService).currentInitialConditions(501L) + + val actual = WebClient.create("http://localhost:$port") + .get() + .uri("/api/catalog/satellites/pdcm/501/initial-conditions") + .retrieve() + .bodyToMono(SatelliteICDTO::class.java) + .block()!! + + assertEquals(501L, actual.satelliteId) + assertEquals(MovementModel.KONDOR, actual.movementModel) + assertEquals(42L, actual.ic.orbPoint.revolution) + verify(ballisticsService).currentInitialConditions(501L) + } + @Test fun `satellite create endpoint proxies request`() { val request = SatelliteCreateDTO( diff --git a/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/service/BallisticsServiceTest.kt b/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/service/BallisticsServiceTest.kt index 6515bcc..6639838 100644 --- a/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/service/BallisticsServiceTest.kt +++ b/services/pcp-ui-service/src/test/kotlin/space/nstart/pcp/slots_service/service/BallisticsServiceTest.kt @@ -59,6 +59,7 @@ class BallisticsServiceTest { ) ) ) + val currentInitialConditions = service.currentInitialConditions(101L) assertEquals( listOf( @@ -67,7 +68,8 @@ class BallisticsServiceTest { "/api/satellites/101/orbit", "/api/satellites/orbit/availability", "/api/satellites/101/extract-time", - "/api/satellites/receive-rv" + "/api/satellites/receive-rv", + "/api/satellites/101/initial-conditions/current" ), requests.map { it.path } ) @@ -78,6 +80,7 @@ class BallisticsServiceTest { "time_start=2026-04-21T10:00&time_stop=2026-04-21T12:00", null, null, + null, null ), requests.map { it.query } @@ -87,6 +90,8 @@ class BallisticsServiceTest { assertNotNull(exactPoint) assertEquals(timeStart, exactPoint.time) assertEquals(true, requestBodies.single().contains(""""movementModel":"KONDOR"""")) + assertNotNull(currentInitialConditions) + assertEquals(101L, currentInitialConditions.satelliteId) } private fun createService(serverUrl: String): BallisticsService { @@ -133,6 +138,32 @@ class BallisticsServiceTest { requestBodies.add(exchange.requestBody.bufferedReader().use { it.readText() }) respond(exchange, "") } + startedServer.createContext("/api/satellites/101/initial-conditions/current") { exchange -> + requests.add(exchange.requestURI) + respond( + exchange, + """ + { + "satelliteId": 101, + "movementModel": "KONDOR", + "ic": { + "orbPoint": { + "time": "2026-04-21T10:00:00", + "revolution": 7, + "vx": 1.0, + "vy": 2.0, + "vz": 3.0, + "x": 4.0, + "y": 5.0, + "z": 6.0 + }, + "sBall": 0.0, + "f81": 147.8 + } + } + """.trimIndent() + ) + } startedServer.start() server = startedServer return "http://localhost:${startedServer.address.port}"