From 3e7c477631902e1840739b401f0f65284ce0161e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 18:51:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(est-test):=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EA=B3=B5=EC=82=AC=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EC=B0=BD(=EA=B0=99=EC=9D=80=20=ED=91=9C=20=EB=B3=B4=EA=B8=B0?= =?UTF-8?q?=C2=B7=EC=B0=BE=EA=B8=B0=C2=B7=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- tools/est-test/PLAN.md | 1 + tools/est-test/index.html | 2 + tools/est-test/native-actions-more.js | 1 + tools/est-test/native-other-load.js | 113 ++++++++++++++++++++++++++ tools/est-test/server.cjs | 1 + 5 files changed, 118 insertions(+) create mode 100644 tools/est-test/native-other-load.js diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 5a8fb0216..a3f724170 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -489,6 +489,7 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD - 남음: 구성비·요율 전환(CalcBase), 약어 표시(SName), 제비율 기준 문서 보기(원본 한글 파일). - [x] 수량산출서 검색·전체보기(원본 TFRMMULLANG btnSerch·btnAllDisplay — 빈칸·|·괄호 무시 포함 검색) — 고르기 줄을 `native-quantity-sheets.js` 로 옮겨 700줄 제한 유지. - 검증완료: `test_est_quantity_sheets.py` 검색 시험 포함 수량 시험 22개 통과. ORCA 브라우저에서 「집수」 검색 → 항목에 집수정이 있는 「배수」만·선택 이동, 전체보기 → 3개 복귀, 샘플 복구. + - [x] 다른 공사 불러오기 창(원본 barbtnSelfLoad → TfrmOtherLoad, 찾기 popSerch) — 저장된 다른 작업을 골라 같은 표를 띄우고 찾기(품명·규격, A&B 둘 다)·고른 줄을 하위 자료와 함께 가져오기(내역서는 고른 줄 아래에 줄, 나머지는 자료·호표 합침, 같은 코드는 이 공사 것). 검증완료: 바인딩 통과, ORCA 브라우저에서 산불진화임도 작업의 일위대가 3개 → 「취수장」 1개 → 가져오기 호표 8→9·하위 41건, 샘플 복구. - [x] 사용위치 검색 창(원본 TfrmUsesDataSerch btnSerch·btnAllDisplay) — 현재 표 항목을 품명·규격(빈칸·|·괄호 무시)으로 거르고 골라 사용위치 보기. 검증완료: 바인딩 통과, ORCA 브라우저에서 노임 9개 → 「보 통」 1개 → 보통인부 사용위치 11곳. - [x] 인쇄양식 불러오기·저장하기(원본 mnuPrintFormatLoad·Save 0x10ed318·0x10ed000 — spec_pc §3) — .pfot(인쇄정보표 MIDAS 이진) 읽기·쓰기 `print-format.cjs`(설명 BLOB·빈 칸 표지까지 보존, 원본 파일 그대로 다시 쓰면 바이트 같음). 불러오면 공사 인쇄표를 그 값으로, 저장은 지금 인쇄표를 원본 설치 main.pfot 필드 설명자 위에(`EST_PFOT_TEMPLATE`). .gfot(cxGrid 열 배치)는 웹 격자와 호환되지 않아 하지 않음. - 검증완료: `test_est_print_format.py`(설치 .pfot 두 개 바이트 같음·고친 값 반영) 및 바인딩 시험 통과, 서버 경로로 읽고 다시 쓴 파일 cmp 같음. ORCA 브라우저에서 샘플 인쇄양식 저장(32,755 바이트). diff --git a/tools/est-test/index.html b/tools/est-test/index.html index 05c27ada2..aed780765 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -20,6 +20,7 @@ + @@ -317,6 +318,7 @@ > { typeof EstPriceTools !== "undefined" && EstPriceTools, typeof EstMiscTools !== "undefined" && EstMiscTools, typeof EstExcelToEst !== "undefined" && EstExcelToEst, + typeof EstOtherLoad !== "undefined" && EstOtherLoad, ].filter(Boolean); function handle(name, state, node) { for (const module of modules()) if (module.handle(name, state, node)) return true; diff --git a/tools/est-test/native-other-load.js b/tools/est-test/native-other-load.js new file mode 100644 index 000000000..c1267e76f --- /dev/null +++ b/tools/est-test/native-other-load.js @@ -0,0 +1,113 @@ +// 원본 「다른 공사 불러오기」 창(barbtnSelfLoad → f135b8 → TfrmOtherLoad — spec_pb2 §5.2·§11): 저장된 다른 작업의 같은 표를 띄워 +// 찾기(Ctrl+F: 품명·규격 빈칸·|·괄호 무시, 「A&B」 는 둘 다)·고른 줄을 하위 자료와 함께 이 공사로 가져오기(EstTransfer, 같은 코드는 이 공사 것). +// 내역서는 고른 줄 아래에 줄을 넣고, 자재·노임·표준·견적·일위·단산·기계는 자료(호표)를 합친다. +const EstOtherLoad = (() => { + const KIND = { + StuffcDat: 5, + LaborcDat: 6, + SilJukDat: 21, + EstiMateDat: 101, + HeadMainDat: 103, + CostMainDat: 104, + MachMainDat: 105, + }; + const norm = (s) => String(s ?? "").replace(/[\s|()]/g, ""); + const match = (row, word) => { + const k = norm(row.Name || EstText.clean(row.LineData || "")) + norm(row.Size); + return norm(word) + .split("&") + .filter(Boolean) + .every((p) => k.includes(p)); + }; + + async function open(state) { + if (!state.project.tables[state.view]) throw Error("자료 화면에서 쓰세요."); + const o = (state.otherLoad ||= { id: "", word: "", picked: new Set() }); + o.projects = await EstApp.api("/api/projects"); + draw(state); + } + function rowsOf(o, view) { + const rows = o.source?.tables?.[view] || []; + return rows + .map((r, i) => [r, i]) + .filter(([r]) => { + if (["HeadMainDat", "CostMainDat", "MachMainDat"].includes(view) && !(Number(r.Index) > 0)) + return false; + return (r.Name || r.Code || r.LineData) && (!o.word || match(r, o.word)); + }); + } + function draw(state) { + const o = state.otherLoad, + { esc, number } = EstApp, + list = rowsOf(o, state.view).slice(0, 400); + EstApp.showDialog( + "다른 공사 불러오기", + `
${list.map(([r, i]) => ``).join("")}
${esc(r.Code ?? "")}${esc(r.Name || EstText.clean(r.LineData || ""))}${esc(r.Size ?? "")}${esc(r.Unite ?? "")}${number(r.TC)}
`, + ); + } + function take(state) { + const o = state.otherLoad, + view = state.view, + picked = [...o.picked].sort((a, b) => a - b).map((i) => o.source.tables[view][i]); + if (!picked.length) throw Error("가져올 줄을 고르세요."); + let added = 0, + kept = 0; + EstApp.change((project) => { + if (view === "EstMainDat") { + const deps = EstTransfer.collect(o.source, picked); + const r = EstTransfer.merge(project, deps); + added += r.added.length; + kept += r.kept.length; + const at = Math.max(-1, ...state.selection) + 1 || project.tables.EstMainDat.length; + added += EstTransfer.paste(project, picked, at); + } else { + const deps = EstTransfer.collect( + o.source, + picked.map((r) => ({ LinkIndex: KIND[view], Code: r.Code })), + ); + const r = EstTransfer.merge(project, deps); + added += r.added.length; + kept += r.kept.length; + } + }); + EstApp.byId("dialog").close(); + state.notice = `${added}건을 가져왔습니다${kept ? `, 같은 코드 ${kept}건은 이 공사 자료를 그대로 씀` : ""}.`; + o.picked = new Set(); + } + + function install() { + document.addEventListener("change", async (event) => { + const t = event.target, + o = EstApp.state.otherLoad; + if (!o) return; + try { + if (t.dataset.olProject !== undefined) { + o.id = t.value; + o.source = t.value ? await EstApp.api(`/api/projects/${t.value}`) : null; + o.picked = new Set(); + draw(EstApp.state); + } else if (t.dataset.olPick !== undefined) { + const i = Number(t.dataset.olPick); + if (t.checked) o.picked.add(i); + else o.picked.delete(i); + } + } catch (error) { + EstApp.state.notice = error.message; + EstApp.render(); + } + }); + } + if (typeof document !== "undefined" && document.addEventListener) install(); + + function handle(name, state) { + if (name === "other-load") + open(state).catch((e) => ((state.notice = e.message), EstApp.render())); + else if (name === "ol-search") { + state.otherLoad.word = document.querySelector("[data-ol-word]")?.value || ""; + draw(state); + } else if (name === "ol-take") take(state); + else return false; + return true; + } + return { match, handle }; +})(); diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index c96fcca5d..1b9c4f2de 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -103,6 +103,7 @@ const allowed = new Set([ "native-price-tools.js", "native-misc-tools.js", "native-excel-to-est.js", + "native-other-load.js", "native-actions-more.js", "native-wonga-tools.js", "native-estimate-print.js",