// 원본 구버전 EST Plus 자료 3종(spec_misc §2 · spec_estplus.md): 「EstPlus 데이타 불러오기」(mnuFileEstPlusLoad — .est 를 풀어 // 표마다 변환해 새 작업으로), 「Est Plus와 단가비교」(ActionEstCompare Ctrl+Alt+E → TfrmCompare: 비교할 항목 4가지 중 고르고 // 비교파일선택 → 이 공사 머리 행과 구형 파일 Index > 0 레코드를 차례로 짝지어 합계가 다르면 표시, 더블클릭 = 그 자료로 이동), // 「EstPlus 데이타로 저장」(ActionConvertToEstPlus Ctrl+Shift+F12 → 394 바이트 판 .est). 읽기·쓰기는 서버 legacy-estplus.cjs. const EstEstPlus = (() => { const num = (v) => Number(v) || 0; const KINDS = [ ["기계경비", "MachMainDat"], ["단가산출", "CostTitleDat"], ["일위대가", "HeadMainDat"], ["내역서", "EstMainDat"], ]; const pickFile = () => new Promise((resolve) => { const picker = document.createElement("input"); picker.type = "file"; picker.accept = ".est,.ebk"; picker.addEventListener("change", () => resolve(picker.files[0])); picker.click(); }); // 이 공사 쪽 행(Index > 0)과 구형 값을 차례로 짝지음 · 확인 = 합계 다름(정확 비교) function pair(project, kind, legacy) { const [, table] = KINDS[kind], rows = (project.tables[table] || []) .map((row, index) => ({ row, index })) .filter(({ row }) => num(row.Index) > 0); return rows.map(({ row, index }, i) => { const o = legacy[i] || { ONU: 0, OGU: 0, OJU: 0 }, mine = num(row.NC) + num(row.GC) + num(row.JC) + num(row.SC); return { index, Index: row.Index, Code: row.Code, Name: row.Name || EstText.clean(row.LineData || ""), Size: row.Size, Unite: row.Unite, NC: num(row.NC), GC: num(row.GC), JC: num(row.JC), SC: num(row.SC), ...o, UsesCheck: mine !== o.ONU + o.OGU + o.OJU, }; }); } let compared = { kind: 0, rows: [] }; function draw() { const { esc, number } = EstApp, { kind, rows } = compared; EstApp.showDialog( "단가비교", `
비교할항목 선택${KINDS.map(([label], i) => ``).join("")}${rows.filter((r) => r.UsesCheck).length}
${rows .map( (r) => ``, ) .join("")}
확인No품명규격단위노무비재료비경비실적노무비재료비경비
${r.UsesCheck ? "✔" : ""}${r.Index}${esc(r.Name)}${esc(r.Size ?? "")}${esc(r.Unite ?? "")}${number(r.NC)}${number(r.GC)}${number(r.JC)}${number(r.SC)}${number(r.ONU)}${number(r.OGU)}${number(r.OJU)}
`, ); } async function compareFile() { compared.kind = Number(document.querySelector('[name="ep-kind"]:checked')?.value || 0); const file = await pickFile(); if (!file) return; const legacy = await EstApp.api(`/api/estplus-compare?kind=${compared.kind}`, { method: "POST", body: await file.arrayBuffer(), }); compared.rows = pair(EstApp.state.project, compared.kind, legacy); draw(); } async function load(state) { const file = await pickFile(); if (!file) return; const project = await EstApp.api( `/api/estplus-read?name=${encodeURIComponent(file.name.replace(/\.(est|ebk)$/i, ""))}`, { method: "POST", body: await file.arrayBuffer() }, ); if (!confirm("구버젼 데이타를 변환했습니다. 현재 작업을 변환한 자료로 바꾸시겠습니까?")) return; EstApp.setProject(project); state.notice = (project.importWarnings || []).join(" "); EstApp.render(); } async function convert(state) { const response = await fetch("/api/estplus-write", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(state.project), }); if (!response.ok) throw Error((await response.json()).error); EstApp.download( `${state.project.name || "내역서"}.est`, await response.blob(), "application/octet-stream", ); state.notice = "변환을 마쳤습니다."; EstApp.render(); } function install() { document.addEventListener("keydown", (event) => { const k = event.key.toLowerCase(); if (event.ctrlKey && event.altKey && k === "e") { event.preventDefault(); handle("estplus-compare", EstApp.state); } else if (event.ctrlKey && event.shiftKey && event.key === "F12") { event.preventDefault(); handle("estplus-convert", EstApp.state); } }); document.addEventListener("dblclick", (event) => { const tr = event.target.closest?.("[data-ep-row]"); if (!tr) return; const [, table] = KINDS[compared.kind], row = EstApp.state.project.tables[table][Number(tr.dataset.epRow)]; if (!row?.Code && table !== "EstMainDat") return; EstApp.byId("dialog").close(); const view = { CostTitleDat: "CostMainDat" }[table] || table; EstApp.navigate(view, table === "EstMainDat" ? "" : row.Code); }); } if (typeof document !== "undefined" && document.addEventListener) install(); function handle(name, state) { const fail = (error) => { const note = EstApp.byId("estplus-error"); if (note) note.textContent = error.message; else { state.notice = error.message; EstApp.render(); } }; if (name === "estplus-compare") draw(); else if (name === "estplus-compare-file") compareFile().catch(fail); else if (name === "estplus-load") load(state).catch(fail); else if (name === "estplus-convert") convert(state).catch(fail); else return false; return true; } return { pair, handle }; })();