diff --git a/resources/tester/test_est_jdl_compare.py b/resources/tester/test_est_jdl_compare.py new file mode 100644 index 000000000..df53faf60 --- /dev/null +++ b/resources/tester/test_est_jdl_compare.py @@ -0,0 +1,32 @@ +"""EST 테스트 앱 JDLExcel과 단가비교(원본 ActionJDLCompare — Excel 코드 칸이 코드를 품으면 같은 행 합계와 TC 비교).""" + +import json +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def test_compare_contains_code_limit_and_xls_tables(): + code = r""" + const fs = require('fs'), vm = require('vm'); + const ctx = vm.createContext({}); + vm.runInContext(fs.readFileSync('tools/est-test/native-jdl-compare.js', 'utf8'), ctx); + const J = vm.runInContext('EstJdlCompare', ctx); + const rows = [{ Code: 'U001', Name: '가', TC: 1000 }, { Code: 'U002', Name: '나', TC: 500 }, + { Code: 'U003', Name: '다', TC: 7 }, { Code: '', Name: '머리' }]; + const table = [['코드', '', '합계'], ['[U001] 가', '', '1,000'], ['U002', '', 499]]; + const out = J.compare(rows, table, 0, 2).map((r) => [r.Code, r.OTC, r.GN, r.Checked]); + const far = Array.from({ length: 60 }, () => ['x']); far.push(['U009', '', 5]); + const limit = J.compare([{ Code: 'U009', TC: 5 }], far, 0, 2)[0].OTC; + const xls = J.xlsTables([{ name: 'S1', cells: { '0,0': { value: 'U001' }, '0,7': { value: 1000 }, '2,1': { value: 'b' } } }]); + process.stdout.write(JSON.stringify({ out, limit, xls: xls.S1 })); + """ + out = json.loads(subprocess.check_output(["node", "-e", code], cwd=ROOT).decode("utf-8")) + assert out["out"] == [ + ["U001", 1000, 0, False], + ["U002", 499, 1, True], + ["U003", None, None, True], + ] + assert out["limit"] is None + assert out["xls"][0][0] == "U001" and out["xls"][0][7] == 1000 and out["xls"][2][1] == "b" diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 72194e9a8..c61170313 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -489,6 +489,9 @@ 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] JDLExcel과 단가비교(원본 ActionJDLCompare, 숨은 Ctrl+Alt+J → TfrmJDLExcelCompare) — 일위대가·단가산출·기계경비 탭, 시트번호·코드칼럼(A)·합계셀칼럼(H), Excel(.xlsx·.xls — `/api/read-xls`) 1행~(호표 수+50)행에서 코드를 품은 칸의 같은 행 합계와 TC 비교, 차이·없음 표시. 보기 전용. 웹은 메뉴 단추로 엶. + - 검증완료: `test_est_jdl_compare.py`(같음·차이 1·없음·+50행 한계·xls 셀 변환) 통과. ORCA 브라우저에서 일위대가 8개 중 같음 1·차이 10·없음 6 표시. + - 제외: TfrmMultiEditor(인쇄 부품의 설계용 편집기 — 실행 파일에 클래스 없음), Est Plus(구버전 .est) 단가비교·내보내기(숨은 단축키, 394바이트 레코드 형식 — 구버전 불러오기와 함께 남음). - [x] 검색 결과 근거자료보기(원본 TfrmFoundDetail btnClose 0xefa7e8) — 통합 검색 결과의 일위·단산·기계 줄 「근거」: 그 작업의 호표 세부 줄(단산은 산출근거 글)과 총액·노무·재료·경비, 「적용」은 그 결과를 고르고 검색 창으로(가져오기는 검색 창에서). 고른 결과는 창을 오가도 유지. - 검증완료: 바인딩·통합 검색 시험 통과. ORCA 브라우저에서 「거푸집」 일위 → 「[합판거푸집]의 근거자료보기」 총액 53,029·노무 38,289·재료 14,740·세부 6줄, 적용 → 그 결과 체크. - [x] ES 정보(원본 mnuESInfo → TfrmESInfo 0x10b838c) — 기준시점·비교시점·최종기성일·선급금지급일(기본 오늘)·선급금지급액을 `project.esInfo` 에 기록(원본 [EscInfo] SDATE·CDATE·ACDATE·PreDATE·PreCost). 원본도 ES 제출 파일 작성에만 씀 — 웹 물가변동 계산과 잇지 않음, ESTX 에는 남지 않음. diff --git a/tools/est-test/index.html b/tools/est-test/index.html index d4c1096a6..6b762096e 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -62,6 +62,7 @@ + @@ -247,6 +248,7 @@ > diff --git a/tools/est-test/native-business-actions.js b/tools/est-test/native-business-actions.js index 74f69f46f..25191017d 100644 --- a/tools/est-test/native-business-actions.js +++ b/tools/est-test/native-business-actions.js @@ -633,7 +633,8 @@ const EstBusinessActions = (() => { (typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) || (typeof EstCellClip !== "undefined" && EstCellClip.handle(name, state)) || (typeof EstRowLinks !== "undefined" && EstRowLinks.handle(name, state)) || - (typeof EstEsInfo !== "undefined" && EstEsInfo.handle(name, state)) + (typeof EstEsInfo !== "undefined" && EstEsInfo.handle(name, state)) || + (typeof EstJdlCompare !== "undefined" && EstJdlCompare.handle(name, state, node)) ) { EstApp.render(); } else if (name === "insert-annual") { diff --git a/tools/est-test/native-excel-map.js b/tools/est-test/native-excel-map.js index 8ef890712..1d0713238 100644 --- a/tools/est-test/native-excel-map.js +++ b/tools/est-test/native-excel-map.js @@ -299,5 +299,16 @@ const EstExcelMap = (() => { return true; } - return { fields, columnIndex, columnName, records, rows, insert, load, shift, handle }; + return { + fields, + columnIndex, + columnName, + records, + rows, + insert, + load, + shift, + sheetTables, + handle, + }; })(); diff --git a/tools/est-test/native-jdl-compare.js b/tools/est-test/native-jdl-compare.js new file mode 100644 index 000000000..c809143e0 --- /dev/null +++ b/tools/est-test/native-jdl-compare.js @@ -0,0 +1,124 @@ +// 원본 「JDLExcel과 단가비교」(ActionJDLCompare, 숨은 단축키 Ctrl+Alt+J → TfrmJDLExcelCompare) — 일위대가·단가산출·기계경비 호표마다 +// Excel 의 코드 열에서 그 코드를 품은 셀(Pos(코드, 셀) > 0)을 1행~(호표 수+50)행 안에서 찾아 같은 행 합계 열 값과 TC 를 비교한다. +// 차이(|TC − Excel|)가 있으면 표시, 못 찾으면 「없음」. 자료는 바꾸지 않는다(보기 전용). Excel 은 .xlsx·.xls 모두. +const EstJdlCompare = (() => { + const TABLES = [ + ["HeadTitleDat", "일위대가"], + ["CostTitleDat", "단가산출"], + ["MachTitleDat", "기계경비"], + ]; + function compare(rows, table, codeCol, totalCol) { + const limit = rows.length + 50; + return rows + .filter((r) => String(r.Code ?? "").trim()) + .map((r) => { + const code = String(r.Code).trim(); + let found = null; + for (let i = 0; i < Math.min(limit, table.length); i++) { + const cell = table[i]?.[codeCol]; + if (cell != null && String(cell).includes(code)) { + found = Number(String(table[i][totalCol] ?? "").replace(/,/g, "")) || 0; + break; + } + } + const tc = Number(r.TC) || 0; + return { + Code: code, + Name: r.Name ?? "", + Size: r.Size ?? "", + Unite: r.Unite ?? "", + TC: tc, + OTC: found, + GN: found == null ? null : Math.abs(tc - found), + Checked: found == null || Math.abs(tc - found) > 1e-9, + }; + }); + } + // .xls(BIFF) 셀 → 행 배열 + function xlsTables(sheets) { + const out = {}; + for (const s of sheets) { + const table = []; + for (const [k, c] of Object.entries(s.cells)) { + const [r, col] = k.split(",").map(Number); + (table[r] ||= [])[col] = c.value; + } + out[s.name] = table; + } + return out; + } + + function form(state) { + const c = (state.jdlCompare ||= { + tab: 0, + sheet: 1, + code: "A", + total: "H", + tables: null, + result: [], + }), + { esc, number } = EstApp, + diff = c.result.filter((r) => r.Checked).length; + EstApp.showDialog( + "JDL엑셀 단가 비교", + `
| 코드 | 품명 | 규격 | 단위 | EST 금액 | Excel 금액 | 차이 |
|---|---|---|---|---|---|---|
| ${esc(r.Code)} | ${esc(r.Name)} | ${esc(r.Size)} | ${esc(r.Unite)} | ${number(r.TC)} | ${r.OTC == null ? "없음" : number(r.OTC)} | ${r.GN == null ? "" : number(r.GN)} |