- 원본 처리 근거대로 내역서가 쓰는 자료만 하위까지 따라가 남기고 나머지 삭제 - 내역서 첫 등장 순서 재배열, 묶음 앞 빈 줄 정리, 미연결 수량산출서 삭제 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
6.2 KiB
JavaScript
148 lines
6.2 KiB
JavaScript
// 원본 무관자료 삭제(TfrmEstMain mnuNotLinkDelete 0x10cd2a8·mnuNotLinkDelSort 0x10cd2b8 → f039f0,
|
|
// mnuNotLinkMulDelete 0x10cd2c8 → 1116db0). 내역서가 쓰는 자료(LinkIndex 5·6·21·101·103·104·105, Code 일치)를
|
|
// 일위대가·단가산출·기계경비 묶음 안까지 따라가 표시하고 나머지 기초·묶음 자료를 지운다. 내역서·원가·적용기준은 지우지 않는다.
|
|
// 내역서순: 내역서 첫 등장 순서(중첩은 깊이 우선)로 표를 다시 늘어놓고, 코드는 바꾸지 않는다.
|
|
// 설계변경 당초 코드(Code_)도 함께 따라간다. 원본은 내역서→일위→단가→기계 차례 한 번 훑기라 기계경비 안에서만
|
|
// 쓰인 일위대가의 하위를 놓칠 수 있으나, 웹은 끝까지 따라가 쓰는 자료를 지우지 않는다.
|
|
const EstCleanup = (() => {
|
|
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;
|
|
const text = (value) => String(value ?? "").trim();
|
|
|
|
function used(project) {
|
|
const order = new Map(Object.values(flat).map((t) => [t, []])),
|
|
seen = new Set(),
|
|
groups = {};
|
|
for (const [, [, main]] of Object.entries(grouped)) {
|
|
order.set(main, []);
|
|
groups[main] = EstNative.groups(project.tables[main] || []);
|
|
}
|
|
function visit(rows) {
|
|
for (const row of rows)
|
|
for (const code of new Set([row.Code, row.Code_].filter(Boolean))) {
|
|
const kind = num(row.LinkIndex),
|
|
id = `${kind}:${code}`;
|
|
if (seen.has(id)) continue;
|
|
if (flat[kind]) {
|
|
if ((project.tables[flat[kind]] || []).some((r) => r.Code === code)) {
|
|
seen.add(id);
|
|
order.get(flat[kind]).push(code);
|
|
}
|
|
} else if (grouped[kind]) {
|
|
const [, main] = grouped[kind],
|
|
group = groups[main].get(code);
|
|
if (!group) continue;
|
|
seen.add(id);
|
|
order.get(main).push(code);
|
|
visit(group.rows.slice(1));
|
|
}
|
|
}
|
|
}
|
|
visit(project.tables.EstMainDat || []);
|
|
return order;
|
|
}
|
|
|
|
function blankRow(table, row) {
|
|
if (table === "CostMainDat") return !text(row.LineData);
|
|
return (
|
|
!text(row.Name) &&
|
|
!text(row.Size) &&
|
|
!text(row.Unite) &&
|
|
!(typeof row.Bigo === "string" && text(row.Bigo)) &&
|
|
!num(row.GN) &&
|
|
!num(row.TC)
|
|
);
|
|
}
|
|
// 빈 줄 정리(f038ac): 묶음 머리 바로 앞의 빈 행을 지운다.
|
|
function trimBeforeHeads(rows, table) {
|
|
return rows.filter((row, i) => {
|
|
if (!blankRow(table, row) || num(row.Index) > 0) return true;
|
|
let next = i + 1;
|
|
while (next < rows.length && blankRow(table, rows[next]) && !(num(rows[next].Index) > 0))
|
|
next++;
|
|
return !(next < rows.length && num(rows[next].Index) > 0);
|
|
});
|
|
}
|
|
|
|
function clean(project, sorted) {
|
|
const order = used(project),
|
|
removed = {};
|
|
for (const table of Object.values(flat)) {
|
|
const rows = project.tables[table] || [],
|
|
keep = order.get(table),
|
|
byCode = new Map(rows.map((r) => [r.Code, r]));
|
|
const next = sorted
|
|
? keep.map((code) => byCode.get(code))
|
|
: rows.filter((r) => keep.includes(r.Code));
|
|
removed[table] = rows.length - next.length;
|
|
project.tables[table] = next;
|
|
}
|
|
for (const [title, main] of Object.values(grouped)) {
|
|
const keep = order.get(main),
|
|
groups = EstNative.groups(project.tables[main] || []),
|
|
before = groups.size;
|
|
const codes = sorted ? keep : [...groups.keys()].filter((code) => keep.includes(code));
|
|
project.tables[main] = trimBeforeHeads(
|
|
codes.flatMap((code) => groups.get(code)?.rows || []),
|
|
main,
|
|
);
|
|
const titles = project.tables[title] || [],
|
|
byCode = new Map(titles.map((r) => [r.Code, r]));
|
|
project.tables[title] = sorted
|
|
? codes.map((code) => byCode.get(code)).filter(Boolean)
|
|
: titles.filter((r) => keep.includes(r.Code));
|
|
removed[main] = before - codes.length;
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
// 수량산출서: 내역 행이 쓰는(MullangIndex 9999·MulCode 앞 조각) 산출서와, 항목이 내역에 연결된 산출서만 남긴다.
|
|
function cleanQuantity(project) {
|
|
const sheets = project.quantitySheets || [],
|
|
estimate = project.tables.EstMainDat || [],
|
|
codes = new Set(estimate.map((r) => r.Code).filter(Boolean)),
|
|
keys = new Set(estimate.map((r) => r.PkID != null && `id:${r.PkID}`).filter(Boolean)),
|
|
named = new Set(
|
|
estimate
|
|
.filter((r) => num(r.MullangIndex) === 9999 && typeof r.MulCode === "string")
|
|
.map((r) => r.MulCode.split("\t")[0]),
|
|
);
|
|
const keep = sheets.filter(
|
|
(sheet) =>
|
|
named.has(sheet.Code) ||
|
|
(sheet.items || []).some(
|
|
(item) =>
|
|
(item.TargetKey && keys.has(item.TargetKey)) ||
|
|
(item.TargetCode && codes.has(item.TargetCode)),
|
|
),
|
|
);
|
|
project.quantitySheets = keep;
|
|
return sheets.length - keep.length;
|
|
}
|
|
|
|
const WARN =
|
|
"내역서상에 사용한 데이타(수량산출)만 남기고 나머지는 삭제합니다.\n삭제된 자료는 되살리기로만 복구할 수 있습니다.";
|
|
function handle(name, state) {
|
|
if (name === "notlink-delete" || name === "notlink-sort") {
|
|
if (!confirm(WARN)) return true;
|
|
let removed;
|
|
EstApp.change((project) => (removed = clean(project, name === "notlink-sort")));
|
|
const total = Object.values(removed).reduce((a, b) => a + b, 0);
|
|
state.notice = `안 쓰는 자료 ${total}건을 지웠습니다${name === "notlink-sort" ? "(내역서 순서로 정리)" : ""}.`;
|
|
} else if (name === "notlink-quantity") {
|
|
if (!confirm(WARN)) return true;
|
|
let removed;
|
|
EstApp.change((project) => (removed = cleanQuantity(project)), false);
|
|
state.notice = `내역에 연결되지 않은 수량산출서 ${removed}개를 지웠습니다.`;
|
|
} else return false;
|
|
return true;
|
|
}
|
|
|
|
return { used, clean, cleanQuantity, trimBeforeHeads, handle };
|
|
})();
|