- 원본 낙찰율 창처럼 전체·선택한 줄·선택한 공종 하위로 범위 지정, 적용된 줄 제외 - 전체낙찰율 한 비율 또는 비목별 선택, 편집 메뉴에 낙찰률 적용·당초단가 복원 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
3.7 KiB
JavaScript
103 lines
3.7 KiB
JavaScript
// 원본 낙찰율(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 };
|
|
})();
|