From 5270eb335f38f740b5934dd553dcbfda5ce63850 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 18:03:42 +0900 Subject: [PATCH] =?UTF-8?q?feat(est-test):=20=EC=97=B0=EC=B0=A8=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=EC=84=9C=C2=B7=EC=83=81=EB=8B=A8=C2=B7?= =?UTF-8?q?=ED=95=98=EB=8B=A8=EB=8D=B0=EC=9D=B4=ED=83=80=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=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 --- resources/tester/helper_est_native.cjs | 1 + .../tester/test_est_progress_workflow.py | 18 +++++ tools/est-test/PLAN.md | 2 + tools/est-test/index.html | 1 + tools/est-test/native-annual-load.js | 74 +++++++++++++++++++ tools/est-test/native-business-actions.js | 3 +- tools/est-test/native-business.js | 1 + tools/est-test/native-extra.js | 9 +++ tools/est-test/server.cjs | 1 + 9 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tools/est-test/native-annual-load.js diff --git a/resources/tester/helper_est_native.cjs b/resources/tester/helper_est_native.cjs index c1b0fb606..9b5f65fc9 100644 --- a/resources/tester/helper_est_native.cjs +++ b/resources/tester/helper_est_native.cjs @@ -15,6 +15,7 @@ for (const file of [ "native-row-match.js", "native-escalation.js", "native-progress.js", + "native-annual-load.js", "native-business.js", "native-review.js", "native-resources.js", diff --git a/resources/tester/test_est_progress_workflow.py b/resources/tester/test_est_progress_workflow.py index ae94c9051..431afafe7 100644 --- a/resources/tester/test_est_progress_workflow.py +++ b/resources/tester/test_est_progress_workflow.py @@ -202,3 +202,21 @@ def test_progress_preserves_prior_costs_for_multiple_blank_codes(): assert result["errors"] == [] assert [row["JunCost"] for row in result["costs"]] == [200, 100] assert [row["TotalCost"] for row in result["costs"]] == [500, 250] + + +def test_annual_loaded_rows_replace_formula(): + result = run( + "math", + """(()=>{ + const p=EstNative.empty();p.options.SourceRefresh=false; + p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{PkID:2,Code:'S',GN:4,NU:50},{LinkIndex:54}]; + const loaded=[{PkID:1,Code:'R',GN:3,GU:100,GC:300,TC:300},{PkID:2,Code:'S',GN:2,NU:50,NC:100,TC:100}]; + p.annual=[{name:'1차',formula:'다른공사',rows:loaded}]; + const out=EstBusiness.annual(EstNative.calculate(p)); + const rows=out.results[0].project.tables.EstMainDat; + return {gn:rows.slice(0,2).map(r=>r.GN), tc:out.results[0].project.calculation.direct.TC, errors:out.errors}; + })()""", + ) + assert result["gn"] == [3, 2] + assert result["tc"] == 400 + assert result["errors"] == [] diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 970b7c90a..37ed2916b 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -489,6 +489,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] 연차 내역서·상단·하단데이타 불러오기(원본 TFRMESTMULTIEDIT popLoadEst·popUpDataLoad·popDownLoad 0xedb784 → 0xf11a60 — spec_part §7) — 차수 줄의 세 단추: 고른 .estx 내역서 줄을 차수에 짝지어 옮기고(행 수는 현재 내역), 수식칸에 파일 이름. 상단·하단은 설계변경 내역만(당초 「_」 칸 있는 것), 상단 = 당초값·하단 = 변경값. 그 차수의 상세 줄은 수식 대신 불러온 값(금액 고정, 다시 셈 없음). 서버 계산도 같은 코드(`native-annual-load.js`). + - 검증완료: 기성·연차 시험 13개(불러온 줄 수량 3·2·직접공사비 400, 수식 무시) 통과. 당초/변경 고르기·설계변경 아닌 파일 거부 확인. ORCA 브라우저에서 차수 줄에 불러오기 단추 3개 표시(파일 선택은 수동 조작 필요). - [x] 현재데이타 갱신(원본 TFRMDATAUPDATE btnApply 0x10bc3d4 → 엔진 0x10e12c8 — spec_update) — 원천: Main Data(원본 설치 main.estx = BufDat\Main\Main_1, 서버 `/api/main-data`, `EST_MAIN_DATA` 로 경로 변경) 또는 고른 .estx. 항목: 노임·자재(유류 제외)·유류(이름 8종)·표준(코드 일치)·기계(손료계수·환율 표 교체, 기계 호표 이름·규격 일치, 산출근거 줄 교체 뒤 자재·노임 다시 잇기)·환율(36·89·69·77·70·76). 맞춤 규칙·복사 칸·단가미갱신 표시·안내 문구 원본대로. 행 추가·삭제 없음. 갱신 뒤 재계산. 하지 않음: 최신(웹접속 — 원본 회사 서버·유지보수 인증), 자재 단가열 출처 재배치(OtherTables — 같은 열 순서 가정), 원본 결함(환율 끔 때 파운드를 76 값으로 덮음). - 검증완료: `test_est_data_update.py`(노임 갱신·당초 칸 유지·못 찾음 표시와 문구, 자재 규격·단위 정리 맞춤·쪽수, 자재만 갱신 때 유류·비율 제외, 표준 코드 맞춤·이름 교체, 손료 계수 표 교체·당초 칸 유지, 환율 끔 유지) 통과. 샘플을 원본 main.estx 로 갱신하면 보통인부 172,068→172,698(2026 하반기)·못 찾음 0(145ms). ORCA 브라우저에서 같은 결과, 샘플 복구. - [x] 견적서 엑셀변환(원본 TFRMEXCELCONVERTESTIPRN btnConvert) — 견적서 창 「엑셀변환」: 머리(제목·공사명·귀하·견적일자·합계금액 한글·공급자)와 레벨에 맞춘 내역 줄(품명·규격·단위·수량·재료/노무/경비/합계 단가·금액·비고)을 .xlsx 로 내려받기. 원본의 인쇄비율·셀 수식은 하지 않음(값만). diff --git a/tools/est-test/index.html b/tools/est-test/index.html index 43f5f8115..29f6f57cd 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -22,6 +22,7 @@ + diff --git a/tools/est-test/native-annual-load.js b/tools/est-test/native-annual-load.js new file mode 100644 index 000000000..17d4c1460 --- /dev/null +++ b/tools/est-test/native-annual-load.js @@ -0,0 +1,74 @@ +// 원본 연차(다차수) 「내역서데이타 불러오기」·「상단데이타 불러오기」·「하단데이타 불러오기」(TFRMESTMULTIEDIT popLoadEst 0xedb784 → +// 0xf11a60 — spec_part §7): 고른 .estx 의 내역서 줄을 차수 열에 짝지어 옮기고(행 수는 현재 내역 그대로), 차수 수식칸에 파일 이름을 적는다. +// 상단·하단은 설계변경 내역만 받으며 상단 = 당초값(「_」 짝 칸)·하단 = 변경값(원본 옵션 +0x37 = 0 기준). 금액은 파일 값 그대로(다시 셈 없음). +const EstAnnualLoad = (() => { + const num = (v) => Number(v) || 0; + const ORIGINAL = [ + "Name", + "Size", + "Unite", + "GN", + "TU", + "TC", + "NU", + "NC", + "GU", + "GC", + "JU", + "JC", + "SU", + "SC", + "Bigo", + ]; + // 차수 계산에서 불러온 줄을 쓰면 true + function row(year, index, target) { + const loaded = year.rows?.[index]; + if (!loaded) return false; + Object.assign(target, structuredClone(loaded)); + target.CalcIndex = num(loaded.CalcIndex) >= 2 ? 3 : 1; + return true; + } + function rows(source, tag) { + const list = structuredClone(source.tables?.EstMainDat || []); + if (!list.length) throw Error("내역서 데이타를 찾을수 없습니다."); + if (tag) { + const changed = list.some((r) => + Object.entries(r).some(([k, v]) => k.endsWith("_") && v != null && v !== ""), + ); + if (!changed) throw Error("설계변경을 한 내역서가 아닙니다."); + if (tag === 1) + for (const r of list) for (const k of ORIGINAL) if (`${k}_` in r) r[k] = r[`${k}_`]; + } + return list; + } + async function load(state, index, tag) { + const file = await new Promise((resolve) => { + const picker = document.createElement("input"); + picker.type = "file"; + picker.accept = ".estx"; + picker.addEventListener("change", () => resolve(picker.files[0])); + picker.click(); + }); + if (!file) return; + const source = await EstApp.api(`/api/import?name=${encodeURIComponent(file.name)}`, { + method: "POST", + body: await file.arrayBuffer(), + }); + const list = rows(source, tag); + EstApp.change((project) => { + const year = project.annual[index]; + year.rows = list.slice(0, project.tables.EstMainDat.length); + year.formula = file.name.replace(/\.estx$/i, ""); + }, false); + state.notice = `「${file.name}」 내역서 ${list.length}줄을 차수에 불러왔습니다.`; + } + function handle(name, state, node) { + if (name !== "annual-load") return false; + load(state, Number(node.dataset.row), Number(node.dataset.tag)).catch((error) => { + state.notice = error.message; + EstApp.render(); + }); + return true; + } + return { row, rows, handle }; +})(); diff --git a/tools/est-test/native-business-actions.js b/tools/est-test/native-business-actions.js index bdcce47e8..a2c660209 100644 --- a/tools/est-test/native-business-actions.js +++ b/tools/est-test/native-business-actions.js @@ -638,7 +638,8 @@ const EstBusinessActions = (() => { (typeof EstGlobalVars !== "undefined" && EstGlobalVars.handle(name, state, node)) || (typeof EstNewEstimate !== "undefined" && EstNewEstimate.handle(name, state)) || (typeof EstBook !== "undefined" && EstBook.handle(name, state, node)) || - (typeof EstDataUpdate !== "undefined" && EstDataUpdate.handle(name, state)) + (typeof EstDataUpdate !== "undefined" && EstDataUpdate.handle(name, state)) || + (typeof EstAnnualLoad !== "undefined" && EstAnnualLoad.handle(name, state, node)) ) { EstApp.render(); } else if (name === "insert-annual") { diff --git a/tools/est-test/native-business.js b/tools/est-test/native-business.js index 86a3f0bda..31eb0fb42 100644 --- a/tools/est-test/native-business.js +++ b/tools/est-test/native-business.js @@ -632,6 +632,7 @@ const EstBusiness = (() => { row.LinkIndex = 99; } if (num(row.Level) > 0 || [54, 55, 58, 59, 62, 67].includes(num(row.LinkIndex))) continue; + if (typeof EstAnnualLoad !== "undefined" && EstAnnualLoad.row(year, index, row)) continue; try { // f0f9b8/f1098c resolve the same column formula against each current field. const value = (field) => diff --git a/tools/est-test/native-extra.js b/tools/est-test/native-extra.js index 7f1ef4ca4..30ede036e 100644 --- a/tools/est-test/native-extra.js +++ b/tools/est-test/native-extra.js @@ -329,6 +329,15 @@ const EstExtra = (() => { `data-row="${index}" data-direction="1" ${index === project.annual.length - 1 ? "disabled" : ""}`, ) + button("insert-annual", "위에 삽입", `data-row="${index}"`) + + ["내역서", "상단", "하단"] + .map((l, t) => + button( + "annual-load", + `${l}데이타 불러오기`, + `data-row="${index}" data-tag="${t}"`, + ), + ) + .join("") + button("delete-extra", "삭제", `data-section="annual" data-row="${index}"`), ]), ), diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index 9a171af68..248e3fb1a 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -36,6 +36,7 @@ const scripts = [ "native-row-match.js", "native-escalation.js", "native-progress.js", + "native-annual-load.js", "native-business.js", "native-review.js", "native-resources.js",