// 원본 무관자료 삭제(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 }; })();