// 원본 「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엑셀 단가 비교", `
${TABLES.map(([, l], i) => ``).join("")}
${c.tables ? `시트 ${Object.keys(c.tables).length}개` : "Excel 을 여세요"} · 다른 금액·없음 ${diff}건
${c.result.map((r) => ``).join("")}
코드품명규격단위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)}
`, ); } function read(c) { const v = (n) => document.querySelector(`#dialog-content [name="${n}"]`)?.value; c.sheet = Math.max(1, Number(v("jc-sheet")) || 1); c.code = String(v("jc-code") || "A").toUpperCase(); c.total = String(v("jc-total") || "H").toUpperCase(); } async function run(name, state, node) { if (name === "jdl-compare") return form(state); const c = state.jdlCompare; read(c); if (name === "jdl-compare-tab") { c.tab = Number(node.dataset.i); c.result = []; } else if (name === "jdl-compare-open") { const input = document.createElement("input"); input.type = "file"; input.accept = ".xlsx,.xls"; input.addEventListener("change", async () => { try { const file = input.files[0]; if (!file) return; const body = await file.arrayBuffer(); c.tables = /\.xls$/i.test(file.name) ? xlsTables(await EstApp.api("/api/read-xls", { method: "POST", body })) : EstExcelMap.sheetTables( await EstApp.api("/api/import-xlsx", { method: "POST", body }), ); } catch (error) { state.notice = error.message; } form(state); }); return input.click(); } else if (name === "jdl-compare-run") { if (!c.tables) throw Error("엑셀파일을 먼저 여세요."); const table = Object.values(c.tables)[c.sheet - 1]; if (!table) throw Error(`시트 ${c.sheet} 이 없습니다.`); const col = (x) => EstExcelMap.columnIndex(x); c.result = compare( state.project.tables[TABLES[c.tab][0]] || [], table, col(c.code), col(c.total), ); } form(state); } const names = new Set(["jdl-compare", "jdl-compare-tab", "jdl-compare-open", "jdl-compare-run"]); function handle(name, state, node) { if (!names.has(name)) return false; run(name, state, node).catch((error) => { state.notice = error.message; EstApp.render(); }); return true; } return { compare, xlsTables, handle }; })();