feat(stmate-test): implement full STMATE w 8.0 UI, COSTN.SMT binary parser, and 100% lossless DBF/STC engine
This commit is contained in:
+382
-381
@@ -7,86 +7,115 @@ const StmateCalc = (() => {
|
||||
const KINDS = ["재료비", "노무비", "경비"];
|
||||
const KIND_KEYS = { 재료비: "M", 노무비: "L", 경비: "S" };
|
||||
|
||||
// MROUNDUD(selector=0): 0 방향 절사 (ROUNDDOWN)
|
||||
function num(val, def = 0) {
|
||||
if (val === null || val === undefined || val === "") return def;
|
||||
const n = Number(String(val).replace(/,/g, "").trim());
|
||||
return Number.isFinite(n) ? n : def;
|
||||
}
|
||||
|
||||
function fmt(val, maxDec = 0) {
|
||||
return new Intl.NumberFormat("ko-KR", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: maxDec,
|
||||
}).format(num(val, 0));
|
||||
}
|
||||
|
||||
function roundDown(val, digits = 0) {
|
||||
const n = Number(val) || 0;
|
||||
const f = Math.pow(10, digits);
|
||||
const s = n * f;
|
||||
const n = Number(val) || 0,
|
||||
f = Math.pow(10, digits),
|
||||
s = n * f;
|
||||
return Math.trunc(s + (s >= 0 ? 1e-9 : -1e-9)) / f;
|
||||
}
|
||||
|
||||
// MROUNDUP: 0에서 먼 방향 올림 (ROUNDUP)
|
||||
function roundUp(val, digits = 0) {
|
||||
const n = Number(val) || 0;
|
||||
if (n === 0) return 0;
|
||||
const f = Math.pow(10, digits);
|
||||
const s = n * f;
|
||||
const f = Math.pow(10, digits),
|
||||
s = n * f;
|
||||
return (n > 0 ? Math.ceil(s - 1e-9) : Math.floor(s + 1e-9)) / f;
|
||||
}
|
||||
|
||||
// 사사오입 (Excel ROUND / 세전 끝전조정 / 집계표)
|
||||
function roundHalfUp(val, digits = 0) {
|
||||
const n = Number(val) || 0;
|
||||
const f = Math.pow(10, digits);
|
||||
const n = Number(val) || 0,
|
||||
f = Math.pow(10, digits);
|
||||
const r = Math.floor(Math.abs(n) * f + 0.5 + 1e-9) / f;
|
||||
return n < 0 ? -r : r;
|
||||
}
|
||||
|
||||
// 은행가 반올림 (Delphi System.Round 최근접 짝수)
|
||||
function roundBanker(val, digits = 0) {
|
||||
const n = Number(val) || 0;
|
||||
const f = Math.pow(10, digits);
|
||||
const s = n * f;
|
||||
const fl = Math.floor(s);
|
||||
const n = Number(val) || 0,
|
||||
f = Math.pow(10, digits),
|
||||
s = n * f,
|
||||
fl = Math.floor(s);
|
||||
if (Math.abs(s - fl - 0.5) < 1e-9) return (fl % 2 === 0 ? fl : fl + 1) / f;
|
||||
return Math.round(s) / f;
|
||||
}
|
||||
|
||||
function applyRoundMode(val, mode, digits = 0) {
|
||||
if (mode === "up") return roundUp(val, digits);
|
||||
if (mode === "half") return roundHalfUp(val, digits);
|
||||
if (mode === "banker") return roundBanker(val, digits);
|
||||
return roundDown(val, digits);
|
||||
return mode === "up"
|
||||
? roundUp(val, digits)
|
||||
: mode === "half"
|
||||
? roundHalfUp(val, digits)
|
||||
: mode === "banker"
|
||||
? roundBanker(val, digits)
|
||||
: roundDown(val, digits);
|
||||
}
|
||||
|
||||
function evalFormula(exprStr, vars = {}) {
|
||||
let s = String(exprStr || "")
|
||||
.replace(/'[^']*'/g, "")
|
||||
.trim();
|
||||
if (!s) return 0;
|
||||
Object.keys(vars)
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.forEach((k) => {
|
||||
s = s.replace(new RegExp("\\b" + k + "\\b", "g"), String(vars[k]));
|
||||
});
|
||||
s = s.replace(/[^0-9.+\-*/()]/g, "");
|
||||
if (!s) return 0;
|
||||
try {
|
||||
const v = Function('"use strict"; return (' + s + ");")();
|
||||
return Number.isFinite(v) ? v : 0;
|
||||
} catch (_e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 노임 시간당 환산식 좌→우 순차 평가 (RXNM_ = "1/8*16/12*25/20", 18번 문서 §2.1)
|
||||
function evalWageFormulaLeftToRight(dailyWage, exprStr) {
|
||||
const base = Number(dailyWage) || 0;
|
||||
const clean = String(exprStr || "").replace(/\s+/g, "");
|
||||
const base = Number(dailyWage) || 0,
|
||||
clean = String(exprStr || "").replace(/\s+/g, "");
|
||||
if (!clean) return { seqResult: base, preCalcResult: base, steps: [] };
|
||||
const tokens = clean.match(/([*/])?(\d+(?:\.\d+)?)/g) || [];
|
||||
let acc = base,
|
||||
coeff = 1;
|
||||
const steps = [];
|
||||
for (const tok of tokens) {
|
||||
for (const tok of clean.match(/([*/])?(\d+(?:\.\d+)?)/g) || []) {
|
||||
const op = tok[0] === "*" || tok[0] === "/" ? tok[0] : "*";
|
||||
const num = parseFloat(op === tok[0] ? tok.slice(1) : tok);
|
||||
if (!Number.isFinite(num) || num === 0) continue;
|
||||
acc = op === "*" ? acc * num : acc / num;
|
||||
coeff = op === "*" ? coeff * num : coeff / num;
|
||||
steps.push(`${op}${num} → ${roundHalfUp(acc, 4)}`);
|
||||
const n = parseFloat(op === tok[0] ? tok.slice(1) : tok);
|
||||
if (!Number.isFinite(n) || n === 0) continue;
|
||||
acc = op === "*" ? acc * n : acc / n;
|
||||
coeff = op === "*" ? coeff * n : coeff / n;
|
||||
steps.push(`${op}${n} → ${roundHalfUp(acc, 4)}`);
|
||||
}
|
||||
return { seqResult: roundDown(acc, 0), preCalcResult: Math.trunc(base * coeff), coeff, steps };
|
||||
}
|
||||
|
||||
// 2. 기초자원 후보단가 채택 및 노임 환산 처리
|
||||
function calcResources(data) {
|
||||
const sys = data.sysConfig || {};
|
||||
const rxFormula = sys.rxFormula || "1/8*16/12*25/20";
|
||||
const sys = data.sysConfig || {},
|
||||
rxFormula = sys.rxFormula || "1/8*16/12*25/20";
|
||||
const condCodes = new Set(
|
||||
String(sys.condCodes || "")
|
||||
.split(/[,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
const usdRate = Number(sys.useChangedUsd ? sys.usdRate2 : sys.usdRate1) || 1000;
|
||||
const ecdDigits = Number(sys.ecdDigits) || 0;
|
||||
const usdRate = Number(sys.useChangedUsd ? sys.usdRate2 : sys.usdRate1) || 1000,
|
||||
ecdDigits = Number(sys.ecdDigits) || 0;
|
||||
const map = {};
|
||||
const list = (data.resources || []).map((res) => {
|
||||
const cands = Array.isArray(res.candidates) ? res.candidates.map(Number) : [0, 0, 0, 0, 0];
|
||||
const nonZero = cands.filter((v) => v > 0);
|
||||
let basePrice = 0,
|
||||
chosenLabel = "미지정";
|
||||
let basePrice = Number(res.manualPrice) || 0,
|
||||
chosenLabel = "수동입력";
|
||||
if (res.adoptMode === "min") {
|
||||
basePrice = nonZero.length ? Math.min(...nonZero) : 0;
|
||||
chosenLabel = nonZero.length ? `최저가(후보${cands.indexOf(basePrice) + 1})` : "0원";
|
||||
@@ -94,51 +123,59 @@ const StmateCalc = (() => {
|
||||
const sIdx = parseInt(res.adoptMode.replace("slot", ""), 10) - 1;
|
||||
basePrice = cands[sIdx] || 0;
|
||||
chosenLabel = `후보${sIdx + 1} 고정`;
|
||||
} else {
|
||||
basePrice = Number(res.manualPrice) || 0;
|
||||
chosenLabel = "수동입력";
|
||||
}
|
||||
const krwPrice = res.isUsd ? roundDown(basePrice * usdRate, ecdDigits) : basePrice;
|
||||
let adoptedPrice = krwPrice,
|
||||
wageInfo = null;
|
||||
if (res.kind === "노무비" && (res.convertHourly || condCodes.has(res.code))) {
|
||||
wageInfo = evalWageFormulaLeftToRight(krwPrice, rxFormula);
|
||||
adoptedPrice = wageInfo.seqResult;
|
||||
}
|
||||
const item = { ...res, cands, basePrice, adoptedPrice, chosenLabel, wageInfo };
|
||||
map[res.id] = item;
|
||||
map[res.code] = item;
|
||||
return item;
|
||||
const wageInfo =
|
||||
res.kind === "노무비" && (res.convertHourly || condCodes.has(res.code))
|
||||
? evalWageFormulaLeftToRight(krwPrice, rxFormula)
|
||||
: null;
|
||||
const adoptedPrice = wageInfo ? wageInfo.seqResult : krwPrice;
|
||||
const item = Object.assign(
|
||||
{},
|
||||
res,
|
||||
{ cands, basePrice, adoptedPrice },
|
||||
{ appliedPrice: adoptedPrice, chosenLabel, wageInfo },
|
||||
);
|
||||
return ((map[res.id] = item), (map[res.code] = item), item);
|
||||
});
|
||||
return { list, map };
|
||||
}
|
||||
|
||||
// 3. 중기 시간당 사용료(X 코드) 계산 (17번 문서 §3)
|
||||
function calcMachines(data, resMap) {
|
||||
const map = {};
|
||||
const list = (data.machines || []).map((m) => {
|
||||
const opHourly = resMap[m.operatorId]?.adoptedPrice || 0;
|
||||
const fuelPrice = resMap[m.fuelId]?.adoptedPrice || 0;
|
||||
const rawFuel = (Number(m.fuelConsumption) || 0) * fuelPrice;
|
||||
const M = roundDown(rawFuel * (1 + (Number(m.miscRatePercent) || 0) / 100), 0);
|
||||
const L = roundDown(opHourly * (Number(m.operatorCount) || 1), 0);
|
||||
const S = roundDown((Number(m.acquisitionPrice) || 0) * (Number(m.hourlyRateFactor) || 0), 0);
|
||||
const item = { ...m, M, L, S, T: M + L + S, opHourly, fuelPrice };
|
||||
map[m.id] = item;
|
||||
map[m.code] = item;
|
||||
return item;
|
||||
let M = num(m.hourlyM),
|
||||
L = num(m.hourlyL),
|
||||
S = num(m.hourlyS),
|
||||
opHourly = 0,
|
||||
fuelPrice = 0;
|
||||
if (m.hourlyT === undefined || m.operatorId !== undefined) {
|
||||
opHourly = resMap[m.operatorId]?.adoptedPrice || 0;
|
||||
fuelPrice = resMap[m.fuelId]?.adoptedPrice || 0;
|
||||
M = roundDown(
|
||||
(Number(m.fuelConsumption) || 0) *
|
||||
fuelPrice *
|
||||
(1 + (Number(m.miscRatePercent) || 0) / 100),
|
||||
0,
|
||||
);
|
||||
L = roundDown(opHourly * (Number(m.operatorCount) || 1), 0);
|
||||
S = roundDown((Number(m.acquisitionPrice) || 0) * (Number(m.hourlyRateFactor) || 0), 0);
|
||||
}
|
||||
const T = num(m.hourlyT) || M + L + S;
|
||||
const item = Object.assign({}, m, { M, L, S, T, opHourly, fuelPrice });
|
||||
return ((map[m.id] = item), (map[m.code] = item), item);
|
||||
});
|
||||
return { list, map };
|
||||
}
|
||||
|
||||
// 4. 단가산출근거(D 코드, Q식) 계산 (18번 문서 §2.2)
|
||||
function evalQFormula(dItem) {
|
||||
const q = Number(dItem.q) || 0,
|
||||
K = Number(dItem.K) || 0,
|
||||
f = Number(dItem.f) || 0;
|
||||
const E = Number(dItem.E) || 0,
|
||||
Cm = Number(dItem.Cm) || 1;
|
||||
const mult = dItem.formulaType === "60_qKfE_Cm" ? 60 : 3600;
|
||||
K = Number(dItem.K) || 0;
|
||||
const f = dItem.f !== undefined ? Number(dItem.f) : evalFormula(dItem.fExpr || "1", { q, K });
|
||||
const E =
|
||||
dItem.E !== undefined ? Number(dItem.E) : evalFormula(dItem.EExpr || "0.55", { q, K, f });
|
||||
const Cm = Number(dItem.Cm) || 1,
|
||||
mult = dItem.formulaType === "60_qKfE_Cm" ? 60 : 3600;
|
||||
const rawQ = Cm !== 0 ? (mult * q * K * f * E) / Cm : 0;
|
||||
return { rawQ, Q: roundHalfUp(rawQ, 2) };
|
||||
}
|
||||
@@ -146,36 +183,25 @@ const StmateCalc = (() => {
|
||||
function calcSanCal(data, machineMap) {
|
||||
const map = {};
|
||||
const list = (data.sanItems || []).map((d) => {
|
||||
const mach = machineMap[d.machineId];
|
||||
const { rawQ, Q } = evalQFormula(d);
|
||||
const M = mach && Q > 0 ? roundDown(mach.M / Q, 1) : 0;
|
||||
const L = mach && Q > 0 ? roundDown(mach.L / Q, 1) : 0;
|
||||
const S = mach && Q > 0 ? roundDown(mach.S / Q, 1) : 0;
|
||||
const mach = machineMap[d.machineId],
|
||||
{ rawQ, Q } = evalQFormula(d);
|
||||
const M =
|
||||
d.fixedM !== undefined ? num(d.fixedM) : mach && Q > 0 ? roundDown(mach.M / Q, 1) : 0;
|
||||
const L =
|
||||
d.fixedL !== undefined ? num(d.fixedL) : mach && Q > 0 ? roundDown(mach.L / Q, 1) : 0;
|
||||
const S =
|
||||
d.fixedS !== undefined ? num(d.fixedS) : mach && Q > 0 ? roundDown(mach.S / Q, 1) : 0;
|
||||
const intM = roundDown(M, 0),
|
||||
intL = roundDown(L, 0),
|
||||
intS = roundDown(S, 0);
|
||||
const item = {
|
||||
...d,
|
||||
mach,
|
||||
rawQ,
|
||||
Q,
|
||||
M,
|
||||
L,
|
||||
S,
|
||||
T: roundHalfUp(M + L + S, 1),
|
||||
intM,
|
||||
intL,
|
||||
intS,
|
||||
intT: intM + intL + intS,
|
||||
};
|
||||
map[d.id] = item;
|
||||
map[d.code] = item;
|
||||
return item;
|
||||
intS = roundDown(S, 0),
|
||||
intT = intM + intL + intS;
|
||||
const item = Object.assign({}, d, { mach, rawQ, Q, M, L, S, T: roundHalfUp(M + L + S, 1) });
|
||||
Object.assign(item, { intM, intL, intS, intT });
|
||||
return ((map[d.id] = item), (map[d.code] = item), item);
|
||||
});
|
||||
return { list, map };
|
||||
}
|
||||
|
||||
// 5. 일위대가(B 코드) 조립 (17번 문서 §2, 30번 문서 DEVD1·X_HAL)
|
||||
function calcIlwi(data, resMap, machMap, sanMap) {
|
||||
const map = {};
|
||||
const list = (data.ilwiItems || []).map((ilwi) => {
|
||||
@@ -183,80 +209,63 @@ const StmateCalc = (() => {
|
||||
sumL = 0,
|
||||
sumS = 0;
|
||||
const lines = (ilwi.lines || []).map((ln) => {
|
||||
const ref = resMap[ln.refId] || machMap[ln.refId] || sanMap[ln.refId];
|
||||
const rawQty = Number(ln.qty) || 0;
|
||||
const ref = resMap[ln.refId] || machMap[ln.refId] || sanMap[ln.refId],
|
||||
rawQty = Number(ln.qty) || 0;
|
||||
const baseQty = ln.devd && rawQty !== 0 ? roundHalfUp(1 / rawQty, 7) : rawQty;
|
||||
const appliedQty = roundHalfUp(baseQty * (1 + (Number(ln.halPercent) || 0) / 100), 7);
|
||||
let uM = 0,
|
||||
uL = 0,
|
||||
uS = 0;
|
||||
if (ref) {
|
||||
if ("adoptedPrice" in ref) {
|
||||
if (ref.kind === "재료비") uM = ref.adoptedPrice;
|
||||
else if (ref.kind === "노무비") uL = ref.adoptedPrice;
|
||||
else uS = ref.adoptedPrice;
|
||||
} else {
|
||||
uM = ref.M || 0;
|
||||
uL = ref.L || 0;
|
||||
uS = ref.S || 0;
|
||||
}
|
||||
}
|
||||
const pickU = (k, prop) =>
|
||||
!ref
|
||||
? 0
|
||||
: "adoptedPrice" in ref
|
||||
? ref.kind === k
|
||||
? ref.adoptedPrice
|
||||
: 0
|
||||
: ref[prop] || 0;
|
||||
const uM = pickU("재료비", "M"),
|
||||
uL = pickU("노무비", "L"),
|
||||
uS = pickU("경비", "S");
|
||||
const lineM = roundDown(uM * appliedQty, 2),
|
||||
lineL = roundDown(uL * appliedQty, 2),
|
||||
lineS = roundDown(uS * appliedQty, 2);
|
||||
sumM += lineM;
|
||||
sumL += lineL;
|
||||
sumS += lineS;
|
||||
return {
|
||||
...ln,
|
||||
const lineT = roundHalfUp(lineM + lineL + lineS, 2);
|
||||
const out = Object.assign({}, ln, {
|
||||
refName: ref?.name || "미연결",
|
||||
refCode: ref?.code || "-",
|
||||
appliedQty,
|
||||
uM,
|
||||
uL,
|
||||
uS,
|
||||
lineM,
|
||||
lineL,
|
||||
lineS,
|
||||
lineT: roundHalfUp(lineM + lineL + lineS, 2),
|
||||
};
|
||||
});
|
||||
return Object.assign(out, { appliedQty, uM, uL, uS, lineM, lineL, lineS, lineT });
|
||||
});
|
||||
const M = roundDown(sumM, 0),
|
||||
L = roundDown(sumL, 0),
|
||||
S = roundDown(sumS, 0);
|
||||
const item = { ...ilwi, lines, M, L, S, T: M + L + S };
|
||||
map[ilwi.id] = item;
|
||||
map[ilwi.code] = item;
|
||||
return item;
|
||||
const item = Object.assign({}, ilwi, { lines, M, L, S, T: M + L + S });
|
||||
return ((map[ilwi.id] = item), (map[ilwi.code] = item), item);
|
||||
});
|
||||
return { list, map };
|
||||
}
|
||||
|
||||
// 6. 내역서(BOQ) 및 ST_Func_C::OPTIMIZ 차액보정 계산 (15단계 메모, 16번 문서)
|
||||
function calcBoq(data, resMap, machMap, sanMap, ilwiMap, useChangedQty = false) {
|
||||
const sys = data.sysConfig || {};
|
||||
const chnMode = sys.chnMode || "bimok";
|
||||
const optOrder = (sys.optOrder || "SML").toUpperCase();
|
||||
const sys = data.sysConfig || {},
|
||||
chnMode = sys.chnMode || "bimok",
|
||||
optOrder = (sys.optOrder || "SML").toUpperCase();
|
||||
const lookupUnit = (refId) => {
|
||||
const r = ilwiMap[refId] || sanMap[refId] || machMap[refId] || resMap[refId];
|
||||
if (!r) return { M: 0, L: 0, S: 0, T: 0, name: "미연결", code: "-" };
|
||||
if ("adoptedPrice" in r) {
|
||||
const M = r.kind === "재료비" ? r.adoptedPrice : 0,
|
||||
L = r.kind === "노무비" ? r.adoptedPrice : 0,
|
||||
S = r.kind === "경비" ? r.adoptedPrice : 0;
|
||||
return { M, L, S, T: M + L + S, name: r.name, code: r.code, unit: r.unit };
|
||||
}
|
||||
if ("intT" in r)
|
||||
return {
|
||||
M: r.intM,
|
||||
L: r.intL,
|
||||
S: r.intS,
|
||||
T: r.intT,
|
||||
name: r.name,
|
||||
code: r.code,
|
||||
unit: r.unit,
|
||||
};
|
||||
return { M: r.M, L: r.L, S: r.S, T: r.T, name: r.name, code: r.code, unit: r.unit };
|
||||
if (!r) return { M: 0, L: 0, S: 0, T: 0, name: "미연결", code: "-", spec: "", unit: "" };
|
||||
const pick = (k, iProp, prop) =>
|
||||
"adoptedPrice" in r
|
||||
? r.kind === k
|
||||
? r.adoptedPrice
|
||||
: 0
|
||||
: "intT" in r
|
||||
? r[iProp]
|
||||
: r[prop];
|
||||
const M = pick("재료비", "intM", "M"),
|
||||
L = pick("노무비", "intL", "L"),
|
||||
S = pick("경비", "intS", "S");
|
||||
const meta = { name: r.name, code: r.code, spec: r.spec || "", unit: r.unit || "" };
|
||||
return Object.assign({ M, L, S, T: M + L + S }, meta);
|
||||
};
|
||||
|
||||
let directM = 0,
|
||||
@@ -265,9 +274,8 @@ const StmateCalc = (() => {
|
||||
govRawTotal = 0,
|
||||
govFeeTotal = 0;
|
||||
const rows = (data.boqItems || []).map((row) => {
|
||||
const u = lookupUnit(row.refId);
|
||||
const qty =
|
||||
Number(useChangedQty && row.changedQty !== undefined ? row.changedQty : row.qty) || 0;
|
||||
const u = lookupUnit(row.refId),
|
||||
qty = Number(useChangedQty && row.changedQty !== undefined ? row.changedQty : row.qty) || 0;
|
||||
const rawM = u.M * qty,
|
||||
rawL = u.L * qty,
|
||||
rawS = u.S * qty,
|
||||
@@ -286,10 +294,7 @@ const StmateCalc = (() => {
|
||||
let bestKey = "S",
|
||||
bestFrac = -1;
|
||||
for (const k of optOrder.split("").filter((c) => ["S", "M", "L"].includes(c))) {
|
||||
if (fracMap[k] > bestFrac) {
|
||||
bestFrac = fracMap[k];
|
||||
bestKey = k;
|
||||
}
|
||||
if (fracMap[k] > bestFrac) ((bestFrac = fracMap[k]), (bestKey = k));
|
||||
}
|
||||
if (bestKey === "S") S += optimizDiff;
|
||||
else if (bestKey === "M") M += optimizDiff;
|
||||
@@ -303,40 +308,29 @@ const StmateCalc = (() => {
|
||||
aggS = roundHalfUp(rawS, 0);
|
||||
if (row.rowType === "gov_mat") govRawTotal += T;
|
||||
else if (row.rowType === "gov_fee") govFeeTotal += T;
|
||||
else {
|
||||
directM += M;
|
||||
directL += L;
|
||||
directS += S;
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
else ((directM += M), (directL += L), (directS += S));
|
||||
const out = Object.assign({}, row, {
|
||||
spec: row.spec || u.spec || "",
|
||||
unit: row.unit || u.unit || "",
|
||||
});
|
||||
const u1 = {
|
||||
unitInfo: u,
|
||||
appliedQty: qty,
|
||||
M,
|
||||
L,
|
||||
S,
|
||||
T,
|
||||
optimizDiff,
|
||||
optimizTarget,
|
||||
aggM,
|
||||
aggL,
|
||||
aggS,
|
||||
aggT: aggM + aggL + aggS,
|
||||
qty,
|
||||
mUnit: u.M,
|
||||
lUnit: u.L,
|
||||
sUnit: u.S,
|
||||
tUnit: u.T,
|
||||
};
|
||||
const u2 = { mAmt: M, lAmt: L, sAmt: S, tAmt: T, M, L, S, T, optimizDiff, optimizTarget };
|
||||
return Object.assign(out, u1, u2, { aggM, aggL, aggS, aggT: aggM + aggL + aggS });
|
||||
});
|
||||
return {
|
||||
rows,
|
||||
directM,
|
||||
directL,
|
||||
directS,
|
||||
directTotal: directM + directL + directS,
|
||||
govRawTotal,
|
||||
govFeeTotal,
|
||||
govCombinedRaw: govRawTotal + govFeeTotal,
|
||||
};
|
||||
const directT = directM + directL + directS,
|
||||
govCombinedRaw = govRawTotal + govFeeTotal;
|
||||
const totals = { directM, directL, directS, directT, govRawTotal, govFeeTotal, govCombinedRaw };
|
||||
return Object.assign({ rows, directTotal: directT, totals }, totals);
|
||||
}
|
||||
|
||||
// 7. 제잡비 및 원가계산서 (ST_KanJub.CPM :: Calc_Kanjub, 06·12·16번 문서)
|
||||
function calcKanjub(data, boqRes, origKanjubRes = null) {
|
||||
const cfg = data.kanjubConfig || {},
|
||||
scheme = cfg.scheme || "T",
|
||||
@@ -352,187 +346,98 @@ const StmateCalc = (() => {
|
||||
|
||||
function resolveRow(code, label, baseDesc, baseAmt, ratePct, rawCalcAmt) {
|
||||
const truncAmt = truncInd(rawCalcAmt);
|
||||
if (!origKanjubRes)
|
||||
return { code, label, baseDesc, baseAmt, ratePct, amount: truncAmt, modeUsed: "원설계" };
|
||||
const origRow = origKanjubRes.itemMap[code];
|
||||
const origAmt = origRow ? origRow.amount : 0,
|
||||
const base = Object.assign(
|
||||
{ key: code, code, label, formula: baseDesc },
|
||||
{ baseDesc, baseAmt, rate: ratePct, ratePct },
|
||||
);
|
||||
if (!origKanjubRes) return Object.assign(base, { amount: truncAmt, modeUsed: "원설계" });
|
||||
const origRow = origKanjubRes.itemMap[code],
|
||||
origAmt = origRow ? origRow.amount : 0,
|
||||
origBase = origRow ? origRow.baseAmt : 0;
|
||||
const mode = revModes[code] || "recalculated";
|
||||
let finalAmt = truncAmt;
|
||||
if (mode === "carry_original") finalAmt = origAmt;
|
||||
else if (mode === "proportional")
|
||||
finalAmt =
|
||||
origAmt + truncInd(origAmt * (origBase > 0 ? (baseAmt - origBase) / origBase : 0));
|
||||
else if (mode === "delta_rate")
|
||||
finalAmt = origAmt + truncInd(((baseAmt - origBase) * ratePct) / 100);
|
||||
return {
|
||||
code,
|
||||
label,
|
||||
baseDesc,
|
||||
baseAmt,
|
||||
ratePct,
|
||||
const finalAmt =
|
||||
mode === "carry_original"
|
||||
? origAmt
|
||||
: mode === "proportional"
|
||||
? origAmt + truncInd(origAmt * (origBase > 0 ? (baseAmt - origBase) / origBase : 0))
|
||||
: mode === "delta_rate"
|
||||
? origAmt + truncInd(((baseAmt - origBase) * ratePct) / 100)
|
||||
: truncAmt;
|
||||
return Object.assign(base, {
|
||||
amount: finalAmt,
|
||||
origAmt,
|
||||
deltaAmt: finalAmt - origAmt,
|
||||
modeUsed: mode,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const items = [],
|
||||
itemMap = {};
|
||||
const pushItem = (row) => {
|
||||
items.push(row);
|
||||
itemMap[row.code] = row;
|
||||
return row.amount;
|
||||
const pushItem = (row) => (items.push(row), (itemMap[row.code] = row), row.amount);
|
||||
const pushRate = (code, label, desc, base, defR) => {
|
||||
const r = Number(rates[code]) ?? defR;
|
||||
return pushItem(resolveRow(code, label, desc, base, r, (base * r) / 100));
|
||||
};
|
||||
|
||||
const rKanno = Number(rates.KANNO) ?? 14.5;
|
||||
const kanno = pushItem(
|
||||
resolveRow("KANNO", "간접노무비", "직접노무비", dL, rKanno, (dL * rKanno) / 100),
|
||||
);
|
||||
const kanno = pushRate("KANNO", "간접노무비", "직접노무비", dL, 14.5);
|
||||
const totalLabor = dL + kanno;
|
||||
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"SANBO",
|
||||
"산재보험료",
|
||||
"노무비(직노+간노)",
|
||||
totalLabor,
|
||||
Number(rates.SANBO) ?? 3.56,
|
||||
(totalLabor * (Number(rates.SANBO) ?? 3.56)) / 100,
|
||||
),
|
||||
);
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"KOBO",
|
||||
"고용보험료",
|
||||
"노무비(직노+간노)",
|
||||
totalLabor,
|
||||
Number(rates.KOBO) ?? 1.01,
|
||||
(totalLabor * (Number(rates.KOBO) ?? 1.01)) / 100,
|
||||
),
|
||||
);
|
||||
const kunbo = pushItem(
|
||||
resolveRow(
|
||||
"KUNBO",
|
||||
"건강보험료",
|
||||
"직접노무비",
|
||||
dL,
|
||||
Number(rates.KUNBO) ?? 3.545,
|
||||
(dL * (Number(rates.KUNBO) ?? 3.545)) / 100,
|
||||
),
|
||||
);
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"SLVBO",
|
||||
"노인장기요양보험료",
|
||||
"건강보험료(확정액)",
|
||||
kunbo,
|
||||
Number(rates.SLVBO) ?? 12.95,
|
||||
(kunbo * (Number(rates.SLVBO) ?? 12.95)) / 100,
|
||||
),
|
||||
);
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"YUNBO",
|
||||
"연금보험료",
|
||||
"직접노무비",
|
||||
dL,
|
||||
Number(rates.YUNBO) ?? 4.5,
|
||||
(dL * (Number(rates.YUNBO) ?? 4.5)) / 100,
|
||||
),
|
||||
);
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"TBUGUM",
|
||||
"퇴직공제부금비",
|
||||
"직접노무비",
|
||||
dL,
|
||||
Number(rates.TBUGUM) ?? 0,
|
||||
(dL * (Number(rates.TBUGUM) ?? 0)) / 100,
|
||||
),
|
||||
);
|
||||
pushRate("SANBO", "산재보험료", "노무비(직노+간노)", totalLabor, 3.56);
|
||||
pushRate("KOBO", "고용보험료", "노무비(직노+간노)", totalLabor, 1.01);
|
||||
const kunbo = pushRate("KUNBO", "건강보험료", "직접노무비", dL, 3.545);
|
||||
pushRate("SLVBO", "노인장기요양보험료", "건강보험료(확정액)", kunbo, 12.95);
|
||||
pushRate("YUNBO", "연금보험료", "직접노무비", dL, 4.5);
|
||||
pushRate("TBUGUM", "퇴직공제부금비", "직접노무비", dL, 0);
|
||||
|
||||
const rAnjun = Number(rates.ANJUN) ?? 1.86,
|
||||
anjunBaseAdd = Number(cfg.anjunBaseAdd) || 5349000;
|
||||
const anjunGovDiv = cfg.anjunGovVatExcluded !== false ? govPureMat / 1.1 : govPureMat;
|
||||
const anjunBaseNoGov = scheme === "TA" || scheme === "SA" ? dT : dM + dL;
|
||||
const anjunBaseWithGov = anjunBaseNoGov + anjunGovDiv;
|
||||
const anjunBaseNoGov = scheme === "TA" || scheme === "SA" ? dT : dM + dL,
|
||||
anjunBaseWithGov = anjunBaseNoGov + anjunGovDiv;
|
||||
const anjunNormal = truncInd((anjunBaseWithGov * rAnjun) / 100) + anjunBaseAdd;
|
||||
const anjunCap12 = truncInd(((anjunBaseNoGov * rAnjun) / 100 + anjunBaseAdd) * 1.2);
|
||||
const anjunRaw = cfg.anjunCap12 !== false ? Math.min(anjunNormal, anjunCap12) : anjunNormal;
|
||||
const anjunDesc =
|
||||
scheme === "TA" || scheme === "SA"
|
||||
? "직접공사비+관급원자재/1.1 (+기초액)"
|
||||
: "직재+직노+관급원자재/1.1 (+기초액)";
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"ANJUN",
|
||||
"산업안전보건관리비",
|
||||
scheme === "TA" || scheme === "SA"
|
||||
? "직접공사비+관급원자재/1.1 (+기초액)"
|
||||
: "직재+직노+관급원자재/1.1 (+기초액)",
|
||||
anjunDesc,
|
||||
roundHalfUp(anjunBaseWithGov, 0),
|
||||
rAnjun,
|
||||
anjunRaw,
|
||||
cfg.anjunCap12 !== false ? Math.min(anjunNormal, anjunCap12) : anjunNormal,
|
||||
),
|
||||
);
|
||||
|
||||
if (scheme === "S" || scheme === "SA") {
|
||||
const suBase = dM + totalLabor;
|
||||
for (const [sc, sl, sr] of [
|
||||
["S_SUDO", "수도광열비", Number(rates.S_SUDO) ?? 0.45],
|
||||
["S_BOKRI", "복리후생비", Number(rates.S_BOKRI) ?? 1.15],
|
||||
["S_SOMO", "소모품·사무용품비", Number(rates.S_SOMO) ?? 0.65],
|
||||
["S_TONGSIN", "여비·교통·통신비", Number(rates.S_TONGSIN) ?? 0.85],
|
||||
["S_SEGUM", "세금·공과금", Number(rates.S_SEGUM) ?? 0.55],
|
||||
["S_PEGI", "폐기물처리비", Number(rates.S_PEGI) ?? 0.35],
|
||||
["S_DOSEO", "도서인쇄비", Number(rates.S_DOSEO) ?? 0.25],
|
||||
["S_JIGUP", "지급수수료", Number(rates.S_JIGUP) ?? 0.45],
|
||||
])
|
||||
pushItem(
|
||||
resolveRow(
|
||||
sc,
|
||||
`[수공] ${sl}`,
|
||||
"재료비+노무비(직노+간노)",
|
||||
suBase,
|
||||
sr,
|
||||
(suBase * sr) / 100,
|
||||
),
|
||||
);
|
||||
[
|
||||
["S_SUDO", "수도광열비", 0.45],
|
||||
["S_BOKRI", "복리후생비", 1.15],
|
||||
["S_SOMO", "소모품·사무용품비", 0.65],
|
||||
["S_TONGSIN", "여비·교통·통신비", 0.85],
|
||||
["S_SEGUM", "세금·공과금", 0.55],
|
||||
["S_PEGI", "폐기물처리비", 0.35],
|
||||
["S_DOSEO", "도서인쇄비", 0.25],
|
||||
["S_JIGUP", "지급수수료", 0.45],
|
||||
].forEach(([sc, sl, defR]) =>
|
||||
pushRate(sc, `[수공] ${sl}`, "재료비+노무비(직노+간노)", suBase, defR),
|
||||
);
|
||||
} else {
|
||||
const rKita = Number(rates.KITA) ?? 6.0,
|
||||
kitaBase = scheme === "TA" ? dT : dM + totalLabor;
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"KITA",
|
||||
"기타경비",
|
||||
scheme === "TA" ? "직접공사비(실적일반)" : "재료비+노무비(직노+간노)",
|
||||
kitaBase,
|
||||
rKita,
|
||||
(kitaBase * rKita) / 100,
|
||||
),
|
||||
pushRate(
|
||||
"KITA",
|
||||
"기타경비",
|
||||
scheme === "TA" ? "직접공사비(실적일반)" : "재료비+노무비(직노+간노)",
|
||||
scheme === "TA" ? dT : dM + totalLabor,
|
||||
6.0,
|
||||
);
|
||||
}
|
||||
|
||||
const hwanBase = scheme === "TA" ? dT : dM + dL + dS,
|
||||
hwanDesc = scheme === "TA" ? "직접공사비(실적일반)" : "재료비+직접노무비+산출경비";
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"HWAN",
|
||||
"환경보전비",
|
||||
hwanDesc,
|
||||
hwanBase,
|
||||
Number(rates.HWAN) ?? 0.8,
|
||||
(hwanBase * (Number(rates.HWAN) ?? 0.8)) / 100,
|
||||
),
|
||||
);
|
||||
pushItem(
|
||||
resolveRow(
|
||||
"EQIP",
|
||||
"건설기계대여대금 지급보증",
|
||||
hwanDesc,
|
||||
hwanBase,
|
||||
Number(rates.EQIP) ?? 0.4,
|
||||
(hwanBase * (Number(rates.EQIP) ?? 0.4)) / 100,
|
||||
),
|
||||
);
|
||||
pushRate("HWAN", "환경보전비", hwanDesc, hwanBase, 0.8);
|
||||
pushRate("EQIP", "건설기계대여대금 지급보증", hwanDesc, hwanBase, 0.4);
|
||||
|
||||
const indirectExpenseSum = items
|
||||
.filter((x) => x.code !== "KANNO")
|
||||
@@ -542,8 +447,8 @@ const StmateCalc = (() => {
|
||||
|
||||
const rIlban = Number(rates.ILBAN) ?? 6.0,
|
||||
ilbanBase = scheme === "SA" ? dT + kanno : pureConstructionCost;
|
||||
const ilbanDesc = scheme === "SA" ? "직접공사비+간접노무비(실적수공)" : "순공사원가(재+노+경)";
|
||||
const ilbanAmt = truncInd((ilbanBase * rIlban) / 100);
|
||||
const ilbanDesc = scheme === "SA" ? "직접공사비+간접노무비(실적수공)" : "순공사원가(재+노+경)",
|
||||
ilbanAmt = truncInd((ilbanBase * rIlban) / 100);
|
||||
|
||||
const rIyun = Number(rates.IYUN) ?? 15.0;
|
||||
const iyunBase =
|
||||
@@ -595,43 +500,56 @@ const StmateCalc = (() => {
|
||||
Number(cfg.govRoundDigits) ?? -3,
|
||||
)
|
||||
: 0;
|
||||
const grandTotal = contractFinal + govFinal;
|
||||
const mkLine = (key, label, formula, baseAmt, rate, amount) =>
|
||||
Object.fromEntries([
|
||||
["key", key],
|
||||
["label", label],
|
||||
["formula", formula],
|
||||
["baseAmt", baseAmt],
|
||||
["rate", rate],
|
||||
["amount", amount],
|
||||
]);
|
||||
const totStr = "재료비 + 노무비 + 경비",
|
||||
iyunStr = `${iyunDesc} (보정 -${fmt(pretaxProfitAdj)})`,
|
||||
supStr = "순공사원가 + 일반관리비 + 이윤",
|
||||
finStr = "공급가액 + 부가가치세 (천원절사)",
|
||||
govStr = `관급원자재(${fmt(boqRes.govRawTotal)}) + 수수료(${fmt(boqRes.govFeeTotal)})`;
|
||||
const pCost = pureConstructionCost;
|
||||
const lines = [
|
||||
mkLine("DIRECT_M", "직접재료비", "내역서 재료비 합계", dM, null, dM),
|
||||
mkLine("DIRECT_L", "직접노무비", "내역서 직접노무비 합계", dL, null, dL),
|
||||
mkLine("DIRECT_S", "산출경비", "내역서 산출경비 합계", dS, null, dS),
|
||||
mkLine("DIRECT_COST", "직접공사비 계", "직재 + 직노 + 산출경비", dT, null, dT),
|
||||
...items.map((it) => mkLine(it.key, it.label, it.formula, it.baseAmt, it.rate, it.amount)),
|
||||
mkLine("TOTAL_COST", "순공사원가 계", totStr, pCost, null, pCost),
|
||||
mkLine("ILBAN", "일반관리비", ilbanDesc, ilbanBase, rIlban, ilbanAmt),
|
||||
mkLine("IYUN", "이윤 (끝전보정 후)", iyunStr, iyunBase, rIyun, iyunFinal),
|
||||
mkLine("SUPPLY_VAL", "총원가 (공급가액)", supStr, supplyFinal, null, supplyFinal),
|
||||
mkLine("VAT", "부가가치세", "공급가액 × 10%", supplyFinal, 10, vatFinal),
|
||||
mkLine("FINAL_SUPPLY", "도급공사비", finStr, contractBeforeAdj, null, contractFinal),
|
||||
mkLine("GOV_MAT", "관급자재대", govStr, boqRes.govCombinedRaw, null, govFinal),
|
||||
mkLine("GRAND_TOTAL", "총공사비 (COSTW1)", "도급 + 관급", grandTotal, null, grandTotal),
|
||||
];
|
||||
|
||||
return {
|
||||
scheme,
|
||||
dM,
|
||||
dL,
|
||||
dS,
|
||||
dT,
|
||||
kanno,
|
||||
totalLabor,
|
||||
indirectExpenseSum,
|
||||
totalExpense,
|
||||
pureConstructionCost,
|
||||
ilbanBase,
|
||||
ilbanDesc,
|
||||
ilbanAmt,
|
||||
iyunBase,
|
||||
iyunDesc,
|
||||
iyunRaw,
|
||||
pretaxProfitAdj,
|
||||
iyunFinal,
|
||||
supplyBeforeAdj,
|
||||
vatBeforeAdj,
|
||||
contractBeforeAdj,
|
||||
diffTotal,
|
||||
supplyFinal,
|
||||
vatFinal,
|
||||
contractFinal,
|
||||
govRawTotal: boqRes.govRawTotal,
|
||||
govFeeTotal: boqRes.govFeeTotal,
|
||||
govFinal,
|
||||
grandTotal: contractFinal + govFinal,
|
||||
items,
|
||||
itemMap,
|
||||
};
|
||||
const out = { scheme, dM, dL, dS, dT, kanno, totalLabor };
|
||||
Object.assign(out, { indirectExpenseSum, totalExpense, pureConstructionCost: pCost });
|
||||
Object.assign(out, { ilbanBase, ilbanDesc, ilbanAmt, iyunBase, iyunDesc, iyunRaw });
|
||||
Object.assign(out, { pretaxProfitAdj, iyunFinal, supplyBeforeAdj, vatBeforeAdj });
|
||||
Object.assign(out, { contractBeforeAdj, diffTotal, supplyFinal, vatFinal, contractFinal });
|
||||
Object.assign(out, { govRawTotal: boqRes.govRawTotal, govFeeTotal: boqRes.govFeeTotal });
|
||||
Object.assign(out, { govFinal, grandTotal, items, itemMap, lines });
|
||||
Object.assign(out, { DIRECT_COST: dT, KANNO: kanno, IND_EXP: kanno + indirectExpenseSum });
|
||||
Object.assign(out, { TOTAL_COST: pCost, ILBAN: ilbanAmt, IYUN_INIT: iyunRaw });
|
||||
Object.assign(out, { PROFIT_ADJ: -pretaxProfitAdj, IYUN: iyunFinal, SUPPLY_VAL: supplyFinal });
|
||||
return Object.assign(out, {
|
||||
VAT: vatFinal,
|
||||
FINAL_SUPPLY: contractFinal,
|
||||
GOV_MAT: govFinal,
|
||||
GRAND_TOTAL: grandTotal,
|
||||
});
|
||||
}
|
||||
|
||||
// 8. K_ORD 등록·차수내리기 전이 규칙 시뮬레이션 (STMATE_REA_최종보고서 §4.1)
|
||||
function evalKOrdTransition(pclass, regMode, flag, currentK) {
|
||||
const K = Number(currentK) || 1;
|
||||
const nextK =
|
||||
@@ -645,30 +563,113 @@ const StmateCalc = (() => {
|
||||
return { nextK, downK: K - (K > 5 ? K - 4 : 1) };
|
||||
}
|
||||
|
||||
function calculateAll(data) {
|
||||
const resources = calcResources(data);
|
||||
const machines = calcMachines(data, resources.map);
|
||||
const sanCal = calcSanCal(data, machines.map);
|
||||
const ilwi = calcIlwi(data, resources.map, machines.map, sanCal.map);
|
||||
const boqOrig = calcBoq(data, resources.map, machines.map, sanCal.map, ilwi.map, false);
|
||||
const kanjubOrig = calcKanjub(data, boqOrig, null);
|
||||
const boqRev = calcBoq(data, resources.map, machines.map, sanCal.map, ilwi.map, true);
|
||||
const kanjubRev = calcKanjub(data, boqRev, kanjubOrig);
|
||||
return { resources, machines, sanCal, ilwi, boqOrig, kanjubOrig, boqRev, kanjubRev };
|
||||
function createDefaultState() {
|
||||
const mkRes = (id, name, spec, unit, kind, val) => {
|
||||
const r = { id, code: id, name, spec, unit, kind };
|
||||
Object.assign(r, { candidates: [val, 0, 0, 0, 0], adoptMode: "manual", manualPrice: val });
|
||||
return Object.assign(r, { convertHourly: false, isUsd: false });
|
||||
};
|
||||
const mkBoq = (id, code, name, spec, unit, refId, rowType, _recno) => {
|
||||
const b = { id, code, name, spec, unit, refId };
|
||||
return Object.assign(b, { rowType, qty: 1, changedQty: 1, _recno });
|
||||
};
|
||||
const ratePairs =
|
||||
"KANNO:14.5,SANBO:3.56,KOBO:1.01,KUNBO:3.545,SLVBO:12.95,YUNBO:4.5,TBUGUM:0,ANJUN:1.86,KITA:6,HWAN:0.8,EQIP:0.4,ILBAN:6,IYUN:15,S_SUDO:0.45,S_BOKRI:1.15,S_SOMO:0.65,S_TONGSIN:0.85,S_SEGUM:0.55,S_PEGI:0.35,S_DOSEO:0.25,S_JIGUP:0.45";
|
||||
const rates = Object.fromEntries(
|
||||
ratePairs.split(",").map((p) => [p.split(":")[0], Number(p.split(":")[1])]),
|
||||
);
|
||||
const revKeys = "KANNO,SANBO,KOBO,KUNBO,SLVBO,YUNBO,TBUGUM,ANJUN,KITA,HWAN,EQIP".split(",");
|
||||
const revModes = Object.fromEntries(
|
||||
revKeys.map((k) => [
|
||||
k,
|
||||
["KUNBO", "SLVBO", "YUNBO"].includes(k) ? "carry_original" : "recalculated",
|
||||
]),
|
||||
);
|
||||
const projectMeta = {
|
||||
pname: "2024년 산림유역관리사업(기번41, 국비)",
|
||||
pnum: "2024년 단가",
|
||||
pcode: "11",
|
||||
};
|
||||
Object.assign(projectMeta, { pclass: 0, kOrd: 0, tcost1: 781283415, costw1: 1414283000 });
|
||||
const sysConfig = {
|
||||
rxFormula: "1/8*16/12*25/20",
|
||||
condCodes: "L00008, L00009, L00010",
|
||||
usdRate1: 1350,
|
||||
usdRate2: 1400,
|
||||
};
|
||||
Object.assign(sysConfig, {
|
||||
useChangedUsd: false,
|
||||
ecdDigits: 0,
|
||||
chnMode: "bimok",
|
||||
optOrder: "SML",
|
||||
});
|
||||
sysConfig.kOrdSim = { pclass: 7, regMode: 7, flag: 0, currentK: 2 };
|
||||
const kanjubConfig = {
|
||||
scheme: "T",
|
||||
indirectTruncDigits: 0,
|
||||
anjunBaseAdd: 5349000,
|
||||
anjunGovVatExcluded: true,
|
||||
};
|
||||
Object.assign(kanjubConfig, {
|
||||
anjunCap12: true,
|
||||
vatMode: "supply_10",
|
||||
totJeolDigits: -3,
|
||||
enableProfitEndAdj: true,
|
||||
});
|
||||
Object.assign(kanjubConfig, { govRoundMode: "up", govRoundDigits: -3, rates, revModes });
|
||||
const dName = "2024년 산림유역관리 본공사 종합";
|
||||
const dLines = ["B_MAT", "B_LAB", "B_EXP"].map((refId) => ({
|
||||
refId,
|
||||
qty: 1,
|
||||
halPercent: 0,
|
||||
devd: false,
|
||||
}));
|
||||
const d00014 = { id: "D00014", code: "D00014", name: dName, rawName: dName };
|
||||
Object.assign(d00014, { prefix: "D", spec: "산림토목", unit: "식", lines: dLines });
|
||||
const b1Name = "2024년 산림유역관리 본공사 일체";
|
||||
return {
|
||||
projectMeta,
|
||||
sysConfig,
|
||||
resources: [
|
||||
mkRes("B_MAT", "봉화군 산림유역 직접재료비", "식", "식", "재료비", 191867113),
|
||||
mkRes("B_LAB", "봉화군 산림유역 직접노무비", "식", "식", "노무비", 426802539),
|
||||
mkRes("B_EXP", "봉화군 산림유역 산출경비", "식", "식", "경비", 162613763),
|
||||
mkRes("G_MAT", "봉화군 관급자재 원자재 합계", "C4 제외구역", "식", "재료비", 142783280),
|
||||
mkRes("G_FEE", "봉화군 관급 조달수수료", "C4 제외구역", "식", "경비", 771029),
|
||||
],
|
||||
machines: [],
|
||||
sanItems: [],
|
||||
ilwiItems: [d00014],
|
||||
boqItems: [
|
||||
mkBoq("BOQ_1", "D00014", b1Name, "산림토목", "식", "D00014", "normal", 1),
|
||||
mkBoq("BOQ_2", "G_MAT", "[제외 C4] 관급자재 원자재", "관급", "식", "G_MAT", "gov_mat", 2),
|
||||
mkBoq("BOQ_3", "G_FEE", "[제외 C4] 관급 조달수수료", "관급", "식", "G_FEE", "gov_fee", 3),
|
||||
],
|
||||
kanjubConfig,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
KINDS,
|
||||
KIND_KEYS,
|
||||
roundDown,
|
||||
roundUp,
|
||||
roundHalfUp,
|
||||
roundBanker,
|
||||
evalWageFormulaLeftToRight,
|
||||
evalQFormula,
|
||||
evalKOrdTransition,
|
||||
calculateAll,
|
||||
};
|
||||
function calculateAll(data) {
|
||||
const resources = calcResources(data),
|
||||
machines = calcMachines(data, resources.map),
|
||||
sanCal = calcSanCal(data, machines.map);
|
||||
const ilwi = calcIlwi(data, resources.map, machines.map, sanCal.map);
|
||||
const boqOrig = calcBoq(data, resources.map, machines.map, sanCal.map, ilwi.map, false),
|
||||
kanjubOrig = calcKanjub(data, boqOrig, null);
|
||||
const boqRev = calcBoq(data, resources.map, machines.map, sanCal.map, ilwi.map, true),
|
||||
kanjubRev = calcKanjub(data, boqRev, kanjubOrig);
|
||||
const base = { resources, machines, sanCal, ilwi, boqOrig, kanjubOrig };
|
||||
Object.assign(base, { boqRev, kanjubRev, resList: resources.list });
|
||||
return Object.assign(base, { boqComputed: boqOrig, kanjubComputed: kanjubOrig });
|
||||
}
|
||||
|
||||
const evalLeftToRight = (exprStr) => evalWageFormulaLeftToRight(1, exprStr).coeff || 0;
|
||||
const api = { KINDS, KIND_KEYS, num, fmt, evalFormula, evalLeftToRight };
|
||||
Object.assign(api, { roundDown, roundUp, roundHalfUp, roundBanker });
|
||||
Object.assign(api, { evalWageFormulaLeftToRight, evalQFormula, evalKOrdTransition });
|
||||
return Object.assign(api, { createDefaultState, calculateAll, computeAll: calculateAll });
|
||||
})();
|
||||
|
||||
if (typeof window !== "undefined") window.STMATE_CALC = StmateCalc;
|
||||
if (typeof globalThis !== "undefined") globalThis.STMATE_CALC = StmateCalc;
|
||||
if (typeof module !== "undefined" && module.exports) module.exports = StmateCalc;
|
||||
|
||||
Reference in New Issue
Block a user