feat(472-6 6-B 노선 바뀐 [저장] 한 본문): 노선 저장 · 관 · 구조물 다시 얹기 · 횡단 고침 넘김을 서버 한 창구로 — 전부 아니면 없음
- POST /design/save 에 route(노선 초안) · carry(옛 노선 세션 칸) 칸 · 서버 = 노선 저장 → Node facility.rebase(rebasePipeDraft · rebaseStructureDraft) → 6-A 차례(새 노선) → Node stations.carry(routeCarryPatches) → 횡단 저장 - 되돌림 범위 = 노선 행 · 자식 표 · 단계 · 배수 폴더 · 노선 파일 · 지표면 확정(설계 지형 바꿈) - 손 상단측은 안 넘김(요구 274) · 노선 바뀐 [확정] = [저장](새 노선 본 뒤 다시) - 브라우저 = 노선 초안 + 사용자 칸만 한 본문 · 옛 차례 길(saveRouteDraft 뒤 flush · carryAfterRouteSave · keepDraftsOnNewRoute) · 부름 0 된 flush 창구 여섯 · saveCachedCrossPatches 걷음 - 등록부 R9 stations.carry · F10 facility.rebase · 옛 차례 단언 시험 한 본문 꼴로 · test_472_6_route_save(옛 길 = 새 길 · 실패 되돌림) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,10 @@
|
||||
(오류 응답 · 측점 맞춤 실패 · 재계산 실패 · 하위 차례 실패) DB 행 · storage 를 시작 전으로 되돌리고
|
||||
그 차례 이름 · 까닭을 돌려줌(`common_util_save_guard` · 브레인 A). 되돌림마저 실패하면 「복구 확인
|
||||
필요」. 같은 프로젝트 저장이 겹치면 뒤 요청이 기다림(`waited`).
|
||||
노선 바뀐 저장(노선 초안)은 아직 옛 길(`/route/replan` 뒤 차례 · 6-B).
|
||||
노선 바뀐 저장(`route` · `carry` 칸 · 6-B) = 맨 앞 노선 저장 → 관 · 구조물 초안 다시 얹기 →
|
||||
위 차례(새 노선) · 횡단 = 옛 노선 세션 칸을 새 측점에 넘긴 patch · 상단측 안 보냄(요구 274) ·
|
||||
늘 [저장](새 노선을 본 뒤 [확정]) · 되돌림 범위에 배수 폴더 · 지표면 확정(설계 지형 바꿈)까지
|
||||
(`B06_Section_Router_DesignSave_Route`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -25,12 +28,14 @@ from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
||||
from B05_Profile.B05_Profile_Route_Save import RouteSaveRequest
|
||||
from B05_Profile.B05_Profile_Schema import (
|
||||
PageSettingsRequest,
|
||||
ProfileAlignmentSaveRequest,
|
||||
RouteConfirmRequest,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureSaveRequest
|
||||
from B06_Section.B06_Section_Router_DesignSave_Route import RouteCarryRequest, StepFailed
|
||||
from B06_Section.B06_Section_Router_Stations import PlusStationsRequest
|
||||
from B06_Section.B06_Section_Schema import SectionConfirmRequest
|
||||
from common_util import common_util_save_guard as guard
|
||||
@@ -60,12 +65,10 @@ class DesignSaveRequest(BaseModel):
|
||||
culvert_options: dict[str, dict[str, Any]] | None = None
|
||||
uphill: RouteConfirmRequest | None = None
|
||||
cross: SectionConfirmRequest | None = None
|
||||
|
||||
|
||||
class StepFailed(Exception):
|
||||
def __init__(self, step: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.step = step
|
||||
#: 노선 초안(`/route/replan` 본문 꼴) — 오면 노선 바뀐 저장(6-B)
|
||||
route: RouteSaveRequest | None = None
|
||||
#: 노선 바뀐 저장의 옛 노선 세션 칸(측점 열쇠 넘김 입력)
|
||||
carry: RouteCarryRequest | None = None
|
||||
|
||||
|
||||
def _body(response: Any) -> dict[str, Any]:
|
||||
@@ -105,6 +108,9 @@ async def _run_steps(
|
||||
from B06_Section.B06_Section_Router_Stations import put_plus_stations
|
||||
|
||||
out: dict[str, Any] = {}
|
||||
route_id = request.route_id
|
||||
moved = request.route is not None
|
||||
before: dict[str, Any] = {}
|
||||
|
||||
async def step(name: str, call: Any) -> dict[str, Any]:
|
||||
try:
|
||||
@@ -119,6 +125,24 @@ async def _run_steps(
|
||||
raise StepFailed(name, "시험 갈래 — 이 차례 뒤 일부러 실패")
|
||||
return out[name]
|
||||
|
||||
if moved:
|
||||
from B05_Profile.B05_Profile_Router_Replan import replan_route
|
||||
from B06_Section import B06_Section_Router_DesignSave_Route as moving
|
||||
|
||||
before = await moving.saved_facilities(project_id)
|
||||
routed = await step("route", replan_route(project_id, request.route))
|
||||
route_id = int(routed["route_id"])
|
||||
# 관 · 구조물 초안 = 서버가 따라 옮긴 정본 위에(Node · 옛 브라우저 `rebasePipeDraft` 짝)
|
||||
try:
|
||||
await moving.rebase_drafts(project_id, request, before)
|
||||
except StepFailed:
|
||||
raise
|
||||
except Exception as error:
|
||||
logger.exception("한 본문 저장 다시 얹기 실패: project_id=%s", project_id)
|
||||
raise StepFailed("route", str(error) or type(error).__name__) from None
|
||||
# 계획고 초안 = 새 노선에(기하 이월 · 서버가 없는 자리 버림)
|
||||
if request.alignment is not None:
|
||||
request.alignment = request.alignment.model_copy(update={"route_id": route_id})
|
||||
if request.settings is not None:
|
||||
await step("settings", save_page_settings(project_id, request.settings))
|
||||
if request.pipes is not None or request.confirm:
|
||||
@@ -140,17 +164,34 @@ async def _run_steps(
|
||||
await step(
|
||||
"culvertOptions", put_pipe_points(project_id, {"points": points}, session, False)
|
||||
)
|
||||
if request.uphill is not None:
|
||||
if request.uphill is not None and not moved: # 노선 바뀜 = 손 상단측 안 넘김(요구 274)
|
||||
parts = await step("uphill", confirm_latest_route(project_id, request.uphill, False))
|
||||
if parts.get("failed_parts"):
|
||||
raise StepFailed("uphill", " · ".join(parts["failed_parts"]) + " 실패")
|
||||
if request.cross is not None:
|
||||
# 노선 바뀜 = 옛 노선 세션 칸 → 새 측점 patch(Node) · 고침이 없어도 한 번(429 ④)
|
||||
if moved:
|
||||
try:
|
||||
carried = await moving.carry_cross(project_id, route_id, request.carry, before)
|
||||
except StepFailed:
|
||||
raise
|
||||
except Exception as error:
|
||||
logger.exception("한 본문 저장 횡단 넘김 실패: project_id=%s", project_id)
|
||||
raise StepFailed("cross", str(error) or type(error).__name__) from None
|
||||
out["carry"] = {"outside": int(carried.get("outside") or 0)}
|
||||
body = request.cross.model_dump() if request.cross is not None else {}
|
||||
body["cross_patches"] = carried.get("patches") or None
|
||||
# 늘 [저장] — [확정]은 새 노선을 본 뒤 다시(옛 길 그대로)
|
||||
crossing = save_sections(project_id, route_id, SectionConfirmRequest.model_validate(body))
|
||||
elif request.cross is not None:
|
||||
save = confirm_sections if request.confirm else save_sections
|
||||
cross = await step("cross", save(project_id, request.route_id, request.cross))
|
||||
if cross.get("recomputed") is False:
|
||||
raise StepFailed("cross", "서버 재계산 실패")
|
||||
if cross.get("stations_synced") is False:
|
||||
raise StepFailed("cross", "구조물 측점 맞춤 실패")
|
||||
crossing = save(project_id, request.route_id, request.cross)
|
||||
else:
|
||||
return out
|
||||
cross = await step("cross", crossing)
|
||||
if cross.get("recomputed") is False:
|
||||
raise StepFailed("cross", "서버 재계산 실패")
|
||||
if cross.get("stations_synced") is False:
|
||||
raise StepFailed("cross", "구조물 측점 맞춤 실패")
|
||||
return out
|
||||
|
||||
|
||||
@@ -167,9 +208,11 @@ async def save_design(
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
stored_path = await get_project_storage_relative_path(connection, project_id)
|
||||
rows = await guard.snapshot_rows(connection, str(project_id))
|
||||
moved = request.route is not None # 노선 저장 = 설계 지형 바꿈(지표면 확정)도 떠 둠
|
||||
rows = await guard.snapshot_rows(connection, str(project_id), surface=moved)
|
||||
root = Path(resolve_stored_project_path(stored_path))
|
||||
pipes = request.pipes is not None or bool(request.culvert_options) or request.confirm
|
||||
pipes = pipes or moved # 노선 저장 = 관 따라감 · 새 교차 관(배수 폴더)
|
||||
files = guard.snapshot_storage(
|
||||
root, guard.STORAGE_SCOPE + (guard.PIPE_SCOPE if pipes else ())
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user