/** * STMATE w 8.0 — 공사목록표(wPrjList) · 계층 설계내역서(wBoq) · 내역도구 모달(wITNum/wBoqRate/wM_Rate/wExpnd/wQtyList) * 근거: REA 09_보조분석 resource-TWPRJLIST.dfm.json, 16번 §4.2, 22번 §4, 28번 §2.2, 34번 §2.1 */ (function (global) { "use strict"; const Calc = global.STMATE_CALC; const fmt = Calc.fmt; const num = Calc.num; const PCLASS_NAMES = { 0: "일반토목/산림토목", 1: "건축공사", 2: "조경공사", 3: "산업설비", 4: "전기/통신", 7: "전문/설계변경", }; const KORD_BADGES = { 0: "설계내역(0)", 1: "계약내역(1)", 2: "1차변경(2)", 3: "실행예산(3)", 4: "기성내역(4)", }; function isStructCode(bd) { return /^(T\d|E\d|C\d|:)$/.test(String(bd || "").trim()); } /** * BOQ11~14.DBF 전수 행(T1~T6 제목, E1~E6 소계/계/총계/총합계, C* 제외금액, 말단 공종행)을 * 실시간 단가·수량과 동기화하여 원단위까지 재집계 */ function evaluateFullBoqRows(ctx) { const rawRows = (ctx.dbfData && ctx.dbfData.boq_rows) || []; const compRes = ctx.computed; const leafByRecno = {}; const leafByCode = {}; (compRes.boqComputed.rows || []).forEach((r) => { if (r._recno !== undefined) leafByRecno[r._recno] = r; if (r.code) leafByCode[r.code] = r; }); // C* 제외구역 판별 (ENDCODE == SORTCODE) const exclSet = new Set(); rawRows.forEach((r, idx) => { const bd = String(r.BDCODE1 || "").trim(); if (bd.startsWith("C")) { const endCode = r.ENDCODE; const startIdx = rawRows.findIndex((q) => q.SORTCODE === endCode); if (startIdx >= 0) { for (let k = Math.min(startIdx, idx); k <= Math.max(startIdx, idx); k++) { exclSet.add(k); } } } }); // 1차: 말단 행 단가·금액 동기화 const rows = rawRows.map((r, idx) => { const bd = String(r.BDCODE1 || "").trim(); const recno = r._recno !== undefined ? r._recno : idx; const leaf = leafByRecno[recno] || leafByCode[bd]; const isStruct = isStructCode(bd) || !bd; if (!isStruct && leaf) { return { ...r, _idx: idx, _recno: recno, _rowClass: exclSet.has(idx) ? "boq-row-excl" : "boq-row-leaf", _isExcl: exclSet.has(idx), QTY1: leaf.qty, MAMT1: leaf.mUnit, LAMT1: leaf.lUnit, SAMT1: leaf.sUnit, TAMT1: leaf.tUnit, M_AMT1: leaf.mAmt, L_AMT1: leaf.lAmt, S_AMT1: leaf.sAmt, T_AMT1: leaf.tAmt, }; } let rowClass = ""; if (bd.startsWith("T")) rowClass = "boq-row-T"; else if (bd.startsWith("E")) rowClass = "boq-row-E"; else if (bd.startsWith("C")) rowClass = "boq-row-C"; return { ...r, _idx: idx, _recno: recno, _rowClass: rowClass, _isExcl: exclSet.has(idx), QTY1: num(r.QTY1), MAMT1: num(r.MAMT1), LAMT1: num(r.LAMT1), SAMT1: num(r.SAMT1), TAMT1: num(r.TAMT1), M_AMT1: num(r.M_AMT1), L_AMT1: num(r.L_AMT1), S_AMT1: num(r.S_AMT1), T_AMT1: num(r.T_AMT1), }; }); // 2차: T*(공종제목) 및 E*(소계/계/총계/총합계), C*(제외금액 총계) 구간 합산 동기화 rows.forEach((r, idx) => { const bd = String(r.BDCODE1 || "").trim(); if (!bd.startsWith("T") && !bd.startsWith("E") && !bd.startsWith("C")) return; const endCode = r.ENDCODE; const partnerIdx = rows.findIndex((q, j) => j !== idx && q.SORTCODE === endCode); if (partnerIdx < 0 && bd !== "E5" && bd !== "T5") return; const sIdx = partnerIdx >= 0 ? Math.min(idx, partnerIdx) : 0; const eIdx = partnerIdx >= 0 ? Math.max(idx, partnerIdx) : rows.length - 1; const isExclSummary = bd.startsWith("C") || exclSet.has(idx); let sumM = 0, sumL = 0, sumS = 0, sumT = 0; for (let k = sIdx; k <= eIdx; k++) { const sub = rows[k]; const sbd = String(sub.BDCODE1 || "").trim(); if (!sbd || isStructCode(sbd)) continue; if (!isExclSummary && sub._isExcl) continue; sumM += num(sub.M_AMT1); sumL += num(sub.L_AMT1); sumS += num(sub.S_AMT1); sumT += num(sub.T_AMT1); } if (sumT > 0 || partnerIdx >= 0) { r.M_AMT1 = sumM; r.L_AMT1 = sumL; r.S_AMT1 = sumS; r.T_AMT1 = sumT; } }); return rows; } /** * 1. TWPRJLIST (wPrjList 공사목록표 화면) * 상단: 등록정보 수정 | 제잡비 계산 | 해당분 수정(wBoq) | 복사등록 | 해당분만 압축(.STC) | 내역서 미리보기 | 정렬 * 중단: RGrdPrjList (PC 내 9개 프로젝트 + 현재 열린 PRJLST.DBF) * 하단: RDbGrid_Boq (선택 프로젝트의 실시간 내역서 미리보기) */ function renderWPrjList(ctx) { const sources = (ctx.sourcesData && ctx.sourcesData.projects) || []; const curPath = ctx.currentSourcePath || ""; const prj = (ctx.dbfData && ctx.dbfData.prjlst) || {}; const comp = ctx.computed; const directCost = comp.boqComputed.totals.directT; const totalCost = comp.kanjubComputed.GRAND_TOTAL; const boqRows = evaluateFullBoqRows(ctx); const showPreview = ctx.uiState.showBoqPreview !== false; return `
| 순번 | 구분 | 공사번호(PNUM) | 공사명 (PNAME / 아카이브명) | 코드(PCODE) | 공사종류(PCLASS) | 단계(K_ORD) | 직공비(TCOST1) | 총공사비(COSTW1) | 작업 |
|---|---|---|---|---|---|---|---|---|---|
| ${idx + 1} | ${src.kind.toUpperCase()} | ${pnum} | ${src.name} | BOQ${pcode} | ${pcls} | ${kord} | ${tcost} | ${costw} |
| 행번 | 공종(ITNUM) | 명칭 (NAME1) | 규격 (SIZE1) | 수량 (QTY1) | 단위 | 합계단가 (TAMT1) | 합계금액 (T_AMT1) | 비고 (BDCODE1) |
|---|---|---|---|---|---|---|---|---|
| ${i + 1} | ${r.ITNUM || ""} | ${r.NAME1 || ""} | ${r.SIZE1 || ""} | ${r.QTY1 ? fmt(r.QTY1, 2) : ""} | ${r.UNIT1 || ""} | ${r.TAMT1 ? fmt(r.TAMT1) : ""} | ${r.T_AMT1 ? fmt(r.T_AMT1) : ""} | ${r.BDCODE1 || ""} |
| No | 공종(ITNUM) | 코드(BDCODE1) | 명칭 (NAME1) | 규격 (SIZE1) | 단위 | 수량(QTY1) | 재료비단가 | 노무비단가 | 경비단가 | 합계단가 | 재료비금액 | 노무비금액 | 경비금액 | 합계금액(T_AMT1) | 보관함 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ${r._idx + 1} | ${ isLeaf ? `${bd}` : `${bd}` } | ${r.SIZE1 || ""} | ${r.UNIT1 || ""} | ${ isLeaf ? `` : "" } | ${r.MAMT1 ? fmt(r.MAMT1) : ""} | ${r.LAMT1 ? fmt(r.LAMT1) : ""} | ${r.SAMT1 ? fmt(r.SAMT1) : ""} | ${r.TAMT1 ? fmt(r.TAMT1) : ""} | ${r.M_AMT1 ? fmt(r.M_AMT1) : ""} | ${r.L_AMT1 ? fmt(r.L_AMT1) : ""} | ${r.S_AMT1 ? fmt(r.S_AMT1) : ""} | ${r.T_AMT1 ? fmt(r.T_AMT1) : ""} | ${ isLeaf ? `` : "" } |
자주 쓰는 공종을 보관함 (1)~(3)에 담아두고 현재 내역서에 즉시 삽입합니다.
${ (pockets[pocketIdx] || []).length === 0 ? `| 코드 | 품명 | 수량 | 삽입 |
|---|---|---|---|
| ${p.code} | ${p.name} | ${fmt(p.qty, 2)} |
[wITNum] 내역서 공종번호 자동입력 — 대공종(T4)/중공종(T2)/말단 공종행에 번호체계를 일괄 부여합니다.
[wBoqRate] 내역 항목 할증율 일괄 적용 — 전체 또는 비목별(재·노·경) 할증 비율(%)을 적용합니다.
[wM_Rate] 선택 범위 수량·비목 단수 일괄 보정 — 절사/반올림 및 소수점 자리수(0~4위)를 일괄 보정합니다.
[wExpnd / wM_Expnd] 일위대가·단가산출 내역서 풀어넣기 — 선택한 호표의 재료·노무·경비 구성행을 내역서에 직접 전개합니다.
[wQtyList] 수량산출서(QCODE.DBF) 코드별 수량 연결·집계 — 수량산출식 결과를 내역서 QTY1에 즉시 반영합니다.
| 코드 | 공종명 | 현재 내역수량 | 산출근거 수식 (예: 3.46*1000) | 반영 |
|---|---|---|---|---|
| ${r.code} | ${r.name} | ${fmt(r.qty, 2)} |