// 원본 「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}건| 코드 | 품명 | 규격 | 단위 | EST 금액 | Excel 금액 | 차이 |
${c.result.map((r) => `| ${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)} |
`).join("")}
`,
);
}
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 };
})();