- design_save: 노선 초안이 없으면 saveOneBody — 차례마다 조각(본문 · 저장 뒤 정리) · 서버가 다 저장한 뒤에만 정리(초안 비움 · 화면 맞춤) · 실패 = 차례 이름째 「아무것도 저장 안 함」 · 되돌림 실패 = 「복구 확인 필요」 · 기다림 알림 - 조각 함수: 페이지 설정 · 관 · 구조물(보기 목록) · 추가 측점 · 계획고(한 벌 종단도) · 관 옵션 · 상단측 · 횡단(횡단 보기) — 옛 flush 함수는 노선 바뀐 저장 길에 그대로(조각을 써서 같은 본문) - 보기 몫 steps · cross → parts · crossPart · 노선 바뀐 저장 길은 옛 차례 그대로(6-B) - 옛 9 차례를 단언하던 시험 10 개를 한 본문 꼴로(옛 PUT 줄 → SAVE 줄 · 조각 정리 차례) · 위키 [저장] 표 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
199 lines
7.1 KiB
JavaScript
199 lines
7.1 KiB
JavaScript
/* PLAN 449 헬퍼 — [저장] 관 차례 · 노선 저장 뒤 실패(433-7 · 433-12). 실제 `design_save.ts` · 관 초안 창구 · 가게를
|
|
* Node 로 · 서버 창구 · 계획고 초안 · 노선 편집 창구만 가짜. 짝 = `test_449_save_pipes.py`. */
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
const CATALOG = require("./helper_322_catalog_module.cjs");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
|
|
function memoryStorage() {
|
|
const map = new Map();
|
|
return {
|
|
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
|
setItem: (key, value) => map.set(key, String(value)),
|
|
removeItem: (key) => map.delete(key),
|
|
key: (index) => [...map.keys()][index] ?? null,
|
|
get length() {
|
|
return map.size;
|
|
},
|
|
};
|
|
}
|
|
globalThis.window = {
|
|
sessionStorage: memoryStorage(),
|
|
localStorage: memoryStorage(),
|
|
setTimeout,
|
|
clearTimeout,
|
|
};
|
|
|
|
const STUBS = {};
|
|
const cache = new Map();
|
|
function loadTs(full) {
|
|
if (cache.has(full)) return cache.get(full);
|
|
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
cache.set(full, box.exports);
|
|
const localRequire = (request) => {
|
|
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
|
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
|
if (key) return STUBS[key];
|
|
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
|
|
if (request.startsWith("@util/"))
|
|
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
|
|
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
|
return require(request);
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
return box.exports;
|
|
}
|
|
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
|
|
|
|
let calls = [];
|
|
let failAt = null; // 이 차례에서 throw
|
|
let routeBody = null;
|
|
const alignment = new Map(); // 노선 칸 → 계획고 초안
|
|
STUBS["ui_template_elements"] = {
|
|
showLoadingOverlay: () => undefined,
|
|
hideLoadingOverlay: () => undefined,
|
|
showToast: () => undefined,
|
|
};
|
|
const failing = (name) => {
|
|
if (failAt === name) throw new Error(`${name} 409`);
|
|
};
|
|
STUBS["B04_PreProcess_Api_Fetch"] = {
|
|
fetchDetailPipePoints: async () => (calls.push(["GET"]), { pipe_points: [] }),
|
|
saveDetailPipePoints: async (_projectId, points, recompute) => {
|
|
calls.push(["PUT", points ?? null, !!recompute]);
|
|
return {};
|
|
},
|
|
};
|
|
STUBS["B05_Profile_Api_Structures"] = {
|
|
flushPendingStructures: async () => failing("structures"),
|
|
structuresPart: async () => null,
|
|
readPendingStructures: () => null, // 구조물 초안 없음 — 노선 저장 뒤 얹기(459) 안 돎
|
|
};
|
|
STUBS["B05_Profile_Api_PlusStations"] = {
|
|
flushPlusStations: async () => undefined,
|
|
plusStationsPart: () => ({ body: null }),
|
|
};
|
|
STUBS["B05_Profile_Api_Fetch"] = {
|
|
flushUphillOverrides: async () => undefined,
|
|
flushRouteParts: async () => undefined, // 상단측 차례(463)
|
|
routePartsBody: async () => ({}),
|
|
saveProfileAlignment: async (_projectId, routeId, draft) => {
|
|
calls.push(["ALIGN", routeId, draft]);
|
|
return {};
|
|
},
|
|
};
|
|
STUBS["B05_Profile_UI_Profile_Edit"] = {
|
|
pendingAlignment: (routeId) => alignment.get(routeId) ?? null,
|
|
markAlignmentSaved: (routeId) => alignment.delete(routeId),
|
|
writeAlignmentDraft: (routeId, edits) => alignment.set(routeId, edits),
|
|
};
|
|
STUBS["B06_Section_Api_Culvert_Options"] = {
|
|
flushCulvertOptions: async () => undefined,
|
|
culvertOptionsPart: () => null,
|
|
};
|
|
// 472-6 한 본문 창구 — 관 칸 · 계획고 칸만 적음(옛 PUT 줄과 같은 꼴)
|
|
STUBS["design_save_api"] = {
|
|
SAVE_PART_KEYS: [
|
|
"settings",
|
|
"pipes",
|
|
"structures",
|
|
"plusStations",
|
|
"alignment",
|
|
"culvertOptions",
|
|
"uphill",
|
|
"cross",
|
|
],
|
|
DesignSaveError: class extends Error {},
|
|
postDesignSave: async (_projectId, routeId, confirm, parts) => {
|
|
const pipes = parts.pipes?.body;
|
|
calls.push([
|
|
"SAVE",
|
|
pipes === undefined || pipes === null ? "none" : (pipes.points ?? null),
|
|
confirm,
|
|
]);
|
|
const align = parts.alignment?.body;
|
|
if (align) {
|
|
const { route_id: _id, ...draft } = align;
|
|
calls.push(["ALIGN", routeId, draft]);
|
|
}
|
|
return { waited: false, steps: {} };
|
|
},
|
|
};
|
|
STUBS["B05_Profile_UI_RouteEdit_Apply"] = {
|
|
pendingRouteSave: async () => routeBody,
|
|
saveRouteDraft: async () => (calls.push(["ROUTE"]), { route_id: 8 }),
|
|
carryAfterRouteSave: async (_projectId, from, to, options) => {
|
|
calls.push(["CARRY", from, to, options?.save ?? true]);
|
|
failing("carry");
|
|
return 0;
|
|
},
|
|
routeSaveSummary: () => "",
|
|
rebuildAfterRouteSave: async () => void calls.push(["REBUILD"]),
|
|
};
|
|
const save = load("A00_Common", "design", "design_save.ts");
|
|
const drafts = load("B05_Profile", "B05_Profile_Api_Pipes_Draft.ts");
|
|
|
|
const P = "proj-449";
|
|
async function once({ route = false, draft = null, confirm = false, fail = null, routeId = 7 }) {
|
|
calls = [];
|
|
failAt = fail;
|
|
routeBody = route ? { draft: {} } : null;
|
|
drafts.writePendingPipes(P, draft);
|
|
let failure = null;
|
|
const ok = await save.saveDesign(
|
|
{
|
|
projectId: P,
|
|
routeId: () => routeId,
|
|
// 화면 몫 = 배수판 다시 받기(PUT 없음) — 433-7 뒤 design_shared 와 같음
|
|
parts: {
|
|
pipes: (base) => ({
|
|
body: base?.body ?? null,
|
|
after: async (response) => {
|
|
await base?.after?.(response);
|
|
calls.push(["RELOAD"]);
|
|
},
|
|
}),
|
|
},
|
|
crossPart: async () => ({ body: {} }),
|
|
failed: (error) => (failure = String(error && error.message ? error.message : error)),
|
|
},
|
|
{ confirm },
|
|
);
|
|
return { ok, failure, calls, left: drafts.readPendingPipes(P) };
|
|
}
|
|
|
|
async function run() {
|
|
window.localStorage.setItem("frd_current_project_id", P);
|
|
const fifteen = [{ chainage_m: 15, source: "user", id: "p1" }];
|
|
const out = {};
|
|
out.draft = await once({ draft: fifteen }); // B06 고침 직후 [저장] = 초안 값
|
|
out.noDraft = await once({}); // 판 안 섬 · 초안 없음 = PUT 없음
|
|
out.confirmNoDraft = await once({ confirm: true }); // [확정] = 칸 없이(서버 저장분)
|
|
out.emptyDraft = await once({ draft: [] }); // 사용자가 다 지움 = 빈 목록
|
|
// 433-12 — 노선 저장 뒤 구조물 실패 → 0 칸 계획고가 새 노선 칸에 · 다음 [저장]이 보냄
|
|
alignment.clear();
|
|
alignment.set(0, { pvi: { 120: 1.5 } });
|
|
out.routeFail = await once({ route: true, fail: "structures" });
|
|
out.alignAfterFail = Object.fromEntries(alignment);
|
|
out.nextSave = await once({ routeId: 8 });
|
|
// 노선 저장 뒤 횡단 넘김 자체가 실패 → 두 번 안 넘김
|
|
alignment.clear();
|
|
out.carryFail = await once({ route: true, fail: "carry" });
|
|
return out;
|
|
}
|
|
|
|
run()
|
|
.then((out) => {
|
|
process.stdout.write(JSON.stringify(out));
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
process.stderr.write(String(error && error.stack ? error.stack : error));
|
|
process.exit(1);
|
|
});
|