feat(est-test): 표시 설정(비목순서·구분 표시·품명정렬)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 13:08:24 +09:00
co-authored by Claude Opus 5.5
parent 8be1ff6135
commit 684e0e7684
7 changed files with 175 additions and 9 deletions
+70
View File
@@ -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"]
+2
View File
@@ -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 — 원본 실행 파일에 코드가 링크되지 않아 열 수 없음).
+2
View File
@@ -37,6 +37,7 @@
<script defer src="native-views.js"></script>
<script defer src="native-extra.js"></script>
<script defer src="native-settings.js"></script>
<script defer src="native-display.js"></script>
<script defer src="native-report.js"></script>
<script defer src="native-forms.js"></script>
<script defer src="native-exchange.js"></script>
@@ -207,6 +208,7 @@
<button data-view="Wonga">원가계산서 보기</button
><button data-action="template">원가 서식 선택</button
><button data-action="settings">자릿수·도급액 고정</button
><button data-action="display-options">표시 설정(비목순서·구분·품명정렬)</button
><button data-action="options-save-default">기본환경으로 설정</button
><button data-action="reset-wonga">원가계산서 초기화</button>
<button data-action="wonga-forms">원가계산서 양식 목록·등록</button>
+2 -1
View File
@@ -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") {
+85
View File
@@ -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 };
})();
+13 -8
View File
@@ -72,7 +72,11 @@ const EstViews = (() => {
if (value && typeof value === "object") return `<span class="muted">바이너리 자료</span>`;
if (EstModel.numeric.has(key))
return `<input ${attrs} inputmode="decimal" type="number" step="any" value="${value ?? ""}">`;
return `<input ${attrs} type="text" value="${esc(value)}" maxlength="10000">`;
const align =
key === "Name" && typeof EstDisplay !== "undefined"
? EstDisplay.nameStyle(EstApp.state.project.options)
: "";
return `<input ${attrs} type="text" value="${esc(value)}" maxlength="10000"${align ? ` style="${align}"` : ""}>`;
}
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 =
+1
View File
@@ -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",