diff --git a/resources/tester/test_est_wonga_tools.py b/resources/tester/test_est_wonga_tools.py index 202c54af8..4175df2f8 100644 --- a/resources/tester/test_est_wonga_tools.py +++ b/resources/tester/test_est_wonga_tools.py @@ -148,3 +148,21 @@ def test_position_sets_row_digits_like_original(): assert result["input"]["Cost"] == 1234600 and result["input"]["TC"] == 1234567 assert result["input"]["Diget"] == 2 and result["input"]["DigetIndex"] == 1 assert result["last"] == 2 + + +def test_abbreviate_formula_like_original_sname_show(): + result = run_js( + r"""(() => { + const rows = source.tables.Wonga, a = EstWongaTools.abbreviate(rows, source.options); + const by = (g) => a[rows.findIndex((r) => r.Gubun === g)]; + const nf = EstWongaTools.abbreviate([{ Index: 1, NumStr: '1', Name: '직 접 노 무 비' }, + { NumStr: '2', VSusic: '1 × 0.05', Youl: '5%' }], { VerFormat: false }); + return { five: by('5'), c: by('C'), e: by('E'), empty: a[0], nf: nf[1], same: rows.every((r, i) => r.VSusic === source.tables.Wonga[i].VSusic) }; + })()""" + ) + assert result["five"] == "직.노 × 16.5%" + assert result["c"] == "( 산출경비 ~ 18 )" + assert result["e"] == "순.공 × 8%" + assert result["empty"] == "" + assert result["nf"] == "직접노무비 × 5%" + assert result["same"] is True diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 08bf91248..1b15084db 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] 원가계산서 산출근거 약어로 표시(원본 popupSNameShow 0xd0e494 · 약어 생성 0xc97790) — 원가계산서(W) 메뉴 단추로 켜고 끔. 산출근거 식의 기호(영문 시작 또는 2자 이하, 범위 기호 뒤 제외)를 그 행 약어(SName, 없으면 빈칸 뺀 이름)로, 숫자×100 이 그 행 요율이면 요율 글자로, 「:」 는 「~」 로 바꿔 화면·인쇄에 보이고 그동안 산출근거 칸 잠금. 저장 값(VSusic)은 그대로. 기성 원가 화면의 같은 확인란과 같은 설정. + - 검증완료: `test_est_wonga_tools.py` 5개(샘플 「직.노 × 16.5%」·「( 산출경비 ~ 18 )」·「순.공 × 8%」, 번호 열쇠, 원본 식 불변) 통과. ORCA 브라우저에서 켜기 → 약어 표시·입력칸 0, 끄기 → 입력칸 31, 설정 지워 복구. - [x] 발췌근거(원본 barbtnColumn — TfrmHeaderChange 0x110b7ec · 자재 TfrmStuffcHeadChange 0xf61b84) — 자재·노임 화면 「발췌근거」 단추로 출처 열(Cost1~6) 이름·쪽수(자재)·보기 편집, 자재는 8가지 출처 이름에서 고르고 중복 거부, 숨긴 출처는 최저·평균·최고 단가 고르기에서 빠짐(서버 재계산도 같은 코드). 값은 계산 환경 `PriceSources`(ESTX 저장에는 남지 않음 — 원본도 출처 이름은 격자 배치에만 둠). - 검증완료: `test_est_sources.py`(최저 80→100·최고 120→100, 중복 거부, 머리글 「물가자료 (120)」, 숨긴 열 빠짐) 및 REA 대조 39개 통과. ORCA 브라우저에서 자재단가 발췌근거 2번 「물가자료」 쪽 77, 3번 숨김 → 머리글 반영·도급액 불변, 설정 지워 복구. - [x] 조달청 표준공사코드 적용(원본 mnuCodeChange → TfrmCodeChange — 디스어셈블 근거) — 작업데이타 7종 탭(일위·단산·기계·표준·견적·자재·노임), 코드변경(목록 코드·직접 입력, 기계 16자리면 끝 0), 검사 색(EST 코드 노랑·다른 표 중복 연분홍+비고·자리수 진황), 변경취소, 적용(자기 표·본문 머리 Code/TitleCode·참조 행 LinkIndex=종류·Index≤0 만, 재계산 없음), 코드일괄변경(고른 .estx 의 같은 표에서 자동연결 맞춤 규칙, EST→비EST 만·중복 금지, 표준은 옛 코드 부분일치), 자동변경(표준 행 번호 구간 C·A·M), 다산코드정리, 사용자정의코드, 목록·작업데이타 검색(& 두 조각). 조달청 목록은 원본 설치의 PPS_STUFFC.dat·PPS_HEAD.dat 를 사용자가 골라 불러옴(내장 자료 없음, `/api/pps-table`). 원본의 분류 트리(PPS_*TYPE)·다산정리 PkID 쓰기는 하지 않음. diff --git a/tools/est-test/README.md b/tools/est-test/README.md index 334a59e19..c7a665023 100644 --- a/tools/est-test/README.md +++ b/tools/est-test/README.md @@ -76,6 +76,8 @@ 편집(E) › `조달청 표준공사코드 적용`은 노임·자재·기계·일위 등의 코드를 조달청 코드로 바꿉니다. 왼쪽 목록은 원본 설치 폴더 `System\MainSys` 의 `PPS_STUFFC.dat`(노임·자재·장비) 또는 `PPS_HEAD.dat`(일위·단산·표준)를 골라 불러옵니다. 코드를 바꾼 뒤 `적용`을 누르면 그 코드를 쓰는 내역·일위·단산 줄도 함께 바뀝니다. `코드일괄변경`은 고른 .estx 파일(예: 원본 JodalCodeDB.estx)에서 품명·규격이 같은 코드를 찾아 EST 코드만 바꿉니다. +원가계산서(W) › `산출근거 약어로 표시`를 누르면 산출근거가 「직.노 × 16.5%」처럼 비목 약어와 요율로 보이고(인쇄 포함), 다시 누르면 원래 식으로 돌아옵니다. 약어로 보이는 동안에는 산출근거를 고칠 수 없습니다. + 자재단가·노임단가 화면의 `발췌근거` 단추로 가격 출처 열(출처1~6)의 이름과 쪽수를 바꾸거나 숨길 수 있습니다. 숨긴 출처는 최저·평균·최고 단가를 고를 때 빠집니다. 원본의 엑셀형 수량산출서(본문이 옛 Excel .xls)는 가져올 때 셀 값과 수식을 함께 읽어 수량산출 화면 아래 격자로 보입니다. 셀을 누르면 수식줄에서 값이나 `=수식`을 고치고 바로 다시 계산합니다(참조·범위·사칙·SUM·ROUND 등). Shift 로 범위를 골라 행·열 넣기·지우기, 잘라내기·복사·붙여넣기, 굵게·정렬·병합·대각선을 쓸 수 있고, 수식의 참조는 원본처럼 따라 옮겨집니다. `우측·위·좌측·아래 합`은 원본처럼 그 방향의 숫자 셀을 더하는 SUM 을 넣고, `수량으로 지정`은 셀을 수량 항목에 연결해 항목 수량이 그 셀 값(산출서 자리수)을 따르게 합니다. 원본 파일에는 셀과 항목의 대응이 보이지 않아, 가져올 때 저장 수량과 값이 같은 합계 셀을 「추정 연결」로 둡니다. 셀을 고친 엑셀형 산출서는 ESTX 저장을 거부합니다. diff --git a/tools/est-test/index.html b/tools/est-test/index.html index 06b215741..c7c1f77ed 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -208,7 +208,8 @@ + >
diff --git a/tools/est-test/native-forms.js b/tools/est-test/native-forms.js index 559b5a666..793081571 100644 --- a/tools/est-test/native-forms.js +++ b/tools/est-test/native-forms.js @@ -266,6 +266,15 @@ const EstForms = (() => { columns = [...common, "GN", "TU", "TC", "NC", "GC", "JC", "SC"]; } } + if ( + table === "Wonga" && + project.options.WongaSNameShow && + typeof EstWongaTools !== "undefined" + ) { + const rows = project.tables.Wonga || [], + abbr = EstWongaTools.abbreviate(rows, project.options); + return { table, columns, rows: rows.map((row, i) => ({ ...row, VSusic: abbr[i] })) }; + } return { table, columns, rows: project.tables[table] || [] }; } const totals = { diff --git a/tools/est-test/native-views.js b/tools/est-test/native-views.js index 65fe81ced..737e550fa 100644 --- a/tools/est-test/native-views.js +++ b/tools/est-test/native-views.js @@ -76,7 +76,13 @@ const EstViews = (() => { } function grid(table, list, columns) { const { esc, number, state } = EstApp; - return `${columns.map((key) => ``).join("")}${list.map(({ row, index }) => ` 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}">${columns.map((key) => ``).join("")}`).join("")}
선택${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || (typeof EstSources !== "undefined" && EstSources.label(state.project.options, table, key)) || EstModel.labels[key] || key)}
0 ? ` style="padding-left:${Number(row.LeftSpace) * 6}px"` : ""}>${cell(table, index, key, row[key])}
`; + const abbr = + table === "Wonga" && + state.project.options.WongaSNameShow && + typeof EstWongaTools !== "undefined" + ? EstWongaTools.abbreviate(state.project.tables.Wonga || [], state.project.options) + : null; + return `${columns.map((key) => ``).join("")}${list.map(({ row, index }) => ` 0 ? "heading " : ""}${Number(row.Level) > 0 ? "level " : ""}${row.Error ? "error-row" : ""}">${columns.map((key) => ``).join("")}`).join("")}
선택${esc((table === "Wonga" && typeof EstJungSang !== "undefined" && EstJungSang.label(state.project, key)) || (typeof EstSources !== "undefined" && EstSources.label(state.project.options, table, key)) || EstModel.labels[key] || key)}
0 ? ` style="padding-left:${Number(row.LeftSpace) * 6}px"` : ""}>${abbr && key === "VSusic" ? `${esc(abbr[index])}` : cell(table, index, key, row[key])}
`; } function render(state) { const { project, view } = state, diff --git a/tools/est-test/native-wonga-tools.js b/tools/est-test/native-wonga-tools.js index d9196572d..cb144ac48 100644 --- a/tools/est-test/native-wonga-tools.js +++ b/tools/est-test/native-wonga-tools.js @@ -241,12 +241,59 @@ const EstWongaTools = (() => { ); } + // 산출근거 약어로 표시(popupSNameShow 0xd0e494 · 약어 생성 0xc97790): 산출근거 식의 기호(영문 시작 또는 2자 이하, + // 범위 기호 뒤 제외)를 그 행의 약어(SName, 없으면 빈칸 뺀 이름)로, 숫자×100 이 그 행 요율과 같으면 요율 글자로, 「:」 는 「~」 로. + // 화면·인쇄에서 산출근거 대신 보이고 그동안 산출근거 칸은 잠근다(원본 VGungu). 저장 값은 바꾸지 않는다. + function abbreviate(rows, options = {}) { + const key = (r) => + String(options.VerFormat === false ? (r.NumStr ?? r.Index ?? "") : (r.Gubun ?? "")).trim(); + const keys = new Map(); + rows.forEach((r) => { + const k = key(r); + if (k && !keys.has(k)) keys.set(k, r); + }); + const label = (r) => String(r.SName || r.Name || "").replace(/\s/g, ""); + return rows.map((row) => { + const s = String(row.VSusic ?? "").trim(); + if (!s) return ""; + const youl = String(row.Youl ?? "") + .replace("%", "") + .trim(); + const tokens = s.match(/\d+(?:\.\d+)?|[A-Za-z가-힣_][A-Za-z0-9가-힣_]*|\S/g) || []; + return tokens + .map((t, i) => { + const prev = tokens[i - 1]; + if (t === ":") return "~"; + const numeric = /^\d+(?:\.\d+)?$/.test(t); + if ( + (/^[A-Z]/.test(t) || (t.length <= 2 && /^[A-Za-z0-9가-힣]+$/.test(t))) && + prev !== ":" && + prev !== "~" + ) { + const hit = keys.get(t); + if (hit) return label(hit); + } + if (numeric && youl && Number(youl) === Number((Number(t) * 100).toPrecision(12))) + return String(row.Youl).trim(); + return t; + }) + .join(" "); + }); + } + function handle(name, state) { + if (name === "wonga-sname") { + EstApp.change( + (project) => (project.options.WongaSNameShow = !project.options.WongaSNameShow), + false, + ); + return true; + } if (name === "wonga-gita") dialog(state, "gita"); else if (name === "wonga-anjun") dialog(state, "anjun"); else return false; return true; } - return { gita, anjun, anjunBase, position, positionDialog, handle }; + return { gita, anjun, anjunBase, position, positionDialog, abbreviate, handle }; })();