// 원본 낙찰율(TFRMBIMOGBIUL) — 전체낙찰율·비목별낙찰율, 적용방법(현재/선택한/전체 라인), 공종별(선택한 공종 하위), // 「낙찰율 적용된 공종 제외」. 계산은 EstBusiness.bid(행 묶음 지정)를 그대로 쓴다. const EstBid = (() => { const num = (value) => Number(value) || 0; // 내역서 공종(Level>0) 행의 하위 상세 행 번호 — 원본 내역 집계와 같은 범위(같거나 높은 Level 전까지). function subtree(rows, heading) { const level = num(rows[heading]?.Level), out = []; if (level <= 0) return out; for (let i = heading + 1; i < rows.length; i++) { if (num(rows[i].Level) >= level || [54, 58, 59].includes(num(rows[i].LinkIndex))) break; if (!num(rows[i].Level)) out.push(i); } return out; } function scope(project, view, selection, range, skipApplied) { const table = view === "HeadMainDat" ? "HeadMainDat" : "EstMainDat", rows = project.tables[table] || [], picked = [...selection].map(Number).sort((a, b) => a - b); let indices; if (range === "all") return null; if (range === "rows") { if (!picked.length) throw Error("낙찰률을 적용할 줄을 선택하세요."); indices = picked; } else { if (table !== "EstMainDat") throw Error("공종별 적용은 내역서 화면에서 하세요."); const headings = picked.filter((i) => num(rows[i]?.Level) > 0); if (!headings.length) throw Error("낙찰률을 적용할 공종(제목 줄)을 선택하세요."); indices = [...new Set(headings.flatMap((i) => subtree(rows, i)))]; } if (skipApplied) indices = indices.filter((i) => !rows[i].ApplyNacChal); if (!indices.length) throw Error("적용할 줄이 없습니다."); return { [table]: new Set(indices) }; } function rates(values) { const list = values.method === "total" ? [values.T, values.T, values.T, values.T] : [values.N, values.G, values.J, values.S]; if (list.some((n) => !Number.isFinite(n) || n < 0)) throw Error("낙찰률은 0 이상의 수치입니다."); return list; } function open(state) { const view = state.view; EstActions.form( "낙찰률 적용", [ { name: "method", label: "방식", value: "bimog", choices: [ ["total", "전체낙찰율(한 비율)"], ["bimog", "비목별낙찰율"], ], }, { name: "T", label: "전체 %", type: "number", value: 100 }, { name: "N", label: "노무 %", type: "number", value: 100 }, { name: "G", label: "재료 %", type: "number", value: 100 }, { name: "J", label: "경비 %", type: "number", value: 100 }, { name: "S", label: "실적 %", type: "number", value: 100 }, { name: "range", label: "적용방법", value: "all", choices: [ ["all", "전체 라인에 적용"], ["rows", "선택한 라인에 적용"], ["groups", "선택한 공종(하위 줄)에 적용"], ], }, { name: "skip", label: "낙찰 적용된 줄", value: "keep", choices: [ ["keep", "다시 적용"], ["skip", "제외"], ], }, ], (values) => { const list = rates(values), only = scope(state.project, view, state.selection, values.range, values.skip === "skip"); EstApp.change(() => (state.project = EstBusiness.bid(state.project, list, only))); }, ); } function handle(name, state) { if (name !== "bid-rates") return false; open(state); return true; } return { subtree, scope, rates, handle }; })();