150 lines
5.6 KiB
JavaScript
150 lines
5.6 KiB
JavaScript
/* -*- coding: utf-8 -*- */
|
|
/**
|
|
* 실행예산 계산 (exec_calc.js) — 실행단가·실행수량 · 중기 실행단가(절사 6택 · 차액 보정 순서)
|
|
* 근거: 지식DB STmate/old/20_분석 27 §4(wM_Boq_ExecX · 1단위당 중기사용료 · 1단위=시간 · 최초단가 → 실행단가
|
|
* · 절사 1~100,000원 · 차액 보정 기본/노/재/경 · 최초수량 → 실행수량 · 실행예산서 Ⅰ/Ⅱ 0673·0674) · 35 §3
|
|
* 미확인(표본 0건, 27 §9): 성분 나눔 방식 — 여기서는 최초단가 비례 · 「기본보정」 = 가장 큰 성분에 차액
|
|
* 시험: resources/tester/test_stmate_stage_exec.py (자기 일관성 — 성분 합 = 절사 목표 · 절사 단위 배수)
|
|
*/
|
|
const StmateExec = (() => {
|
|
const Calc = (typeof window !== "undefined" && window.STMATE_CALC) || require("./calc.js");
|
|
const Change =
|
|
(typeof window !== "undefined" && window.STMATE_CHANGE) || require("./change_calc.js");
|
|
const { roundDown } = Calc;
|
|
const n0 = (v) => Number(v) || 0;
|
|
// 절사 기준 6택 — 1원 · 10원 · 100원 · 1,000원 · 10,000원 · 100,000원 미만 절사
|
|
const JEOL = [1, 10, 100, 1000, 10000, 100000];
|
|
// 차액 보정 순서 — 기본보정 · 노무비 · 재료비 · 경비
|
|
const ORDERS = [
|
|
["base", "기본보정"],
|
|
["l", "노무비"],
|
|
["m", "재료비"],
|
|
["s", "경비"],
|
|
];
|
|
const cut = (v, j) => Math.trunc(n0(v) / (n0(j) || 1) + 1e-9) * (n0(j) || 1);
|
|
|
|
// 중기 실행단가 — 1단위당 사용료 ÷ 시간 → 절사 → 최초단가 비례 나눔 → 성분 절사 → 차액 한 성분에
|
|
function machineExecUnit(p) {
|
|
const j = n0(p.jeol) || 1;
|
|
const hours = n0(p.hours) || 1;
|
|
const target = n0(p.fee) / hours;
|
|
const T = cut(target, j);
|
|
const init = {
|
|
l: n0(p.init && p.init.l),
|
|
m: n0(p.init && p.init.m),
|
|
s: n0(p.init && p.init.s),
|
|
};
|
|
const sum = init.l + init.m + init.s;
|
|
const comps = {};
|
|
for (const k of ["l", "m", "s"]) comps[k] = sum ? cut((T * init[k]) / sum, j) : 0;
|
|
if (!sum) comps.s = T;
|
|
const diff = T - (comps.l + comps.m + comps.s);
|
|
let to = p.order || "base";
|
|
if (to === "base") to = ["l", "m", "s"].reduce((a, k) => (init[k] > init[a] ? k : a), "l");
|
|
comps[to] += diff;
|
|
const ratio = n0(p.initQty) ? n0(p.execQty || p.initQty) / n0(p.initQty) : 1;
|
|
return {
|
|
target,
|
|
T,
|
|
comps,
|
|
diff,
|
|
to,
|
|
initT: sum,
|
|
execQty: n0(p.execQty || p.initQty),
|
|
qtyRatio: ratio,
|
|
};
|
|
}
|
|
|
|
// 실행예산 행 — 실행단가 = 직접 입력 또는 trunc(설계단가 × 실행율, 절사 단위) · 실행수량 = 입력 또는 설계수량
|
|
function execRow(r, o) {
|
|
const ov = (o.overrides || {})[r.id] || {};
|
|
const j = n0(o.jeol) || 1;
|
|
const rate = { m: n0(o.rateM ?? 100), l: n0(o.rateL ?? 100), s: n0(o.rateS ?? 100) };
|
|
const u = {};
|
|
["m", "l", "s"].forEach((k, i) => {
|
|
u[k] = ov.mls ? n0(ov.mls[i]) : cut((n0(r[k]) * rate[k]) / 100, j);
|
|
});
|
|
const q = ov.qty != null ? n0(ov.qty) : n0(r.qty);
|
|
const a = {
|
|
mAmt: roundDown(u.m * q, 0),
|
|
lAmt: roundDown(u.l * q, 0),
|
|
sAmt: roundDown(u.s * q, 0),
|
|
};
|
|
a.tAmt = a.mAmt + a.lAmt + a.sAmt;
|
|
return { qty: q, mUnit: u.m, lUnit: u.l, sUnit: u.s, tUnit: u.m + u.l + u.s, ...a };
|
|
}
|
|
|
|
// 평면 행 실행예산 — rows [{id, qty, m, l, s, gov}]
|
|
function budget(rows, opts = {}) {
|
|
const out = rows.map((r) => {
|
|
const x = execRow(r, opts);
|
|
const d = {
|
|
mAmt: roundDown(n0(r.m) * n0(r.qty), 0),
|
|
lAmt: roundDown(n0(r.l) * n0(r.qty), 0),
|
|
sAmt: roundDown(n0(r.s) * n0(r.qty), 0),
|
|
};
|
|
d.tAmt = d.mAmt + d.lAmt + d.sAmt;
|
|
return Object.assign({}, r, { design: d, exec: x });
|
|
});
|
|
const tot = (key) =>
|
|
out
|
|
.filter((r) => !r.gov)
|
|
.reduce(
|
|
(a, r) => ({
|
|
mAmt: a.mAmt + r[key].mAmt,
|
|
lAmt: a.lAmt + r[key].lAmt,
|
|
sAmt: a.sAmt + r[key].sAmt,
|
|
tAmt: a.tAmt + r[key].tAmt,
|
|
}),
|
|
{ mAmt: 0, lAmt: 0, sAmt: 0, tAmt: 0 },
|
|
);
|
|
const design = tot("design"),
|
|
exec = tot("exec");
|
|
return {
|
|
rows: out,
|
|
design,
|
|
exec,
|
|
diff: exec.tAmt - design.tAmt,
|
|
rate: design.tAmt ? Math.round((exec.tAmt / design.tAmt) * 1e5) / 1e3 : 0,
|
|
};
|
|
}
|
|
|
|
// 내역 구조 위 실행예산(실행예산서 Ⅰ: 공종 | 설계(계획) | 실행예산 | 구분) — 머리 합계 포함
|
|
function budgetFromTree(tree, stage, opts = {}) {
|
|
const nodes = tree.nodes.map((nd) => {
|
|
const o = Object.assign({}, nd, { st: {} });
|
|
if (nd.kind !== "leaf") return o;
|
|
const s = nd.st[stage] || {};
|
|
o.st.d = {
|
|
qty: n0(s.qty),
|
|
mUnit: n0(s.mUnit),
|
|
lUnit: n0(s.lUnit),
|
|
sUnit: n0(s.sUnit),
|
|
tUnit: n0(s.tUnit),
|
|
mAmt: n0(s.mAmt),
|
|
lAmt: n0(s.lAmt),
|
|
sAmt: n0(s.sAmt),
|
|
tAmt: n0(s.tAmt),
|
|
};
|
|
o.st.x = execRow(
|
|
{ id: nd.id, qty: s.qty, m: s.mUnit, l: s.lUnit, s: s.sUnit },
|
|
nd.gov ? Object.assign({}, opts, { rateM: 100, rateL: 100, rateS: 100 }) : opts,
|
|
);
|
|
return o;
|
|
});
|
|
Change.sumHeads(nodes, ["d", "x"]);
|
|
const tops = nodes.filter((nd) => nd.kind === "head" && !nd.zone);
|
|
const top = tops.reduce((a, h) => (!a || h.level > a.level ? h : a), null);
|
|
return {
|
|
nodes,
|
|
design: top ? top.st.d.tAmt : 0,
|
|
exec: top ? top.st.x.tAmt : 0,
|
|
};
|
|
}
|
|
|
|
return { JEOL, ORDERS, machineExecUnit, execRow, budget, budgetFromTree };
|
|
})();
|
|
|
|
if (typeof window !== "undefined") window.STMATE_EXEC = StmateExec;
|
|
if (typeof module !== "undefined" && module.exports) module.exports = StmateExec;
|