/** * STMATE w 8.0 — 초기값(wM_Set_Sys) · 단가목록표(wM_JabPum) · 대가표/산근 편집기(wM_Edit_iLWi · wM_Edit_San · COSTN.SMT) * 근거: 22_UI_폼_분석.md, 23_COSTN_SMT_전수파싱.md, 26_편집기와_사이클식.md, 28_변환_수량산출_식언어.md, 34_화면구조_정리.md */ (function (global) { "use strict"; const Calc = global.STMATE_CALC; const fmt = Calc.fmt; const num = Calc.num; const KIND_LABELS = { ALL: "모든단가", L: "노무비(L)", M: "재료비(M)", S: "경비(S)", B: "일위대가(B)", X: "중기비(X)", D: "단가산출(D)", W: "견적/표준시장(W)", }; /** * COSTN.SMT 텍스트를 실시간 평가하여 Q식, Cm식, 참조(&X00006L/M/S&) 단가 및 비목별 합계 산출 */ function evaluateSmtFormulaText(smtText, ctx) { const rawCostn = (ctx.dbfData && ctx.dbfData.costn_rows) || []; const byCode = {}; rawCostn.forEach((r) => { const c = String(r.CODE || "").trim(); if (c) byCode[c] = r; }); // state.resources / machines 동기화 반영 (ctx.computed.resList || []).forEach((r) => { if (!r.isSynthetic && byCode[r.code]) { byCode[r.code].TAMT1 = r.appliedPrice; if (r.kind === "노무비") byCode[r.code].LAMT1 = r.appliedPrice; if (r.kind === "재료비") byCode[r.code].MAMT1 = r.appliedPrice; if (r.kind === "경비") byCode[r.code].SAMT1 = r.appliedPrice; } }); const vars = {}; const linesOut = []; let sumL = 0, sumM = 0, sumS = 0; function evalMathWithVars(exprStr) { let s = String(exprStr || "") .replace(/'[^']*'/g, "") .trim(); if (!s) return 0; // 변수 치환 (긴 변수명 우선) Object.keys(vars) .sort((a, b) => b.length - a.length) .forEach((k) => { const re = new RegExp("\\b" + k + "\\b", "g"); s = s.replace(re, 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; } } const rawLines = String(smtText || "").split(/\r?\n/); rawLines.forEach((rawLn, idx) => { const ln = rawLn.trim(); if (!ln) return; // 1) 변수 대입 (q=0.4 , K=0.7 , E=0.45-0.05= , f=1/1.175 , Cm=... , Q=...) const assignRe = /\b([A-Za-z]\w*)\s*(?:'[^']*')?\s*=\s*([^,\r\n'=]+)/g; let m; const assignedHere = []; while ((m = assignRe.exec(ln)) !== null) { const vName = m[1].trim(); const vExpr = m[2].trim(); const val = evalMathWithVars(vExpr); if (val > 0 || vExpr === "0") { vars[vName] = Math.round(val * 100) / 100; assignedHere.push(`${vName} = ${vars[vName]}`); } } // 2) 자원 참조 (&X00026L& / Q = , &M00080& * 0.00438'㎥' = ) 또는 주석형 단가 요약 const refMatch = ln.match(/&([A-Z]\d{4,5})([LMS])?&(.*)/); const commentCostMatch = !refMatch && ln.match(/(재료비|노무비|경\s*비)\s*[:=]\s*([\d,]+)/); let refInfo = null; if (refMatch) { const refCode = refMatch[1]; const compTag = refMatch[2] || refCode[0]; const tailExpr = refMatch[3] || ""; const targetRow = byCode[refCode] || {}; let baseUnitPrice = num(targetRow.TAMT1); if (compTag === "L") baseUnitPrice = num(targetRow.LAMT1) || baseUnitPrice; else if (compTag === "M") baseUnitPrice = num(targetRow.MAMT1) || baseUnitPrice; else if (compTag === "S") baseUnitPrice = num(targetRow.SAMT1) || baseUnitPrice; const cleanTail = tailExpr.split("=")[0].split(":=")[0]; const fullExpr = String(baseUnitPrice) + " " + cleanTail; const lineAmt = evalMathWithVars(fullExpr); const roundedAmt = Math.floor(lineAmt * 10) / 10; if (compTag === "L" || refCode.startsWith("L")) sumL += roundedAmt; else if (compTag === "S" || refCode.startsWith("S")) sumS += roundedAmt; else sumM += roundedAmt; refInfo = { refCode, compTag, refName: targetRow.NAME || refCode, baseUnitPrice, calcAmt: roundedAmt, }; } else if (commentCostMatch) { const cKind = commentCostMatch[1].replace(/\s+/g, ""); const cVal = num(commentCostMatch[2], 0); const cTag = cKind === "노무비" ? "L" : cKind === "경비" ? "S" : "M"; if (cTag === "L") sumL += cVal; else if (cTag === "S") sumS += cVal; else sumM += cVal; refInfo = { refCode: cKind, compTag: cTag, refName: "단가 직접지정", baseUnitPrice: cVal, calcAmt: cVal, }; } linesOut.push({ lineNo: idx + 1, raw: rawLn, assigned: assignedHere.join(", "), refInfo, }); }); return { vars, linesOut, sumM: Math.floor(sumM), sumL: Math.floor(sumL), sumS: Math.floor(sumS), sumT: Math.floor(sumM) + Math.floor(sumL) + Math.floor(sumS), }; } /** * 1. wM_Set_Sys (초기값 입력 및 수정 화면) */ function renderWMSetSys(ctx) { const sys = ctx.state.sysConfig; const prj = (ctx.dbfData && ctx.dbfData.prjlst) || {}; const evalRx = Calc.evalLeftToRight(sys.rxFormula); return `
※ STMATE 고유 파서 검증: 1/8*16/12*25/20 → 좌→우 순차 평가 0.20833333 (기계운전원 일급 267,360원 × 0.20833333 = 55,700원/hr 정확 재현)
| No | 코드(CODE) | 구분 | 명칭 (NAME) | 규격 (SIZE) | 단위 | 재료단가(MAMT1) | 노무단가(LAMT1) | 경비단가(SAMT1) | 합계단가(TAMT1) | 1단가(조달) | 2단가(물가) | 3단가(거래) | 산근/대가 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ${i + 1} | ${code} | ${p} | ${r.NAME || ""} | ${r.SIZE || ""} | ${r.UNIT || ""} | ${fmt(t1)} | ${num(r.AMT_11) ? fmt(r.AMT_11) : "-"} | ${num(r.AMT_21) ? fmt(r.AMT_21) : "-"} | ${num(r.AMT_31) ? fmt(r.AMT_31) : "-"} | ${ hasMemo || p === "B" || p === "D" || p === "X" ? `` : "-" } |
| 코드 | 품명 (NAME) | 규격 | 단위 | 합계단가 | SMT |
|---|---|---|---|---|---|
| ${c} | ${r.NAME || ""} | ${r.SIZE || ""} | ${r.UNIT || ""} | ${fmt(num(r.TAMT1))} | ${hasM ? "📝" : "-"} |
&X00026L& 참조 · Q=3600*q*K*f*E/Cm · :=A01 문법 실시간 해석
DB 저장단가: 재 ${fmt(num(selRow.MAMT1))} / 노 ${fmt(num(selRow.LAMT1))} / 경 ${fmt(num(selRow.SAMT1))} / 계 ${fmt(num(selRow.TAMT1))}원
| 행 | 산식 원문 (COSTN.SMT Line) | 변수 대입/평가 | 자원참조(&코드&) | 참조단가 | 산출금액 |
|---|---|---|---|---|---|
| ${ln.lineNo} | ${ln.raw} | ${ln.assigned || ""} | ${ln.refInfo ? `${ln.refInfo.refCode}[${ln.refInfo.compTag}] (${ln.refInfo.refName})` : ""} | ${ln.refInfo ? fmt(ln.refInfo.baseUnitPrice) : ""} | ${ln.refInfo ? fmt(ln.refInfo.calcAmt, 1) : ""} |
[wM_Boxa] 5개 가격지 단가 비교·일괄 채택 — 자재 후보단가(AMT_11~51) 중 지정 슬롯 또는 최저가를 적용단가(AMT_61/MAMT1)로 일괄 채택합니다.
[wM_Edt_Hal] 기준단가 비율 일괄 수정 — 적용단가 = 기준단가 × 비율(%) (적용단가 0원 및 보호항목 UC=T 제외).
[STMATE 표준 카탈로그 브라우저] — 30_원자료에서 추출한 표준코드 ${cat.codes.length}종 · 일위대가 레시피 ${cat.recipes.length}종 · 특수 단가산출 ${cat.special_san.length}종
| 호표 | 명칭 | 단위 | Q식 | 편집기로 복사 |
|---|---|---|---|---|
| #${s["호표"]} | ${s["명칭"]} | ${s["단위"]} | ${(s["인자"] && s["인자"].Q) || ""} |
| 구분 | 명칭 | 규격 | 단위 | 구성수 |
|---|---|---|---|---|
| ${r["종류"]} | ${r["명칭"]} | ${r["규격"] || ""} | ${r["단위"] || ""} | ${(r["구성"] || []).length}행 |