diff --git a/tools/est-test/README.md b/tools/est-test/README.md index 9c8aedc0a..37887e603 100644 --- a/tools/est-test/README.md +++ b/tools/est-test/README.md @@ -4,6 +4,8 @@ 내역·일위대가·수식 단가산출·기계경비·기초 단가·원가계산서를 원본 테이블 구조로 편집합니다. 수량 연결, 설계변경 당초 보존, 낙찰률, 기성 회차와 사정 누계, 품목조정, 연차 배분, 소요 자원 전개 화면을 제공합니다. 실행 취소·다시 실행, 검색, 행 편집, JSON 저장·열기, CSV와 브라우저 인쇄를 사용할 수 있습니다. +인쇄 미리보기는 화면의 100행 페이지 제한 없이 검색 결과/현재 호표의 전체 행을 표시합니다. 출력 범위에서 현재 자료의 모든 호표·행도 선택할 수 있습니다. 브라우저 인쇄 창에서 종이 출력이나 PDF 저장을 선택합니다. 원본 인쇄 양식과 같은 레이아웃이라는 의미는 아닙니다. + 저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. ESTX는 읽기 전용으로 가져오며 원본 압축 파일과 바이너리를 보존합니다. 편집 결과를 원본 ESTX로 다시 쓰는 기능은 없습니다. 초기 자료는 기술정보 DB의 실제 REA 저장 샘플입니다. `REA 샘플 대조`에서 원본 저장값과 275개 계산 결과를 비교합니다. 테스트용 임의 요율을 원본 기본값으로 넣지 않습니다. 새 원가 서식의 요율은 사용자가 입력합니다. diff --git a/tools/est-test/index.html b/tools/est-test/index.html index eb1c51b4f..ac8aea017 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -19,6 +19,7 @@ + diff --git a/tools/est-test/native-actions.js b/tools/est-test/native-actions.js index 445241f7f..477a3b202 100644 --- a/tools/est-test/native-actions.js +++ b/tools/est-test/native-actions.js @@ -193,7 +193,8 @@ const EstActions = (() => { if (!selected().length) throw Error("레벨을 바꿀 행을 선택하세요."); const level = Number(node.dataset.level); EstApp.change(() => selected().forEach((i) => (activeRows()[i].Level = level))); - } else if (name === "print") window.print(); + } else if (name === "print") EstReport.open(); + else if (name === "print-document") EstReport.print(); else if (name === "csv") { const rows = activeRows(), keys = EstModel.columns(state.view); diff --git a/tools/est-test/native-report.js b/tools/est-test/native-report.js new file mode 100644 index 000000000..2580a9126 --- /dev/null +++ b/tools/est-test/native-report.js @@ -0,0 +1,100 @@ +const EstReport = (() => { + function preview(all = false) { + const { state, esc } = EstApp; + const number = (value) => + new Intl.NumberFormat("ko-KR", { maximumFractionDigits: 12 }).format(Number(value) || 0); + const root = document.createElement("div"); + if (state.project.tables[state.view]) { + const rows = all + ? state.project.tables[state.view].map((row, index) => ({ row, index })) + : EstApp.visibleRows(); + const common = ["Code", "Name", "Size", "Unite"]; + const columns = { + EstMainDat: [...common, "GN", "TU", "TC", "NC", "GC", "JC", "SC"], + HeadMainDat: [...common, "GN", "NU", "GU", "JU", "SU", "TU", "NC", "GC", "JC", "SC", "TC"], + CostMainDat: ["LineData"], + MachMainDat: [...common, "GN", "TU", "NC", "GC", "JC", "TC"], + Wonga: ["Gubun", "Name", "Youl", "VSusic", "Cost"], + }; + root.innerHTML = EstViews.grid( + state.view, + rows, + columns[state.view] || + EstModel.columns(state.view).filter( + (key) => + ![ + "ValueIndex", + "SelectField", + "Ratio", + "NachalRatio", + "Diget", + "DigetIndex", + ].includes(key), + ), + ); + } else root.innerHTML = EstApp.byId("work-content").innerHTML; + root + .querySelectorAll("button,.row-number,.business-tools,select#quantity-sheet") + .forEach((node) => node.remove()); + root.querySelectorAll("input,textarea,select").forEach((node) => { + const value = + node.tagName === "SELECT" + ? node.selectedOptions[0]?.textContent || "" + : node.type === "number" && node.value !== "" + ? number(node.value) + : EstText.clean(EstText.encode(node.value)); + if (node.tagName === "TEXTAREA") node.parentElement.classList.add("col-LineData"); + if (node.type === "number") node.parentElement.classList.add("numeric"); + node.replaceWith(document.createTextNode(value)); + }); + root + .querySelectorAll("[contenteditable]") + .forEach((node) => node.removeAttribute("contenteditable")); + const title = EstApp.titles[state.view] || state.view; + const detail = + state.view === "quantity" + ? state.project.quantitySheets?.find((sheet) => sheet.Code === state.quantitySheet)?.Name + : state.view === "progress" + ? state.project.progress.find((round) => round.id === state.roundId)?.name + : state.group + ? EstNative.groups(state.project.tables[state.view] || []).get(state.group)?.rows[0] + ?.Name + : ""; + const totals = state.project.calculation; + const errors = totals?.errors || []; + const source = `${esc(state.project.name)} · ${esc(title)} +

${esc(state.project.name)}

${esc(title)}

+ ${detail ? `

${esc(detail)}

` : ""}${totals ? `
순공사비 ${number(totals.direct?.TC)}원 · 도급액 ${number(totals.total)}원
` : ""} + ${root.innerHTML}${errors.length ? `

계산 오류 ${errors.length}건: ${esc(errors.join(" / "))}

` : ""}`; + EstApp.byId("print-frame").srcdoc = source; + } + function open() { + const table = EstApp.state.project.tables[EstApp.state.view]; + EstApp.showDialog( + "인쇄 미리보기", + `
+ ${table ? '' : ""} +
+ `, + ); + EstApp.byId("print-scope")?.addEventListener("change", (event) => + preview(event.target.value === "all"), + ); + preview(); + } + function print() { + const frame = EstApp.byId("print-frame"); + if (!frame?.contentDocument?.body) throw Error("인쇄 미리보기가 준비되지 않았습니다."); + frame.contentWindow.focus(); + frame.contentWindow.print(); + } + return { open, preview, print }; +})(); diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index 65d09a512..fa728e607 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -47,6 +47,7 @@ const allowed = new Set([ "native-actions.js", "native-extra.js", "native-settings.js", + "native-report.js", ...scripts, ]); function reply(res, status, data, type = "application/json; charset=utf-8") {