125 lines
5.5 KiB
JavaScript
125 lines
5.5 KiB
JavaScript
// 원본 「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엑셀 단가 비교",
|
||
`<div class="business-tools">${TABLES.map(([, l], i) => `<button data-action="jdl-compare-tab" data-i="${i}"${i === c.tab ? ' style="font-weight:bold"' : ""}>${l}</button>`).join("")}</div><div class="form-fields"><label>시트번호<input name="jc-sheet" type="number" min="1" value="${c.sheet}"></label><label>코드칼럼<input name="jc-code" value="${esc(c.code)}" size="3"></label><label>합계셀칼럼<input name="jc-total" value="${esc(c.total)}" size="3"></label></div><button data-action="jdl-compare-open">엑셀파일열기</button> <button data-action="jdl-compare-run">검 색</button> ${c.tables ? `시트 ${Object.keys(c.tables).length}개` : "Excel 을 여세요"} · 다른 금액·없음 ${diff}건<div style="max-height:380px;overflow:auto"><table class="sheet"><thead><tr><th>코드</th><th>품명</th><th>규격</th><th>단위</th><th>EST 금액</th><th>Excel 금액</th><th>차이</th></tr></thead><tbody>${c.result.map((r) => `<tr${r.Checked ? ' style="background:#fde2e2"' : ""}><td>${esc(r.Code)}</td><td style="text-align:left">${esc(r.Name)}</td><td>${esc(r.Size)}</td><td>${esc(r.Unite)}</td><td>${number(r.TC)}</td><td>${r.OTC == null ? "없음" : number(r.OTC)}</td><td>${r.GN == null ? "" : number(r.GN)}</td></tr>`).join("")}</tbody></table></div>`,
|
||
);
|
||
}
|
||
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 };
|
||
})();
|