From 684e0e7684cdb47f502c7edec7d5ab53d2dee498 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 13:08:24 +0900 Subject: [PATCH] =?UTF-8?q?feat(est-test):=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95(=EB=B9=84=EB=AA=A9=EC=88=9C=EC=84=9C=C2=B7?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20=ED=91=9C=EC=8B=9C=C2=B7=ED=92=88=EB=AA=85?= =?UTF-8?q?=EC=A0=95=EB=A0=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- resources/tester/test_est_display.py | 70 +++++++++++++++++++ tools/est-test/PLAN.md | 2 + tools/est-test/index.html | 2 + tools/est-test/native-business-actions.js | 3 +- tools/est-test/native-display.js | 85 +++++++++++++++++++++++ tools/est-test/native-views.js | 21 +++--- tools/est-test/server.cjs | 1 + 7 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 resources/tester/test_est_display.py create mode 100644 tools/est-test/native-display.js diff --git a/resources/tester/test_est_display.py b/resources/tester/test_est_display.py new file mode 100644 index 000000000..239476ff7 --- /dev/null +++ b/resources/tester/test_est_display.py @@ -0,0 +1,70 @@ +"""EST 테스트 앱 표시 설정(원본 연차공사 환경설정 TfrmEstMultiOption — 비목순서·구분 표시·품명정렬).""" + +import json +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def test_column_order_gubun_and_align(): + code = r""" + const fs = require('fs'), vm = require('vm'); + const ctx = vm.createContext({}); + vm.runInContext(fs.readFileSync('tools/est-test/native-display.js', 'utf8'), ctx); + const D = vm.runInContext('EstDisplay', ctx); + const cols = ['Code', 'Name', 'ValueIndex', 'GN', 'TU', 'NU', 'GU', 'JU', 'TC', 'NC', 'GC', 'JC', 'SC']; + const o = (Display) => ({ Display }); + process.stdout.write(JSON.stringify({ + base: D.columns({}, cols), + jae: D.columns(o({ order: 3 }), cols), + four: D.columns(o({ order: 4, gubun: false }), cols), + align: [D.nameStyle({}), D.nameStyle(o({ align: 2 }))], + })); + """ + out = json.loads(subprocess.check_output(["node", "-e", code], cwd=ROOT).decode("utf-8")) + assert out["base"] == [ + "Code", + "Name", + "ValueIndex", + "GN", + "TU", + "NU", + "GU", + "JU", + "TC", + "NC", + "GC", + "JC", + "SC", + ] + assert out["jae"] == [ + "Code", + "Name", + "ValueIndex", + "GN", + "GU", + "NU", + "JU", + "TU", + "GC", + "NC", + "JC", + "SC", + "TC", + ] + assert out["four"] == [ + "Code", + "Name", + "GN", + "TU", + "NU", + "GU", + "JU", + "TC", + "NC", + "GC", + "JC", + "SC", + ] + assert out["align"] == ["", "text-align:center"] diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index e74efa976..4b08ac894 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -487,6 +487,8 @@ 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] 표시 설정(원본 연차공사 환경설정 TfrmEstMultiOption 0xf9c8f0 · 열 순서 0xf0f620) — 비목순서(합/노/재/경 · 합/재/노/경 · 노/재/경/합 · 재/노/경/합, 4·5 는 원본처럼 그대로) 금액·단가 열 재배치, 구분(선택출처) 열 보이기, 품명정렬. 계산 환경 `Display`, 계산 불변. 원본은 연차공사 문서에서만 열지만 웹은 모든 표에 적용. 품명 두 줄 표기·비고란 발췌근거는 하지 않음. + - 검증완료: `test_est_display.py`(재/노/경/합 순·4번 그대로·구분 숨김·정렬) 통과. ORCA 브라우저에서 내역서 노/재/경/합 → NU·GU·JU·SU·TU / NC·GC·JC·SC·TC, 품명 가운데, 도급액 불변, 설정 지워 복구. - [x] 원가계산서 순공사비 입력(원본 가로양식 TfrmWongaH popTotalInput 0xe7fbf8) — 연결 54 줄이 있으면 「순공사비 항목이 있습니다」(그 줄 고름), 없으면 맨 위에 「순 공 사 비」(연결 54, 금액 = 직접공사비) 넣고 다시 계산. 원본이 이어서 하는 원가 전체 초기화(모든 줄 자리수·도급액 고정 지움)는 사용자 설정을 지우는 부작용이라 하지 않음. - 검증완료: 원가·바인딩 시험 통과. ORCA 브라우저에서 넣기 → 맨 위 순공사비 45,293,066(= 직접공사비)·도급액 79,603,000 불변, 다시 누르면 거부 문구, 샘플 복구. - 제외: 자동비목생성(Ctrl+Alt+G 숨은 시험용 — 비목군을 무작위로 채움), 단가산출 정리(TfrmCostSpaceEdit — 원본 실행 파일에 코드가 링크되지 않아 열 수 없음). diff --git a/tools/est-test/index.html b/tools/est-test/index.html index 50b7c9f4e..3cb7a0323 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -37,6 +37,7 @@ + @@ -207,6 +208,7 @@ diff --git a/tools/est-test/native-business-actions.js b/tools/est-test/native-business-actions.js index 6faf47891..2edcd466c 100644 --- a/tools/est-test/native-business-actions.js +++ b/tools/est-test/native-business-actions.js @@ -629,7 +629,8 @@ const EstBusinessActions = (() => { (typeof EstSources !== "undefined" && EstSources.handle(name, state)) || (typeof EstDocEditor !== "undefined" && EstDocEditor.handle(name, state, node)) || (typeof EstDocForms !== "undefined" && EstDocForms.handle(name, state, node)) || - (typeof EstBlockFile !== "undefined" && EstBlockFile.handle(name, state)) + (typeof EstBlockFile !== "undefined" && EstBlockFile.handle(name, state)) || + (typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) ) { EstApp.render(); } else if (name === "insert-annual") { diff --git a/tools/est-test/native-display.js b/tools/est-test/native-display.js new file mode 100644 index 000000000..916fb1a20 --- /dev/null +++ b/tools/est-test/native-display.js @@ -0,0 +1,85 @@ +// 원본 연차공사 환경설정(TfrmEstMultiOption 0xf9c8f0 — 적용 0xf9cc6c · 열 순서 0xf0f620) — 화면 표시만 바꾸고 계산은 그대로. +// 비목순서: 0 합/노/재/경 · 1 합/재/노/경 · 2 노/재/경/합 · 3 재/노/경/합 (4·5 는 원본도 열을 옮기지 않음) — 금액(TC·NC·GC·JC·SC)과 +// 단가(TU·NU·GU·JU·SU) 열을 그 자리 안에서 다시 늘어놓는다. 구분 표시 = 선택출처(ValueIndex) 열 보이기. 품명정렬 = 품명 칸 정렬. +// 값은 계산 환경 Display 에 둔다(원본 옵션 객체 +0x34·+0x38·+0x39). 품명 두 줄 표기·비고란 발췌근거는 하지 않음. +const EstDisplay = (() => { + const ORDERS = [ + ["T", "N", "G", "J", "S"], + ["T", "G", "N", "J", "S"], + ["N", "G", "J", "S", "T"], + ["G", "N", "J", "S", "T"], + ]; + const LABELS = [ + "합/노/재/경", + "합/재/노/경", + "노/재/경/합", + "재/노/경/합", + "합/경/재/노", + "경/재/노/합", + ]; + const get = (options) => ({ order: 0, gubun: true, align: 0, ...(options?.Display || {}) }); + + function columns(options, cols) { + const d = get(options); + let out = d.gubun ? cols : cols.filter((k) => k !== "ValueIndex"); + const order = ORDERS[d.order]; + if (!order || d.order === 0) return out; + for (const suffix of ["C", "U"]) { + const keys = order.map((p) => p + suffix), + at = out.map((k, i) => (keys.includes(k) ? i : -1)).filter((i) => i >= 0); + if (at.length < 2) continue; + const present = keys.filter((k) => out.includes(k)); + out = out.slice(); + at.forEach((i, n) => (out[i] = present[n])); + } + return out; + } + function nameStyle(options) { + return ["", "text-align:right", "text-align:center"][get(options).align] || ""; + } + + function dialog(state) { + const d = get(state.project.options); + EstActions.form( + "환경설정(표시)", + [ + { name: "order", label: "비목순서", value: d.order, choices: LABELS.map((l, i) => [i, l]) }, + { + name: "gubun", + label: "구분 표시", + value: d.gubun ? "1" : "0", + choices: [ + ["1", "표시"], + ["0", "숨김"], + ], + }, + { + name: "align", + label: "품명정렬", + value: d.align, + choices: [ + [0, "좌정렬"], + [1, "우정렬"], + [2, "중앙정렬"], + ], + }, + ], + (values) => + EstApp.change( + (project) => + (project.options.Display = { + order: Number(values.order), + gubun: values.gubun === "1", + align: Number(values.align), + }), + false, + ), + ); + } + function handle(name, state) { + if (name !== "display-options") return false; + dialog(state); + return true; + } + return { columns, nameStyle, handle }; +})(); diff --git a/tools/est-test/native-views.js b/tools/est-test/native-views.js index 737e550fa..e661c793e 100644 --- a/tools/est-test/native-views.js +++ b/tools/est-test/native-views.js @@ -72,7 +72,11 @@ const EstViews = (() => { if (value && typeof value === "object") return `바이너리 자료`; if (EstModel.numeric.has(key)) return ``; - return ``; + const align = + key === "Name" && typeof EstDisplay !== "undefined" + ? EstDisplay.nameStyle(EstApp.state.project.options) + : ""; + return ``; } function grid(table, list, columns) { const { esc, number, state } = EstApp; @@ -128,13 +132,14 @@ const EstViews = (() => { if (project.tables[view]) { const all = EstApp.visibleRows(), start = state.page * 100, - list = all.slice(start, start + 100), - columns = - view === "Wonga" && typeof EstJungSang !== "undefined" - ? EstJungSang.columns(project, EstModel.columns(view)) - : typeof EstSources !== "undefined" - ? EstSources.columns(project.options, view, EstModel.columns(view)) - : EstModel.columns(view); + list = all.slice(start, start + 100); + let columns = + view === "Wonga" && typeof EstJungSang !== "undefined" + ? EstJungSang.columns(project, EstModel.columns(view)) + : typeof EstSources !== "undefined" + ? EstSources.columns(project.options, view, EstModel.columns(view)) + : EstModel.columns(view); + if (typeof EstDisplay !== "undefined") columns = EstDisplay.columns(project.options, columns); byId("work-content").innerHTML = grid(view, list, columns); byId("paging").hidden = false; byId("page-info").textContent = diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index 4d376bfb0..ab5fb1db0 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -79,6 +79,7 @@ const allowed = new Set([ "native-docs.js", "native-doc-forms.js", "native-block-file.js", + "native-display.js", "native-wonga-tools.js", "native-estimate-print.js", "native-xml-check.js",