// 원본 구버전 EST Plus 자료 3종(spec_misc §2 · spec_estplus.md): 「EstPlus 데이타 불러오기」(mnuFileEstPlusLoad — .est 를 풀어
// 표마다 변환해 새 작업으로), 「Est Plus와 단가비교」(ActionEstCompare Ctrl+Alt+E → TfrmCompare: 비교할 항목 4가지 중 고르고
// 비교파일선택 → 이 공사 머리 행과 구형 파일 Index > 0 레코드를 차례로 짝지어 합계가 다르면 표시, 더블클릭 = 그 자료로 이동),
// 「EstPlus 데이타로 저장」(ActionConvertToEstPlus Ctrl+Shift+F12 → 394 바이트 판 .est). 읽기·쓰기는 서버 legacy-estplus.cjs.
const EstEstPlus = (() => {
const num = (v) => Number(v) || 0;
const KINDS = [
["기계경비", "MachMainDat"],
["단가산출", "CostTitleDat"],
["일위대가", "HeadMainDat"],
["내역서", "EstMainDat"],
];
const pickFile = () =>
new Promise((resolve) => {
const picker = document.createElement("input");
picker.type = "file";
picker.accept = ".est,.ebk";
picker.addEventListener("change", () => resolve(picker.files[0]));
picker.click();
});
// 이 공사 쪽 행(Index > 0)과 구형 값을 차례로 짝지음 · 확인 = 합계 다름(정확 비교)
function pair(project, kind, legacy) {
const [, table] = KINDS[kind],
rows = (project.tables[table] || [])
.map((row, index) => ({ row, index }))
.filter(({ row }) => num(row.Index) > 0);
return rows.map(({ row, index }, i) => {
const o = legacy[i] || { ONU: 0, OGU: 0, OJU: 0 },
mine = num(row.NC) + num(row.GC) + num(row.JC) + num(row.SC);
return {
index,
Index: row.Index,
Code: row.Code,
Name: row.Name || EstText.clean(row.LineData || ""),
Size: row.Size,
Unite: row.Unite,
NC: num(row.NC),
GC: num(row.GC),
JC: num(row.JC),
SC: num(row.SC),
...o,
UsesCheck: mine !== o.ONU + o.OGU + o.OJU,
};
});
}
let compared = { kind: 0, rows: [] };
function draw() {
const { esc, number } = EstApp,
{ kind, rows } = compared;
EstApp.showDialog(
"단가비교",
`
비교할항목 선택${KINDS.map(([label], i) => ``).join("")}${rows.filter((r) => r.UsesCheck).length}
| 확인 | No | 품명 | 규격 | 단위 | 노무비 | 재료비 | 경비 | 실적 | | 노무비 | 재료비 | 경비 |
${rows
.map(
(r) =>
`| ${r.UsesCheck ? "✔" : ""} | ${r.Index} | ${esc(r.Name)} | ${esc(r.Size ?? "")} | ${esc(r.Unite ?? "")} | ${number(r.NC)} | ${number(r.GC)} | ${number(r.JC)} | ${number(r.SC)} | | ${number(r.ONU)} | ${number(r.OGU)} | ${number(r.OJU)} |
`,
)
.join("")}
`,
);
}
async function compareFile() {
compared.kind = Number(document.querySelector('[name="ep-kind"]:checked')?.value || 0);
const file = await pickFile();
if (!file) return;
const legacy = await EstApp.api(`/api/estplus-compare?kind=${compared.kind}`, {
method: "POST",
body: await file.arrayBuffer(),
});
compared.rows = pair(EstApp.state.project, compared.kind, legacy);
draw();
}
async function load(state) {
const file = await pickFile();
if (!file) return;
const project = await EstApp.api(
`/api/estplus-read?name=${encodeURIComponent(file.name.replace(/\.(est|ebk)$/i, ""))}`,
{ method: "POST", body: await file.arrayBuffer() },
);
if (!confirm("구버젼 데이타를 변환했습니다. 현재 작업을 변환한 자료로 바꾸시겠습니까?")) return;
EstApp.setProject(project);
state.notice = (project.importWarnings || []).join(" ");
EstApp.render();
}
async function convert(state) {
const response = await fetch("/api/estplus-write", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state.project),
});
if (!response.ok) throw Error((await response.json()).error);
EstApp.download(
`${state.project.name || "내역서"}.est`,
await response.blob(),
"application/octet-stream",
);
state.notice = "변환을 마쳤습니다.";
EstApp.render();
}
function install() {
document.addEventListener("keydown", (event) => {
const k = event.key.toLowerCase();
if (event.ctrlKey && event.altKey && k === "e") {
event.preventDefault();
handle("estplus-compare", EstApp.state);
} else if (event.ctrlKey && event.shiftKey && event.key === "F12") {
event.preventDefault();
handle("estplus-convert", EstApp.state);
}
});
document.addEventListener("dblclick", (event) => {
const tr = event.target.closest?.("[data-ep-row]");
if (!tr) return;
const [, table] = KINDS[compared.kind],
row = EstApp.state.project.tables[table][Number(tr.dataset.epRow)];
if (!row?.Code && table !== "EstMainDat") return;
EstApp.byId("dialog").close();
const view = { CostTitleDat: "CostMainDat" }[table] || table;
EstApp.navigate(view, table === "EstMainDat" ? "" : row.Code);
});
}
if (typeof document !== "undefined" && document.addEventListener) install();
function handle(name, state) {
const fail = (error) => {
const note = EstApp.byId("estplus-error");
if (note) note.textContent = error.message;
else {
state.notice = error.message;
EstApp.render();
}
};
if (name === "estplus-compare") draw();
else if (name === "estplus-compare-file") compareFile().catch(fail);
else if (name === "estplus-load") load(state).catch(fail);
else if (name === "estplus-convert") convert(state).catch(fail);
else return false;
return true;
}
return { pair, handle };
})();