pcp-tgu: редактор — расчёт плана съёмки+сброса одной операцией, очистка плана, удаление включений

Вкладка «Редактор»:
- Кнопка «Рассчитать план» — запускает surveys/calculate → drops/calculate
  одной операцией на черновике (ensureDraft), затем перечитывает включения.
  Источник комплексного плана (snapshot этапа 4) выбирает бэкенд по интервалу.
  Вызовы идут напрямую в pcp-mission-planing-service (/api/pcp-mission).
  Ручные маршруты съёмки (source=MANUAL) сохраняются при пересчёте.
- Кнопка «Очистить план» — новый эндпоинт DELETE /api/missions/{id}/modes
  (clearPlan) сносит ВСЕ включения, включая ручные, с бронями и связями.
- Удаление маршрута съёмки / сброса в правой панели с подтверждением:
  WorkInspector.canDelete показывает кнопку удаления и для рассчитанных
  (read-only) включений, когда есть черновик; удаление через deleteMode.
This commit is contained in:
Дмитрий Соловьев
2026-06-04 21:03:44 +03:00
parent 9f1756a9f9
commit bd2bc9d583
6 changed files with 226 additions and 6 deletions
@@ -252,6 +252,27 @@ class MissionController(
return ResponseEntity.noContent().build()
}
@Operation(
summary = "Полностью очистить план",
description = "Удаляет ВСЕ включения плана миссии: съёмки (включая построенные вручную, source=MANUAL) " +
"и сбросы, вместе с бронями слотов и связями съёмка↔сброс. В отличие от пересчёта плана, " +
"ручные маршруты тоже удаляются. Используется кнопкой «Очистить план» в редакторе."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "204", description = "План очищен"),
ApiResponse(responseCode = "404", description = "Миссия не найдена")
]
)
@DeleteMapping("/{mission_id}/modes")
fun clearPlan(
@Parameter(description = "Идентификатор миссии")
@PathVariable("mission_id") id: UUID
): ResponseEntity<Void> {
missionPlaningService.clearPlan(id)
return ResponseEntity.noContent().build()
}
@Operation(
summary = "Проверить миссию-черновик на конфликты",
description = "Считает конфликты/наложения включений плана-черновика и возвращает дифф по " +
@@ -936,6 +936,21 @@ class MissionPlaningService(
logger.info("Удалён сброс (drop_mode id={}), снято связей со съёмками={}", dropId, links.size)
}
/**
* Полная очистка плана: удаляет ВСЕ включения миссии — съёмки (включая ручные, source=MANUAL)
* и сбросы — вместе с бронями слотов и связями съёмка↔сброс. В отличие от пересчёта плана
* ([loadSlots]/[calculateDrops]), ручные маршруты тоже удаляются. Сбросы удаляются первыми,
* чтобы снять связи до удаления съёмок и не плодить проверку осиротевших сбросов.
*/
@Transactional
fun clearPlan(missionId: UUID) {
missionRepository.findById(missionId).orElseThrow { notFound(missionId) }
val modes = modeRepository.findAllByPlan_Mission_MissionIdOrderByTimeStartAsc(missionId)
modes.filterIsInstance<DropModeEntity>().forEach { deleteDropMode(it) }
modes.filterIsInstance<SurveyModeEntity>().forEach { deleteSurveyMode(it) }
logger.info("План миссии {} полностью очищен: удалено включений={}", missionId, modes.size)
}
/**
* Валидация миссии-черновика на конфликты/наложения включений.
*