/* -*- 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;