Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_6' into sub_laptop_3
This commit is contained in:
@@ -23,7 +23,7 @@ import { workflowSteps } from "../b_page_scaffold";
|
||||
import {
|
||||
clearWorkflowStateCache,
|
||||
fetchWorkflowState,
|
||||
goToWorkflowStage,
|
||||
moveStage,
|
||||
WORKFLOW_STEP_ROUTES,
|
||||
type WorkflowState,
|
||||
} from "../b_workflow_nav";
|
||||
@@ -194,12 +194,13 @@ export function renderDesignPage(root: HTMLElement): MountedPage {
|
||||
currentStage: [2, 3, 4],
|
||||
onOptionsOpenChange: (isOpen) => isOpen && scrollToPick(shared.structures), // 499-1
|
||||
routes: WORKFLOW_STEP_ROUTES,
|
||||
// 종단설계 · 횡단설계 두 칸은 같은 페이지 — 주소만 바꿔 보기만 바꾼다. B07~B09 = 확정 뒤 이동(496-1).
|
||||
// 종단설계 · 횡단설계 두 칸은 같은 페이지 — 주소만 바꿔 보기만 바꾼다. B07~B09 = 확정 뒤 이동(496-1) ·
|
||||
// 그 밖 = 페이지 넘김 잠금 길(520-4 · 떠남 확인은 잠금이 풀린 뒤 라우터가).
|
||||
onStepClick: (stepIndex) => {
|
||||
const route = WORKFLOW_STEP_ROUTES[stepIndex];
|
||||
if (designViewOf(route)) navigateTo(route);
|
||||
else if (stepIndex >= 4) confirmAndGo(shared, route);
|
||||
else goToWorkflowStage(projectId, route);
|
||||
else void moveStage(projectId, route, []);
|
||||
},
|
||||
});
|
||||
layout.root.classList.add("design-page__layout");
|
||||
|
||||
@@ -33,6 +33,7 @@ import { culvertOptionsPart } from "../../B06_Section/B06_Section_Api_Culvert_Op
|
||||
import { pageSettingsPart } from "../../B05_Profile/B05_Profile_Api_PageSettings";
|
||||
import {
|
||||
DesignSaveError,
|
||||
fetchDesignSaveProgress,
|
||||
postDesignSave,
|
||||
SAVE_PART_KEYS,
|
||||
type SavePart,
|
||||
@@ -136,6 +137,17 @@ const STEP_LABEL: Record<SaveStepKey, string> = {
|
||||
cross: "횡단",
|
||||
};
|
||||
|
||||
/** 잠금판 진행(520-4) — 서버 저장 차례 「n/N 이름」 · 안 돌면 null(페이지 넘김 `moveStage` progress 칸). */
|
||||
export async function designSaveProgress(
|
||||
projectId: string,
|
||||
): Promise<{ ratio: number; label: string } | null> {
|
||||
const now = await fetchDesignSaveProgress(projectId);
|
||||
if (!now?.active || !now.step || !now.index || !now.total) return null;
|
||||
const name =
|
||||
now.step === "basins" ? "배수유역 다시 셈" : (STEP_LABEL[now.step as SaveStepKey] ?? now.step);
|
||||
return { ratio: (now.index - 1) / now.total, label: `${now.index}/${now.total} ${name}` };
|
||||
}
|
||||
|
||||
/** 서버가 버린 계획고 편집 자리(측점 · 관 · 구조물이 없는 자리 · 213-2)를 알린다. */
|
||||
export function noticeDroppedEdits(saved: { dropped_edit_chainages?: number[] }): void {
|
||||
const dropped = saved.dropped_edit_chainages ?? [];
|
||||
|
||||
@@ -108,3 +108,17 @@ export async function postDesignSave(
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
/** 서버 저장 진행(520-4) — 차례 이름 · n/N · 지난 초 · 안 돌면 active 거짓 · 물음 실패 = null. */
|
||||
export async function fetchDesignSaveProgress(projectId: string): Promise<{
|
||||
active: boolean;
|
||||
step?: string;
|
||||
index?: number;
|
||||
total?: number;
|
||||
elapsed_s?: number;
|
||||
} | null> {
|
||||
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/design/save/progress`, {
|
||||
credentials: "include",
|
||||
});
|
||||
return response.ok ? response.json() : null;
|
||||
}
|
||||
|
||||
@@ -9,18 +9,14 @@
|
||||
* 시설 줄(알약 · 비정규 측점 · 관 id 장부)은 페이지 한 벌(`design_facility_lane`) — 관 알림 · 노선 따라감을 여기서 넘김.
|
||||
* [저장] · 확정(B06 [>] · 단계 판 B07~B09 · 496-1)은 두 보기 몫을 모아 저장 한 줄기(`saveDesign`) 한 번으로
|
||||
* 보낸다. 종단 보기의 확정은 횡단 보기를 숨긴 채 세워 횡단 자료를 같은 페이지에서 얻는다(`ensureView`).
|
||||
* 확정 이동 = 페이지 넘김 공용 `moveStage` 잠금판 한 단계(서버 저장 차례 진행 · 520-4).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { RoutePath } from "@config/config_frontend";
|
||||
import { showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
||||
import { splitSidebarActions } from "@ui/ui_template_overlay";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import {
|
||||
clearWorkflowStateCache,
|
||||
fetchWorkflowState,
|
||||
goToWorkflowStage,
|
||||
WORKFLOW_STEP_ROUTES,
|
||||
} from "../b_workflow_nav";
|
||||
import { clearWorkflowStateCache, fetchWorkflowState, moveStage } from "../b_workflow_nav";
|
||||
import { readState, writeState } from "../b_page_state";
|
||||
import { currentRoute, remountRoute } from "../router";
|
||||
import { clearRouteLatestCache } from "../../B05_Profile/B05_Profile_Api_Fetch";
|
||||
@@ -32,7 +28,13 @@ import { drainageSide } from "../../B05_Profile/B05_Profile_UI_Profile_SideTool"
|
||||
import { createDesignPanel, type DesignPanel } from "./design_panel";
|
||||
import { createDesignProfile, type DesignProfile } from "./design_profile";
|
||||
import { attachProfilePopout, attachSubPanelPopouts } from "./design_profile_popout";
|
||||
import { designSaveBusy, queueDesignSave, saveDesign, type PartOverride } from "./design_save";
|
||||
import {
|
||||
designSaveBusy,
|
||||
designSaveProgress,
|
||||
queueDesignSave,
|
||||
saveDesign,
|
||||
type PartOverride,
|
||||
} from "./design_save";
|
||||
import type { SavePart, SavePartKey } from "./design_save_api";
|
||||
import { designDirty } from "./design_leave";
|
||||
import { pendingRouteSave } from "../../B05_Profile/B05_Profile_UI_RouteEdit_Apply";
|
||||
@@ -366,7 +368,7 @@ export function sharedPanel(shared: DesignPageShared): DesignPanel {
|
||||
),
|
||||
},
|
||||
// 자료가 서기 전에는 잠근다 — 보기가 `designReady` 로 연다.
|
||||
save: { run: () => saveSharedDesign(shared, false), enabled: false },
|
||||
save: { run: async () => void (await saveSharedDesign(shared, false)), enabled: false },
|
||||
},
|
||||
},
|
||||
shared.view(),
|
||||
@@ -379,23 +381,26 @@ export function sharedPanel(shared: DesignPageShared): DesignPanel {
|
||||
return panel;
|
||||
}
|
||||
|
||||
/** 노선 바뀐 채 확정 → [B07 로] 고름 — 다시 세운 페이지가 자료를 세우면 새 노선으로 확정 뒤 그 주소(요구 300 B2). */
|
||||
const confirmOnReady = new Map<string, RoutePath>();
|
||||
/** 노선 바뀐 채 확정 → [B07 로] 고름 — 다시 세운 페이지가 자료를 세우면 새 노선으로 확정 · 받는 값 = 그 결과
|
||||
* (요구 300 B2 · 잠금판이 다시 마운트를 넘어 기다렸다 이동 · 520-4). */
|
||||
const confirmOnReady = new Map<string, (ok: boolean) => void>();
|
||||
// ponytail: 다시 세운 페이지가 자료를 못 세우면 이만큼 뒤 머묾 — 마운트가 이보다 길면 늘림
|
||||
const CONFIRM_ON_READY_MS = 120_000;
|
||||
|
||||
/** 자료가 섰다 — [저장] · 확정 이동을 연다(두 보기 어느 쪽이 먼저든 한 번이면 된다). */
|
||||
export function designReady(shared: DesignPageShared): void {
|
||||
shared.ready = true;
|
||||
shared.panel?.dock.setActions({
|
||||
save: {
|
||||
run: () => saveSharedDesign(shared, false),
|
||||
run: async () => void (await saveSharedDesign(shared, false)),
|
||||
enabled: true,
|
||||
title: L("B06_Profile_Btn_Save_Tip"),
|
||||
},
|
||||
});
|
||||
const target = confirmOnReady.get(shared.projectId);
|
||||
if (!target) return;
|
||||
const settle = confirmOnReady.get(shared.projectId);
|
||||
if (!settle) return;
|
||||
confirmOnReady.delete(shared.projectId);
|
||||
void saveSharedDesign(shared, true, target);
|
||||
void saveSharedDesign(shared, true).then(settle, () => settle(false));
|
||||
}
|
||||
|
||||
/** 고친 것 없고 단계 2 · 3 이미 완료 — [>]가 저장 없이 바로 감(요구 302 D1 · 그 밖은 496 확정 그대로). */
|
||||
@@ -408,25 +413,18 @@ async function alreadyConfirmed(projectId: string): Promise<boolean> {
|
||||
return !!done(2) && !!done(3);
|
||||
}
|
||||
|
||||
/** B06 [>] · 단계 판 B07~B09(496-1) — 확정 뒤 그 주소 · 실패 · 자료 서기 전 = 머묾. */
|
||||
/** B06 [>] · 단계 판 B07~B09(496-1 · 520-4) — 잠금판 「설계 확정」(서버 저장 차례 n/N · 낡은 배수유역은 관 차례가
|
||||
* 한 번 다시 셈 · 옛 확인 창 · 화면 [업데이트] 없음) 뒤 그 주소 · 실패 · 자료 서기 전 = 머묾. */
|
||||
export function confirmAndGo(shared: DesignPageShared, route: RoutePath): void {
|
||||
if (!shared.ready) return void showToast(L("Design_Next_NotReady"), "info");
|
||||
const go = () =>
|
||||
alreadyConfirmed(shared.projectId).then((skip) =>
|
||||
skip ? goToWorkflowStage(shared.projectId, route) : saveSharedDesign(shared, true, route),
|
||||
);
|
||||
void drainageFirst(shared).then((ready) => (ready ? go() : undefined));
|
||||
}
|
||||
|
||||
/** 배수유역도가 낡은 채 B07 로 — 확인 창 → [업데이트] 끝난 뒤 감 · 취소 · 실패 = 머묾(요구 326-6). */
|
||||
async function drainageFirst(shared: DesignPageShared): Promise<boolean> {
|
||||
const side = shared.profile?.side;
|
||||
if (!side?.stale()) return true;
|
||||
if (!(await showConfirmDialog(L("Design_Drainage_Stale"), L("Design_Drainage_Stale_Go"))))
|
||||
return false;
|
||||
if (await side.update()) return true;
|
||||
showToast(L("Design_Drainage_Stale_Failed"), "error");
|
||||
return false;
|
||||
const id = shared.projectId;
|
||||
void moveStage(id, route, [
|
||||
{
|
||||
name: "설계 확정",
|
||||
run: async () => (await alreadyConfirmed(id)) || saveSharedDesign(shared, true),
|
||||
progress: () => designSaveProgress(id),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,23 +444,27 @@ async function routeChanged(shared: DesignPageShared): Promise<boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* [저장] · 확정 한 번(확정 = `target` 으로 감 · 기본 B07) — 관(배수유역도) · 계획고는 한 벌 종단도가, 구조물 · 상단측은 종단 보기가,
|
||||
* [저장] · 확정 한 번(참 = 다 저장 · 확정 이동은 부른 쪽 `moveStage`) — 관(배수유역도) · 계획고는 한 벌 종단도가, 구조물 · 상단측은 종단 보기가,
|
||||
* 저장 앞 재계산 · 암거 옵션 · 횡단 본문은 횡단 보기가 낸다. 차례는 `saveDesign` 한 곳 · 줄 세움은 `queueDesignSave`(498-3).
|
||||
*/
|
||||
export async function saveSharedDesign(
|
||||
shared: DesignPageShared,
|
||||
confirm: boolean,
|
||||
target: RoutePath = WORKFLOW_STEP_ROUTES[4],
|
||||
): Promise<void> {
|
||||
await queueDesignSave(shared.projectId, confirm, () => saveOnce(shared, confirm, target));
|
||||
): Promise<boolean> {
|
||||
// 노선 바뀐 채 확정 = 다시 세운 페이지의 둘째 확정 — 저장 줄 밖에서 기다림(줄 안이면 둘째가 막힘)
|
||||
const moved: { wait?: Promise<boolean> } = {};
|
||||
const saved = await queueDesignSave(shared.projectId, confirm, () =>
|
||||
saveOnce(shared, confirm, moved),
|
||||
);
|
||||
return saved === true && (moved.wait ? await moved.wait : true);
|
||||
}
|
||||
|
||||
async function saveOnce(
|
||||
shared: DesignPageShared,
|
||||
confirm: boolean,
|
||||
target: RoutePath,
|
||||
): Promise<void> {
|
||||
if (await routeChanged(shared)) return;
|
||||
moved: { wait?: Promise<boolean> },
|
||||
): Promise<boolean> {
|
||||
if (await routeChanged(shared)) return false;
|
||||
// [저장] · [확정] 본문 = 횡단 보기 한 벌(표준 횡단면 · 전 측점 면적 · 카드 고침) — 들어온 화면 무관 · 숨긴 채라도
|
||||
// 세움(B05 만 연 캐시 패치 길 지움 · 386-6)
|
||||
await shared.ensureView("cross");
|
||||
@@ -471,11 +473,11 @@ async function saveOnce(
|
||||
const crossPart = crossSave?.crossPart;
|
||||
if (!crossPart) {
|
||||
showToast(L(confirm ? "B06_Profile_Confirm_Failed" : "B06_Profile_Save_Failed"), "error");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const profile = shared.profile;
|
||||
const side = profile?.side;
|
||||
await saveDesign(
|
||||
return saveDesign(
|
||||
{
|
||||
projectId: shared.projectId,
|
||||
routeId: () => routeIdOf(shared),
|
||||
@@ -510,7 +512,6 @@ async function saveOnce(
|
||||
// 서버가 정본을 다시 씀 — 다음 페이지(B07)가 브라우저 셈 · 옛 세션 사본 말고 정본을 받게(470 N1)
|
||||
invalidateSectionDetail(shared.projectId);
|
||||
showToast(L("B06_Profile_Confirm_Success"), "success");
|
||||
goToWorkflowStage(shared.projectId, target);
|
||||
return;
|
||||
}
|
||||
// 서버가 정본(계획선 · 횡단 · 면적)을 다시 썼다 — 공유 캐시를 한 번 비우고 두 보기가 새 상세 한 객체를
|
||||
@@ -529,7 +530,16 @@ async function saveOnce(
|
||||
L("Design_Route_Moved_Go"),
|
||||
L("Design_Route_Moved_Stay"),
|
||||
);
|
||||
if (go) confirmOnReady.set(shared.projectId, target);
|
||||
moved.wait = go
|
||||
? new Promise<boolean>((settle) => {
|
||||
confirmOnReady.set(shared.projectId, settle);
|
||||
window.setTimeout(() => {
|
||||
if (confirmOnReady.get(shared.projectId) !== settle) return;
|
||||
confirmOnReady.delete(shared.projectId);
|
||||
settle(false);
|
||||
}, CONFIRM_ON_READY_MS);
|
||||
})
|
||||
: Promise.resolve(false);
|
||||
},
|
||||
failed: (error, isConfirm) => {
|
||||
const key = isConfirm ? "B06_Profile_Confirm_Failed" : "B06_Profile_Save_Failed";
|
||||
|
||||
@@ -35,6 +35,7 @@ from B05_Profile.B05_Profile_Schema import (
|
||||
RouteConfirmRequest,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureSaveRequest
|
||||
from B06_Section import B06_Section_Router_DesignSave_Progress as progress
|
||||
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
|
||||
@@ -83,6 +84,15 @@ def _body(response: Any) -> dict[str, Any]:
|
||||
return dict(response or {})
|
||||
|
||||
|
||||
async def _stale_or_false(stale: Any, project_id: UUID) -> bool:
|
||||
"""진행 이름 고르기만 — 판정 실패는 저장을 막지 않음(관 차례가 다시 판정)."""
|
||||
try:
|
||||
return bool(await stale(project_id))
|
||||
except Exception:
|
||||
logger.exception("한 본문 저장 배수유역 낡음 판정 실패: project_id=%s", project_id)
|
||||
return False
|
||||
|
||||
|
||||
def _patched_points(stored: dict[str, Any], patches: dict[str, dict[str, Any]]) -> list | None:
|
||||
"""저장된 관 목록에 관 옵션 조각 — 고친 것이 없으면 None(브라우저 `flushCulvertOptions` 짝)."""
|
||||
points, touched = [], False
|
||||
@@ -100,7 +110,11 @@ async def _run_steps(
|
||||
project_id: UUID, request: DesignSaveRequest, session: dict[str, Any] | None
|
||||
) -> dict[str, Any]:
|
||||
"""차례대로 안쪽 창구 · 차례 이름째 실패를 던짐 → 차례별 응답(알림 · 화면 맞춤용)."""
|
||||
from B04_PreProcess.B04_PreProcess_Router_Basins import get_pipe_points, put_pipe_points
|
||||
from B04_PreProcess.B04_PreProcess_Router_Basins import (
|
||||
_analysis_stale,
|
||||
get_pipe_points,
|
||||
put_pipe_points,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Router import save_profile_alignment
|
||||
from B05_Profile.B05_Profile_Router_Lifecycle import confirm_latest_route, save_page_settings
|
||||
from B05_Profile.B05_Profile_Structures_Router import write_structures
|
||||
@@ -111,8 +125,26 @@ async def _run_steps(
|
||||
route_id = request.route_id
|
||||
moved = request.route is not None
|
||||
before: dict[str, Any] = {}
|
||||
# 잠금판 진행(520-4) — 이 본문이 거칠 차례 목록 = N
|
||||
planned = [
|
||||
name
|
||||
for name, on in (
|
||||
("route", moved),
|
||||
("settings", request.settings is not None),
|
||||
("pipes", request.pipes is not None or request.confirm),
|
||||
("structures", request.structures is not None),
|
||||
("plusStations", request.plus_stations is not None),
|
||||
("alignment", request.alignment is not None),
|
||||
("culvertOptions", bool(request.culvert_options)),
|
||||
("uphill", request.uphill is not None and not moved),
|
||||
("cross", moved or request.cross is not None),
|
||||
)
|
||||
if on
|
||||
]
|
||||
progress.begin(project_id, planned)
|
||||
|
||||
async def step(name: str, call: Any) -> dict[str, Any]:
|
||||
async def step(name: str, call: Any, label: str | None = None) -> dict[str, Any]:
|
||||
progress.mark(project_id, label or name)
|
||||
try:
|
||||
out[name] = _body(await call)
|
||||
except StepFailed as failed:
|
||||
@@ -146,8 +178,12 @@ async def _run_steps(
|
||||
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:
|
||||
# [확정] + 낡은 분석 = 관 차례가 배수유역부터 다시 셈(한 번 · 화면 [업데이트] 없음 · 520-4)
|
||||
basins = request.confirm and await _stale_or_false(_analysis_stale, project_id)
|
||||
pipes = await step(
|
||||
"pipes", put_pipe_points(project_id, request.pipes or {}, session, request.confirm)
|
||||
"pipes",
|
||||
put_pipe_points(project_id, request.pipes or {}, session, request.confirm),
|
||||
"basins" if basins else None,
|
||||
)
|
||||
if pipes.get("stations_failed"):
|
||||
raise StepFailed("pipes", "관 자리 측점을 못 맞췄습니다")
|
||||
@@ -170,6 +206,7 @@ async def _run_steps(
|
||||
raise StepFailed("uphill", " · ".join(parts["failed_parts"]) + " 실패")
|
||||
# 노선 바뀜 = 옛 노선 세션 칸 → 새 측점 patch(Node) · 고침이 없어도 한 번(429 ④)
|
||||
if moved:
|
||||
progress.mark(project_id, "cross")
|
||||
try:
|
||||
carried = await moving.carry_cross(project_id, route_id, request.carry, before)
|
||||
except StepFailed:
|
||||
@@ -251,9 +288,16 @@ async def save_design(
|
||||
)
|
||||
finally:
|
||||
files.discard()
|
||||
progress.end(project_id)
|
||||
return JSONResponse(content={"status": "success", "waited": waited, "steps": steps})
|
||||
|
||||
|
||||
@router.get("/{project_id}/design/save/progress")
|
||||
async def get_design_save_progress(project_id: UUID) -> dict[str, Any]:
|
||||
"""잠금판 진행 묻기(520-4) — 차례 이름 · n/N · 지난 초 · 안 돌면 active 거짓."""
|
||||
return progress.snapshot(project_id)
|
||||
|
||||
|
||||
async def _restore(project_id: UUID, rows: dict[str, list], files: guard.StorageSnapshot) -> bool:
|
||||
"""떠 둔 대로 — 성공 = 참 · 하나라도 못 되돌리면 로그 남기고 거짓(조용히 넘기지 않음)."""
|
||||
ok = True
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""B05 · B06 한 본문 저장 진행(PLAN 520-4) — 지금 도는 차례 이름 · n/N · 지난 초를 메모리에.
|
||||
|
||||
`B06_Section_Router_DesignSave._run_steps` 가 차례마다 `mark` · 끝에 `end` · 화면 잠금판이 1 s 마다
|
||||
`GET /design/save/progress`(창구는 `B06_Section_Router_DesignSave`)로 물음
|
||||
(페이지 넘김 `moveStage` 단계의 progress 칸).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
# ponytail: 프로세스 메모리 한 벌 — 서버 일꾼이 여럿이면 다른 일꾼 진행은 안 보임(그때 파일 · DB 로)
|
||||
_PROGRESS: dict[str, dict[str, Any]] = {}
|
||||
|
||||
|
||||
def begin(project_id: UUID, planned: list[str]) -> None:
|
||||
"""저장 한 번 시작 — 차례 이름 목록(N)을 적음."""
|
||||
_PROGRESS[str(project_id)] = {"planned": planned, "step": None, "started": time.monotonic()}
|
||||
|
||||
|
||||
def mark(project_id: UUID, name: str) -> None:
|
||||
"""차례 들어감 — 목록 밖 이름(배수유역 다시 셈 등)은 앞 차례 자리 그대로."""
|
||||
entry = _PROGRESS.get(str(project_id))
|
||||
if entry is not None:
|
||||
entry["step"] = name
|
||||
|
||||
|
||||
def end(project_id: UUID) -> None:
|
||||
_PROGRESS.pop(str(project_id), None)
|
||||
|
||||
|
||||
def snapshot(project_id: UUID) -> dict[str, Any]:
|
||||
"""지금 진행 — 안 돌면 active 거짓."""
|
||||
entry = _PROGRESS.get(str(project_id))
|
||||
if entry is None or entry["step"] is None:
|
||||
return {"active": False}
|
||||
planned: list[str] = entry["planned"]
|
||||
step = entry["step"]
|
||||
# 배수유역 다시 셈 = 관 차례 안 · 자리는 관
|
||||
seat = "pipes" if step == "basins" else step
|
||||
index = planned.index(seat) + 1 if seat in planned else len(planned)
|
||||
return {
|
||||
"active": True,
|
||||
"step": step,
|
||||
"index": index,
|
||||
"total": len(planned),
|
||||
"elapsed_s": round(time.monotonic() - entry["started"], 1),
|
||||
}
|
||||
@@ -67,6 +67,7 @@ function makeLoader({
|
||||
if (useCache && cache.has(full)) return cache.get(full);
|
||||
const stub = fileStub && fileStub(full);
|
||||
if (stub) return stub;
|
||||
if (full.endsWith(".css")) return {}; // 화면 글꼴 import(ui_template_progress.css 등) — 트랜스파일 못 함
|
||||
if (full.endsWith(".json")) {
|
||||
const data = JSON.parse(fs.readFileSync(full, "utf8"));
|
||||
if (useCache) cache.set(full, data);
|
||||
|
||||
@@ -115,7 +115,8 @@ def test_화면_선만_바뀌면_낡음_다시_셈() -> None:
|
||||
|
||||
def test_확정_성공도_상세_비움() -> None:
|
||||
# 470 N1 — [확정] 은 다음 페이지(B07)로 바로 감 · [저장]처럼 상세 메모리 · 옛 세션 사본을 비움
|
||||
# (이동은 잠금판 `moveStage` 가 · 520-4)
|
||||
shared = (ROOT / "A00_Common/design/design_shared.ts").read_text(encoding="utf-8")
|
||||
done = shared[shared.index(" done: async (isConfirm, reload = true) => {") :]
|
||||
confirm = done[done.index("if (isConfirm) {") : done.index("goToWorkflowStage(")]
|
||||
confirm = done[done.index("if (isConfirm) {") : done.index('L("B06_Profile_Confirm_Success")')]
|
||||
assert "invalidateSectionDetail(shared.projectId);" in confirm
|
||||
|
||||
@@ -97,15 +97,11 @@ def test_3_고친_것_없고_단계_완료면_바로_이동():
|
||||
assert 's.state === "COMPLETE"' in fn and "done(2) && !!done(3)" in fn
|
||||
go = shared[shared.index("export function confirmAndGo(") :]
|
||||
go = go[: go.index("\n}\n")]
|
||||
assert (
|
||||
"skip ? goToWorkflowStage(shared.projectId, route) : saveSharedDesign(shared, true, route)"
|
||||
in go
|
||||
)
|
||||
# 잠금판 한 단계 — 확정됐으면 저장 없이 이동(520-4 moveStage)
|
||||
assert "run: async () => (await alreadyConfirmed(id)) || saveSharedDesign(shared, true)," in go
|
||||
# 줄 세움 = [저장] · 확정 한 곳
|
||||
assert (
|
||||
"await queueDesignSave(shared.projectId, confirm, () => saveOnce(shared, confirm, target));"
|
||||
in shared
|
||||
)
|
||||
assert "await queueDesignSave(shared.projectId, confirm, () =>" in shared
|
||||
assert "saveOnce(shared, confirm, moved)," in shared
|
||||
|
||||
|
||||
def test_결과_한_줄_걷음():
|
||||
|
||||
@@ -55,14 +55,13 @@ def test_노선선은_답의_노선_낡은_답은_옛_그림():
|
||||
assert "if (!routeFromAnswer) takeRoute(points, true);" in panel # 페이지 노선 = 첫 답 전만
|
||||
|
||||
|
||||
def test_B06_다음은_낡으면_확인_뒤_업데이트():
|
||||
def test_B06_다음은_잠금판_한_단계_배수유역은_서버가_한_번():
|
||||
# 520-4 — 낡음 확인 창 · 화면 [업데이트] 없앰 · 서버 [확정] 관 차례가 한 번 다시 셈(진행 이름 basins)
|
||||
shared = _read(ROOT / "A00_Common" / "design" / "design_shared.ts")
|
||||
go = shared[shared.index("export function confirmAndGo(") :]
|
||||
go = go[: go.index("\n}\n", go.index("async function drainageFirst("))]
|
||||
assert "void drainageFirst(shared).then((ready) => (ready ? go() : undefined));" in go
|
||||
first = go[go.index("async function drainageFirst(") :]
|
||||
assert (
|
||||
first.index("side?.stale()")
|
||||
< first.index("showConfirmDialog(")
|
||||
< first.index("side.update()")
|
||||
)
|
||||
go = go[: go.index("\n}\n")]
|
||||
assert "moveStage(id, route, [" in go and "progress: () => designSaveProgress(id)" in go
|
||||
assert "drainageFirst" not in shared and "side.update()" not in go
|
||||
save = _read(ROOT / "B06_Section" / "B06_Section_Router_DesignSave.py")
|
||||
assert "basins = request.confirm and await _stale_or_false(_analysis_stale, project_id)" in save
|
||||
assert '"basins" if basins else None' in save
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""520-4 — B05 · B06 한 본문 저장 진행(잠금판 「n/N 차례 이름」) 메모리 기록 · 배수유역 다시 셈 자리."""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from B06_Section import B06_Section_Router_DesignSave_Progress as progress
|
||||
|
||||
|
||||
def test_차례_n_N_과_배수유역_자리() -> None:
|
||||
pid = uuid4()
|
||||
assert progress.snapshot(pid) == {"active": False}
|
||||
progress.begin(pid, ["settings", "pipes", "structures", "cross"])
|
||||
assert progress.snapshot(pid) == {"active": False} # 첫 차례 전
|
||||
progress.mark(pid, "basins")
|
||||
now = progress.snapshot(pid)
|
||||
assert (now["step"], now["index"], now["total"]) == ("basins", 2, 4) # 관 차례 자리
|
||||
progress.mark(pid, "cross")
|
||||
assert progress.snapshot(pid)["index"] == 4
|
||||
progress.mark(pid, "stages") # 목록 밖 = 끝 자리
|
||||
assert progress.snapshot(pid)["index"] == 4
|
||||
progress.end(pid)
|
||||
assert progress.snapshot(pid) == {"active": False}
|
||||
@@ -92,9 +92,9 @@ def test_두_페이지가_저장_한_줄기를_탄다():
|
||||
shared = (ROOT / "A00_Common" / "design" / "design_shared.ts").read_text(encoding="utf-8")
|
||||
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text(encoding="utf-8")
|
||||
persist = (ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts").read_text(encoding="utf-8")
|
||||
assert shared.count("await saveDesign(") == 1, "저장 한 줄기를 한 자리에서만 부름"
|
||||
assert shared.count(" saveDesign(") == 1, "저장 한 줄기를 한 자리에서만 부름"
|
||||
page = (ROOT / "A00_Common" / "design" / "design_page.ts").read_text(encoding="utf-8")
|
||||
assert "saveSharedDesign(shared, true, route)" in shared, "확정(B06 [>])이 한 줄기를 안 탐"
|
||||
assert "saveSharedDesign(shared, true)" in shared, "확정(B06 [>])이 한 줄기를 안 탐"
|
||||
assert "confirmAndGo(shared, route)" in page, "B06 [>] · 단계 판 B07~B09 가 확정을 안 탐(496-1)"
|
||||
assert 'await shared.ensureView("cross")' in shared, "종단 보기 [확정]이 횡단 자료를 못 얻음"
|
||||
for name, text in (("종단 보기", actions), ("횡단 보기", persist), ("한 벌", shared)):
|
||||
|
||||
@@ -35,7 +35,9 @@ def test_저장이_면적을_모으기_전에_다시_계산한다() -> None:
|
||||
assert "collectSectionEdits(ctx)" in body[body.index("crossPart:") :], (
|
||||
"면적 수집이 횡단 조각 밖"
|
||||
)
|
||||
shared = _body(SHARED.read_text(encoding="utf-8"), "export async function saveSharedDesign")
|
||||
shared = _body(
|
||||
SHARED.read_text(encoding="utf-8"), "export async function saveSharedDesign", 2000
|
||||
)
|
||||
assert "prepare: crossSave?.prepare" in shared, "한 벌이 재계산을 저장 한 줄기에 안 넘긴다"
|
||||
save = SAVE.read_text(encoding="utf-8")
|
||||
# 472-6 — [저장] = 한 본문(`saveDesign` · 노선 바뀜도) · 재계산 뒤에 횡단 조각을 모음
|
||||
|
||||
Reference in New Issue
Block a user