Files
Aislo/tools/est-test/native-bid.js
eomsangdonandClaude Opus 5.5 6518238ef1 feat(est-test): 낙찰률 적용 범위와 전체낙찰율
- 원본 낙찰율 창처럼 전체·선택한 줄·선택한 공종 하위로 범위 지정, 적용된 줄 제외
- 전체낙찰율 한 비율 또는 비목별 선택, 편집 메뉴에 낙찰률 적용·당초단가 복원

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:16:20 +09:00

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 };
})();