- 다른 공사 파일의 순공사비 위 내역 전체를 하위 자료와 함께 이 공사 순공사비 위에 합침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
198 lines
8.2 KiB
JavaScript
198 lines
8.2 KiB
JavaScript
// 원본 다른 공사 불러오기·복사(TFRMOTHERLOAD)와 프로젝트 추가(mnuFileLoadProjectAs)의 하위 자료 보존.
|
|
// 내역 행이 쓰는 자재·노임·실적·견적 행과 일위대가·단가산출·기계경비 호표를 하위까지 모아 함께 옮긴다.
|
|
// 붙여넣을 공사에 같은 코드가 있으면 그 공사의 자료를 그대로 쓰고(덮어쓰지 않음) 개수만 알린다.
|
|
// 기계경비 적용기준은 붙여넣을 공사에 하나도 없을 때만 함께 옮긴다.
|
|
const EstTransfer = (() => {
|
|
const flat = { 5: "StuffcDat", 6: "LaborcDat", 21: "SilJukDat", 101: "EstiMateDat" };
|
|
const grouped = {
|
|
103: ["HeadTitleDat", "HeadMainDat"],
|
|
104: ["CostTitleDat", "CostMainDat"],
|
|
105: ["MachTitleDat", "MachMainDat"],
|
|
};
|
|
const num = (value) => Number(value) || 0;
|
|
|
|
function collect(source, rows) {
|
|
const deps = { flat: {}, groups: {} },
|
|
seen = new Set(),
|
|
queue = [...rows];
|
|
while (queue.length) {
|
|
const row = queue.shift(),
|
|
kind = num(row.LinkIndex),
|
|
code = row.Code;
|
|
if (!code || seen.has(`${kind}:${code}`)) continue;
|
|
if (flat[kind]) {
|
|
const found = (source.tables[flat[kind]] || []).find((r) => r.Code === code);
|
|
if (!found) continue;
|
|
seen.add(`${kind}:${code}`);
|
|
(deps.flat[flat[kind]] ||= []).push(structuredClone(found));
|
|
} else if (grouped[kind]) {
|
|
const [titleTable, mainTable] = grouped[kind],
|
|
group = EstNative.groups(source.tables[mainTable] || []).get(code);
|
|
if (!group) continue;
|
|
seen.add(`${kind}:${code}`);
|
|
const title = (source.tables[titleTable] || []).find((r) => r.Code === code);
|
|
(deps.groups[mainTable] ||= []).push({
|
|
code,
|
|
titleTable,
|
|
title: title ? structuredClone(title) : null,
|
|
rows: structuredClone(group.rows),
|
|
});
|
|
queue.push(...group.rows.slice(1));
|
|
}
|
|
}
|
|
// 기계경비 적용기준은 공사 전체 설정 — 붙여넣을 공사에 없을 때만 함께 옮긴다.
|
|
if (deps.groups.MachMainDat?.length && source.tables.MachGijunDat?.length)
|
|
deps.criteria = structuredClone(source.tables.MachGijunDat);
|
|
return deps;
|
|
}
|
|
|
|
function nextPkID(project, table, extra = []) {
|
|
return (
|
|
Math.max(
|
|
0,
|
|
...(project.tables[table] || []).map((r) => num(r.PkID)),
|
|
...extra.map((r) => num(r.PkID)),
|
|
) + 1
|
|
);
|
|
}
|
|
|
|
function merge(target, deps) {
|
|
const added = [],
|
|
kept = [];
|
|
for (const [table, list] of Object.entries(deps.flat)) {
|
|
const rows = (target.tables[table] ||= []);
|
|
for (const row of list) {
|
|
if (rows.some((r) => r.Code === row.Code)) {
|
|
kept.push(row.Code);
|
|
continue;
|
|
}
|
|
row.PkID = nextPkID(target, table);
|
|
row.Index = Math.max(0, ...rows.map((r) => num(r.Index))) + 1;
|
|
rows.push(row);
|
|
added.push(row.Code);
|
|
}
|
|
}
|
|
for (const [table, list] of Object.entries(deps.groups)) {
|
|
const rows = (target.tables[table] ||= []);
|
|
for (const group of list) {
|
|
if (EstNative.groups(rows).has(group.code)) {
|
|
kept.push(group.code);
|
|
continue;
|
|
}
|
|
let pk = nextPkID(target, table);
|
|
const index = Math.max(0, ...rows.map((r) => num(r.Index))) + 1;
|
|
group.rows.forEach((row, i) => {
|
|
row.PkID = pk++;
|
|
if (i === 0) row.Index = index;
|
|
});
|
|
rows.push(...group.rows);
|
|
if (group.title) {
|
|
const titles = (target.tables[group.titleTable] ||= []);
|
|
if (!titles.some((r) => r.Code === group.code)) {
|
|
group.title.PkID = nextPkID(target, group.titleTable);
|
|
group.title.Index = index;
|
|
titles.push(group.title);
|
|
}
|
|
}
|
|
added.push(group.code);
|
|
}
|
|
}
|
|
let criteria = false;
|
|
if (deps.criteria && !target.tables.MachGijunDat?.length) {
|
|
target.tables.MachGijunDat = deps.criteria;
|
|
criteria = true;
|
|
}
|
|
return { added, kept, criteria };
|
|
}
|
|
|
|
function paste(target, rows, at) {
|
|
const list = target.tables.EstMainDat;
|
|
let pk = nextPkID(target, "EstMainDat");
|
|
const copies = structuredClone(rows).map((row) => {
|
|
row.PkID = pk++;
|
|
row.NewItem = true;
|
|
delete row.PreviousCodes;
|
|
return row;
|
|
});
|
|
list.splice(at, 0, ...copies);
|
|
return copies.length;
|
|
}
|
|
|
|
// 프로젝트 추가: 다른 공사의 순공사비 위 내역 행 전체를 하위 자료와 함께 이 공사의 순공사비 위에 붙인다.
|
|
function addProject(target, source) {
|
|
const list = source.tables?.EstMainDat || [];
|
|
let end = list.findIndex((row) => Number(row.LinkIndex) === 54);
|
|
if (end < 0) end = list.length;
|
|
const rows = list.slice(0, end);
|
|
if (!rows.length) throw Error("추가할 공사에 순공사비 위 내역 행이 없습니다.");
|
|
const result = merge(target, collect(source, rows));
|
|
let at = target.tables.EstMainDat.findIndex((row) => Number(row.LinkIndex) === 54);
|
|
if (at < 0) at = target.tables.EstMainDat.length;
|
|
paste(target, rows, at);
|
|
return { ...result, rows: rows.length };
|
|
}
|
|
|
|
function chooseProject(state) {
|
|
if (state.view !== "EstMainDat") throw Error("내역서 화면에서 프로젝트를 추가하세요.");
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = ".estx,.ebkx,.json";
|
|
input.addEventListener("change", async () => {
|
|
try {
|
|
const file = input.files[0];
|
|
if (!file) return;
|
|
const source = /\.(estx|ebkx)$/i.test(file.name)
|
|
? await EstApp.api(`/api/import?name=${encodeURIComponent(file.name)}`, {
|
|
method: "POST",
|
|
body: await file.arrayBuffer(),
|
|
})
|
|
: JSON.parse(await file.text());
|
|
if (!confirm(`「${source.name || file.name}」의 내역을 이 공사의 순공사비 위에 합칩니다.`))
|
|
return;
|
|
let result;
|
|
EstApp.change((project) => (result = addProject(project, source)));
|
|
state.notice = `내역 ${result.rows}행을 합쳤습니다. 연결 자료 ${result.added.length}건 추가${result.kept.length ? `, 같은 코드 ${result.kept.length}건은 이 공사 자료를 그대로 씀` : ""}.`;
|
|
} catch (error) {
|
|
state.notice = error.message;
|
|
}
|
|
EstApp.render();
|
|
});
|
|
input.click();
|
|
}
|
|
|
|
// 화면
|
|
let clipboard = null;
|
|
function handle(name, state) {
|
|
if (name === "copy-with-links") {
|
|
if (state.view !== "EstMainDat") throw Error("내역서 화면에서 행을 고르세요.");
|
|
const rows = state.project.tables.EstMainDat,
|
|
picked = [...state.selection].sort((a, b) => a - b).map((i) => rows[i]);
|
|
if (!picked.length) throw Error("복사할 내역 행을 선택하세요.");
|
|
clipboard = {
|
|
rows: structuredClone(picked),
|
|
deps: collect(state.project, picked),
|
|
from: state.project.name,
|
|
};
|
|
const count =
|
|
Object.values(clipboard.deps.flat).flat().length +
|
|
Object.values(clipboard.deps.groups).flat().length;
|
|
state.notice = `내역 ${picked.length}행과 연결 자료 ${count}건을 복사했습니다. 다른 공사를 열고 붙여넣으세요.`;
|
|
} else if (name === "paste-with-links") {
|
|
if (!clipboard) throw Error("먼저 「연결 자료와 함께 복사」를 하세요.");
|
|
if (state.view !== "EstMainDat") throw Error("내역서 화면에서 붙여넣으세요.");
|
|
const rows = state.project.tables.EstMainDat,
|
|
at = ([...state.selection].sort((a, b) => a - b).at(-1) ?? rows.length - 1) + 1;
|
|
let result;
|
|
EstApp.change((project) => {
|
|
result = merge(project, structuredClone(clipboard.deps));
|
|
paste(project, clipboard.rows, at);
|
|
});
|
|
state.notice = `내역 ${clipboard.rows.length}행을 붙여넣었습니다. 연결 자료 ${result.added.length}건 추가${result.kept.length ? `, 같은 코드 ${result.kept.length}건은 이 공사 자료를 그대로 씀` : ""}${result.criteria ? ", 비어 있던 기계경비 적용기준도 함께 옮김" : ""}.`;
|
|
} else if (name === "project-add") chooseProject(state);
|
|
else return false;
|
|
return true;
|
|
}
|
|
|
|
return { collect, merge, paste, addProject, handle };
|
|
})();
|