/* -*- coding: utf-8 -*- */ /** * 보고서 양식 변형 (report_forms.js) — 원본 출력 견본이 없는 SET_REP 서식 * 0303 일위대가표(수공양식) · 0605 분할내역서 · 0607 사·관급내역서 · 0609 총괄집계표 · 0613·0614 견적내역서 · * 0615 견적단가대비표 · 0606·0654·0656 내역서 변형 · 0651·0655 구.시도 · 0652·0653·0672 총괄 변형 · * 0675~0677 재료비·노무비·중장비 집계표 · 0671·0678·0679 투입계획 · 0695 간이공사비 산출명세서 * 근거: 지식DB STmate/REA 분석 35(보고서양식_수량산출서) — ST_Prt 인쇄 분기(0x44fc34 · 서식 번호 → Prn_* 함수) · * 인쇄 양식 DFM(TPM_*: 머리 칸 글·차례·DB 칸 이름) · Prn_Won_A 구성비(T_PER = 금액 ÷ 총액 × 100 · 0x48c6ab) · * old/20_분석 20 §3(SET_REP FLD_N·FLD_W) · 24 §2.4(AMTTOHAN) * 판정: 부분 — 열 차례·머리 글 = 인쇄 양식 DFM · 0605·0615 값 = ST_P_XLS 엑셀판(BOQ.DBF 1·2 칸) · 회사분 등은 후보 */ const StmateReportForms = (() => { const g = typeof globalThis !== "undefined" ? globalThis : {}; const C = g.STMATE_REPORT_CORE || require("./report_core.js"); const B = g.STMATE_REPORT_BOQ || require("./report_boq.js"); const Co = g.STMATE_REPORT_COLL || require("./report_coll.js"); const In = g.STMATE_REPORT_INIT || require("./report_init.js"); const { num, str, code, half, trunc } = C; /** * 인쇄 분기(ST_Prt 0x44fc34 · 분기 값 = 20000 + 서식 번호) — 서식 → [인쇄 함수, 엑셀 함수, 모듈] * 0106 = 0104 와 같은 갈래(Prn_iLMok·Prn_SanMok 중 설정 +0x173f) · 0671·0678·0679 = Prn_Nae_Plan_{A|K}_{X|M|L} */ const FORM_PROC = { "0106": ["Prn_iLMok|Prn_SanMok", "Prn_iLMok_EXL|Prn_SanMok_EXL", "ST_P_List"], "0206": ["Prn_AList", "Prn_AList_EXL", "ST_P_List"], "0303": ["Prn_iLWi_S", "Prn_iLWi_S_EXL", "ST_P_iLWi"], "0605": ["Prn_Nae_B", "Prn_Nae_B_EXL", "ST_P_Nae"], "0606": ["Prn_Nae_D", "Prn_Nae_P_EXL", "ST_P_Nae"], "0607": ["Prn_Bqm", "Prn_Bqm_EXL", "ST_P_Nae"], "0608": ["Prn_Wonga", "Prn_Wonga_EXL", "ST_P_Nae"], "0609": ["Prn_Won_A", "Prn_Won_A_EXL", "ST_P_Nae"], "0613": ["Prn_Nae|Prn_NaeW", "Prn_Nae_EXL", "ST_P_Nae"], "0614": ["Prn_Nae_P|Prn_NaeW_P", "Prn_Nae_P_EXL", "ST_P_Nae"], "0615": ["Prn_Nae_Est", "Prn_Nae_EST_EXL", "ST_P_ConEst"], "0651": ["Prn_Nae_Ex1_CT", "Prn_Nae_Ex1_CT_EXL", "ST_P_Excav"], "0652": ["Prn_Nae_Ex1_C1", "Prn_Nae_Ex1_C1_EXL", "ST_P_Excav"], "0653": ["Prn_Nae_Ex1_C2", "Prn_Nae_Ex1_C2_EXL", "ST_P_Excav"], "0654": ["Prn_Nae_Ex1_0", "Prn_Nae_Ex1_0_EXL", "ST_P_Excav"], "0655": ["Prn_Nae_Ex1_T", "Prn_Nae_Ex1_T_EXL", "ST_P_Excav"], "0656": ["Prn_Nae_Ex1_1", "Prn_Nae_Ex1_1_EXL", "ST_P_Excav"], "0671": ["Prn_Nae_Plan_{A|K}_X", "Prn_Nae_PLNX", "ST_P_Nae"], "0672": ["Prn_Jab", "Prn_Jab_EXL", "ST_P_Nae"], "0675": ["Prn_Nae_ColM", "Prn_Nae_ColM", "ST_P_Nae"], "0676": ["Prn_Nae_ColL", "Prn_Nae_ColL", "ST_P_Nae"], "0677": ["Prn_Nae_ColX", "Prn_Nae_ColX", "ST_P_Nae"], "0678": ["Prn_Nae_Plan_{A|K}_M", "Prn_Nae_PLNM", "ST_P_Nae"], "0679": ["Prn_Nae_Plan_{A|K}_L", "Prn_Nae_PLNL", "ST_P_Nae"], "0695": ["Prn_Boq_Not", "Prn_Boq_Not", "ST_P_Nae"], }; // SET_REP 칸 이름(FLD_N1~) function fields(dbf, indx) { const r = ((dbf && dbf.set_rep_rows) || []).find((x) => String(x.INDX).trim() === indx); const out = []; if (r) for (let i = 1; i <= 11; i++) out.push(str(r[`FLD_N${i}`]).trim()); return out; } const form = (sh, dbf, indx) => C.applyForm(sh, dbf, indx); const T4 = (s) => s.M + s.L + s.S; const excluded = (n) => { for (let p = n.parent; p; p = p.parent) if (p.excluded) return true; return false; }; const won = (n) => C.commas(n); /** * 0303 일위대가표(수공양식 · TPM_ILWI_S) — 호표마다 머리 세 줄 * 1줄 「항 목 번 호 :」 · 「제 n 호표」 · 비고 칸 = 코드 / 2줄 「공 종 :」 명칭 · 규격 · 재료비·노무비·경비·총액 / * 3줄 「단 위 :」 단위 — 아래 구성 줄 = 명칭 · 규격 · 수량 · 단위 · 재·노·경 단가·금액 · 비고(코드) · 총액 칸 없음 · * 호표 끝 「합 계」 줄(엑셀판) — 엑셀판 비목 칸 자리(Pos(비목, 설정 글)×2+3·+4)의 설정 글 이름은 미확인(고정 재·노·경) */ function iLwiHandSheet(u) { const { dbf, idx, evals, hopyoNo } = u; const f = fields(dbf, "0303"); const sh = C.newSheet("일위대가표수공", "일 위 대 가 표", C.projectName(dbf), "K"); C.push(sh, { A: "명 칭", B: "규 격", C: "수 량", D: "단위", E: "재 료 비", G: "노 무 비", I: "경 비", K: "비고", _k: "head", }); C.push(sh, { E: "단 가", F: "금 액", G: "단 가", H: "금 액", I: "단 가", J: "금 액", _k: "head", }); for (const r of idx.ofPrefix("B")) { const c = code(r); const ev = evals[c]; const tot = ev ? ev.tot : { M: 0, L: 0, S: 0 }; C.push(sh, { A: f[0] || "항 목 번 호 :", B: `${f[1] || "제"} ${hopyoNo[c]} ${f[2] || "호표"}`, K: c, _k: "hopyo", }); C.push(sh, { A: f[3] || "공 종 :", B: str(r.NAME), C: str(r.SIZE), E: `재 료 비 ${won(tot.M)}`, F: `노 무 비 ${won(tot.L)}`, H: `경 비 ${won(tot.S)}`, I: `총 액 ${won(ev ? ev.T : 0)}`, _k: "parent", }); C.push(sh, { A: f[4] || "단 위 :", B: C.unitText(r.UNIT), _k: "parent" }); if (!ev) continue; for (const ln of ev.lines) { if (ln.text) continue; const ref = ln.ref || {}; const cells = { A: ref.name || ln.code, B: ref.spec || "" }; Object.assign(cells, { C: ln.qty, D: ln.pct ? "%" : ref.unitDisp || ln.unit }); Object.assign(cells, { E: C.fix(ln.u.M), F: C.fix(ln.a.M, 1) }); Object.assign(cells, { G: C.fix(ln.u.L), H: C.fix(ln.a.L, 1) }); Object.assign(cells, { I: C.fix(ln.u.S), J: C.fix(ln.a.S, 1), K: ln.code, _k: "line" }); C.push(sh, cells); } // 호표 끝 「합 계」 줄 — 비목 금액 칸(엑셀판 Prn_iLWi_S_EXL 0x5b1270 · 글 0x5b2cd8) C.push(sh, { A: "합 계", F: C.fix(tot.M, 1), H: C.fix(tot.L, 1), J: C.fix(tot.S, 1), _k: "sum", }); } return form(sh, dbf, "0303"); } /** * 내역 칸(차수 slot) 나무 — 엔진 차수 계산이 있으면 그 값 · 없으면 BOQ 저장 칸(QTYn · MAMTn… · M_AMTn…) 그대로 * 근거: ST_P_XLS Prn_Nae_B_EXL(0x528e00)·Prn_Nae_EST_EXL(0x5611dc) = BOQ.DBF 를 SORTCODE 차례로 직접 읽음 */ function slotTree(dbf, computed, slot) { const st = computed && computed.stages && computed.stages[slot]; const v = (r, k) => num(r[`${k}${slot}`]); const raw = ((dbf && dbf.boq_rows) || []).map((r) => ({ _recno: r._recno, qty: v(r, "QTY"), mUnit: v(r, "MAMT"), lUnit: v(r, "LAMT"), sUnit: v(r, "SAMT"), mAmt: v(r, "M_AMT"), lAmt: v(r, "L_AMT"), sAmt: v(r, "S_AMT"), })); const tree = B.boqTree(dbf, st && st.boq ? st.boq : { rows: raw }, slot); if (st && st.boq) return tree; // 저장 칸 그대로 — % 줄도 QTYn·M_AMTn… (boqTree 의 QTY1 대신 쓰기 없이) · 묶음 합 다시 for (const n of tree.out) if (n.pct) { n.qty = v(n.row, "QTY"); n.amt = { M: v(n.row, "M_AMT"), L: v(n.row, "L_AMT"), S: v(n.row, "S_AMT") }; } const sumOf = (node) => { const s = { M: 0, L: 0, S: 0 }; for (const ch of node.children) { const x = ch.kind === "T" ? sumOf(ch) : ch.amt; if (!(ch.kind === "T" && ch.excluded)) for (const k of "MLS") s[k] += x[k]; } return (node.sum = s); }; sumOf(tree.root); return tree; } const byRec = (tree) => new Map(tree.out.filter((n) => n.row).map((n) => [n.row._recno, n])); /** * 0605 분할내역서(TPM_NAE_B) — 공종 · 명칭 · 규격 · 수량(전체·금회) · 단위 · 단가 · 금액(전체·금회) · 비고 * 엑셀판 Prn_Nae_B_EXL(ST_P_XLS 0x528e00 · BOQ.DBF 직접) — 수량 전체 = QTY1 · 금회 = QTY2(0x529c8c·0x529c9c) · * 단가 = TAMT1 · 금액 전체 = T_AMT1 · 금회 = T_AMT2 — 1·2 칸 고정(인쇄 차수와 무관) */ function splitSheet(dbf, tree1, tree2) { const f = fields(dbf, "0605"); const two = byRec(tree2); const sh = C.newSheet("분할내역서", "분 할 내 역 서", C.projectName(dbf), "J"); const head = { A: "공 종", B: "명 칭", C: "규 격", D: "수 량", F: "단위" }; C.push(sh, Object.assign(head, { G: "단 가", H: "금 액", J: "비고", _k: "head" })); const sub = { D: f[0] || "전 체", E: f[1] || "금 회", H: f[0] || "전 체", I: f[1] || "금 회" }; C.push(sh, Object.assign(sub, { _k: "head" })); for (const n of tree1.out) { const m = two.get(n.row && n.row._recno); if (n.kind === "T") { const cells = { A: str(n.row.ITNUM).trim(), B: str(n.row.NAME1), C: str(n.row.SIZE1) }; Object.assign(cells, { H: T4(n.sum), I: m ? T4(m.sum) : 0 }); C.push(sh, Object.assign(cells, { _k: `T${n.level}` })); continue; } if (n.kind !== "item") continue; const cells = { B: str(n.row.NAME1), C: str(n.row.SIZE1), D: n.qty, E: m ? m.qty : 0 }; Object.assign(cells, { F: C.unitText(n.row.UNIT1), G: T4(n.unitP), H: T4(n.amt) }); Object.assign(cells, { I: m ? T4(m.amt) : 0 }); C.push(sh, Object.assign(cells, { J: n.code, _k: "item", _code: n.code })); } C.push(sh, { B: "합 계", H: T4(tree1.root.sum), I: T4(tree2.root.sum), _k: "final" }); return form(sh, dbf, "0605"); } /** * 0609 총괄집계표(Prn_Won_A · TPM_WON_A) — 공사원가계산서와 같은 임시 표의 줄(NAME · T_AMT · T_PER · S_DET/BIGO) * 칸: 구분 · 금액('#,#') · 구성비(T_PER = 금액 ÷ 총공사비 × 100 · 표시 '0.000 %' · 0 은 빈칸) · 비고(S_DET 없으면 BIGO) */ function totalSheet(dbf, stage) { const f = fields(dbf, "0609"); const sh = C.newSheet("총괄집계표", "총 괄 집 계 표", C.projectName(dbf), "E"); C.push(sh, { A: f[0] || "구 분", B: f[1] || "금 액", C: f[2] || "구 성 비", D: f[3] || "비 고", _k: "head", }); const w = B.wonSheet(dbf, stage, null); for (const r of w.rows.slice(4)) { if (r.D == null) continue; const lbl = [r.A, r.B, r.C].filter((x) => x && String(x).trim()).pop(); C.push(sh, { A: String(lbl || "").trim(), B: r.D, C: r.D ? half(num(r.E), 3) : "", D: r.F || "", _k: "cost", }); } C.push(sh, { A: In.amtToHan(stage.kanjub.grandTotal), _k: "final" }); return form(sh, dbf, "0609"); } /** * 0615 견적단가대비표(TPM_NAE_EST) — 엑셀판 Prn_Nae_EST_EXL(ST_P_XLS 0x5611dc · BOQ.DBF 직접) * 줄마다 두 행 — 1행: 공종 · 명칭 · 규격 · 단위 · 비목(BMKNM 차례 Pos×2+3) 견적단가 = xAMT1 · 기준단가 = xAMT2 * (x = 'MLSTAN' 의 M·L·S·T · 0x56225c) · 2행: 견적 칸에 xAMT1 ÷ xAMT2(기준 0 이면 0 · 서식 '#,0.00 %;;#') · * 비고(13열, 두 행) = 코드 비고 · ':' 줄과 (설정 +0x1759 이면) C/E 줄만 건너뜀 — 그 밖 줄 모두(W 코드 한정 아님) */ function estimateCompareSheet(u, tree1, tree2) { const { dbf, idx } = u; const ini = (dbf && dbf.start_ini) || {}; const f = fields(dbf, "0615"); const order = C.bimokOrder(ini); const two = byRec(tree2); const sh = C.newSheet("견적단가대비표", "견 적 단 가 대 비 표", C.projectName(dbf), "M"); const col = (k, d) => String.fromCharCode(69 + order.indexOf(k) * 2 + d); // E + 비목 자리 × 2 const head = { A: "공 종", B: "명 칭", C: "규 격", D: "단위", M: "비고", _k: "head" }; const sub = { _k: "head" }; for (const k of order) { head[col(k, 0)] = C.bimokLabel(ini, k); sub[col(k, 0)] = f[0] || "견적단가"; sub[col(k, 1)] = f[1] || "기준단가"; } C.push(sh, head); C.push(sh, sub); const unit = (n) => { const p = n && n.kind === "item" ? n.unitP : { M: 0, L: 0, S: 0 }; return Object.assign({ T: p.M + p.L + p.S }, p); }; for (const n of tree1.out) { if (!(n.kind === "T" || n.kind === "item" || n.kind === "blank")) continue; const r = n.row; const a = unit(n), b = unit(two.get(r._recno)); const top = { A: str(r.ITNUM).trim(), B: str(r.NAME1), C: str(r.SIZE1) }; top.D = n.kind === "item" ? C.unitText(r.UNIT1) : ""; const low = { _k: "ratio", _nf: {} }; for (const k of order) { top[col(k, 0)] = a[k]; top[col(k, 1)] = b[k]; low[col(k, 0)] = b[k] ? a[k] / b[k] : 0; low._nf[col(k, 0)] = "#,0.00 %;;#"; } if (n.kind === "item") top.M = B.itemRemark(dbf, idx, n.code); const kind = n.kind === "T" ? `T${n.level}` : n.kind; C.push(sh, Object.assign(top, { _k: kind }, n.kind === "item" ? { _code: n.code } : {})); C.push(sh, low); } return form(sh, dbf, "0615"); } /** * 0651·0655 구.시도 양식 — 금액을 시기금·구기금으로 나눔(opts.fundRate = 시기금 %, 기본 100) · 합계 * 0655 = 품목 줄(Prn_Nae_Ex1_T) · 0651 = 공종 묶음 줄(Prn_Nae_Ex1_CT) · 나눔 비율 출처는 미확인 */ function fundSheet(dbf, tree, indx, opts = {}) { const f = fields(dbf, indx); const rate = opts.fundRate == null ? 100 : num(opts.fundRate); const items = indx === "0655"; const id = items ? "설계내역서구시도" : "총괄내역서구시도"; const title = items ? "설 계 내 역 서 (구.시도)" : "총 괄 내 역 서 (구.시도)"; const sh = C.newSheet(id, title, C.projectName(dbf), "J"); const head = { A: "공 종", B: "명 칭", C: "규 격", D: items ? "수 량" : "" }; const tail = { E: f[0] || "시 기 금", F: f[1] || "구 기 금", G: f[2] || "합 계", _k: "head", }; C.push(sh, Object.assign(head, tail)); for (const n of tree.out) { if (!(n.kind === "T" || (items && n.kind === "item"))) continue; const v = T4(n.kind === "T" ? n.sum : n.amt); const si = trunc((v * rate) / 100); const cells = { A: n.kind === "T" ? str(n.row.ITNUM).trim() : "", B: str(n.row.NAME1), C: str(n.row.SIZE1), }; if (n.kind === "item") cells.D = n.qty; Object.assign(cells, { E: si, F: v - si, G: v, _k: n.kind === "T" ? `T${n.level}` : "item" }); C.push(sh, cells); } return form(sh, dbf, indx); } // 공종 묶음(opts.level 단 · 기본 2) 별 품목 → 투입 자원 전개(report_coll) function groupBlocks(tree, level) { const groupOf = (n) => { let p = n.parent; while (p && p.row && p.level > level) p = p.parent; return p && p.row ? p : null; }; const blocks = new Map(); for (const n of tree.out) { if (n.kind !== "item" || n.pct || excluded(n)) continue; const gnode = groupOf(n); const key = gnode ? gnode.row._recno : "-"; if (!blocks.has(key)) blocks.set(key, { node: gnode, items: [] }); blocks.get(key).items.push(n); } return blocks; } function usedOf(dbf, it, prefix) { const ex = Co.explode(dbf, [{ code: it.code, qty: it.qty }]); const used = Object.keys(ex.qty) .filter((c) => c[0] === prefix && c !== it.code) .sort(); if (it.code[0] === prefix) used.unshift(it.code); return used.map((c) => [c, half(ex.qty[c], 2)]); } /** * 0675 재료비 · 0676 노무비(Prn_Nae_ColM·ColL · TPM_NAE_COL_ML) — 공종(번호+명칭) · 작업명 · 구분(명칭·규격) · * 수량 · 단위 · 단가 · 금액 · 비고 / 0677 중장비(Prn_Nae_ColX · TPM_NAE_COL) — 단가·금액이 재·노·경·총액 네 쌍 */ function collSheet(u, tree, indx, prefix, opts = {}) { const { dbf, idx } = u; const f = fields(dbf, indx); const rep = ((dbf && dbf.set_rep_rows) || []).find((x) => String(x.INDX).trim() === indx) || {}; const title = str(rep.HEADTITLE).trim() || indx; const four = indx === "0677"; const sh = C.newSheet( title.replace(/[\s,/]+/g, ""), title, C.projectName(dbf), four ? "O" : "I", ); const head = { A: f[0] || "공 종", B: f[1] || "작 업 명", C: f[2] || "구 분" }; Object.assign(head, { E: "수 량", F: "단위" }); if (four) { Object.assign(head, { G: "재 료 비", I: "노 무 비", K: "경 비", M: "총 액", O: "비 고", }); C.push(sh, Object.assign(head, { _k: "head" })); const sub = { G: "단가", H: "금 액", I: "단가", J: "금 액", K: "단가", L: "금 액" }; C.push(sh, Object.assign(sub, { M: "단가", N: "금 액", _k: "head" })); } else C.push(sh, Object.assign(head, { G: "단가", H: "금 액", I: "비 고", _k: "head" })); for (const { node, items } of groupBlocks(tree, opts.level || 2).values()) { const gname = node ? `${str(node.row.ITNUM).trim()} ${str(node.row.NAME1).trim()}`.trim() : ""; C.push(sh, { A: gname, _k: "T2" }); for (const it of items) for (const [c, q] of usedOf(dbf, it, prefix)) { const r = idx.by[c]; if (!r) continue; const p = idx.price(c, 1); const cells = { B: str(it.row.NAME1), C: str(r.NAME), D: str(r.SIZE), E: q }; cells.F = C.unitText(r.UNIT); if (four) { Object.assign(cells, { G: p.M, H: half(q * p.M), I: p.L, J: half(q * p.L) }); Object.assign(cells, { K: p.S, L: half(q * p.S), M: T4(p), N: half(q * T4(p)), O: c }); } else { const own = prefix === "L" ? p.L : prefix === "M" ? p.M : T4(p); Object.assign(cells, { G: own, H: half(q * own), I: c }); } C.push(sh, Object.assign(cells, { _k: "item", _code: c })); } } return form(sh, dbf, indx); } /** * 투입계획(0671 장비 X · 0678 자재 M · 0679 인력 L · Prn_Nae_Plan_{A|K}_*) — 설정 +0x1767 로 A/K 갈래 * A(TPM_NAE_PLAN_A) = 명칭 · 규격 · 수량 · 단위 · 비고(코드 합계 줄) * K(TPM_NAE_PLAN_K) = 공종(번호+명칭) · 명칭 · 규격 · 수량 · 단위 · 비고(공종별) * opts.planAll = true → A · 그 밖 K */ function planSheet(u, tree, indx, prefix, opts = {}) { const { dbf, idx } = u; const rep = ((dbf && dbf.set_rep_rows) || []).find((x) => String(x.INDX).trim() === indx) || {}; const title = str(rep.HEADTITLE).trim() || indx; const all = !!opts.planAll; const sh = C.newSheet( title.replace(/[\s,/]+/g, ""), title, C.projectName(dbf), all ? "E" : "F", ); const row = (c, q) => { const r = idx.by[c]; return r ? { name: str(r.NAME), size: str(r.SIZE), unit: C.unitText(r.UNIT), q } : null; }; if (all) { C.push(sh, { A: "명 칭", B: "규 격", C: "수 량", D: "단위", E: "비 고", _k: "head" }); const sum = {}; for (const { items } of groupBlocks(tree, opts.level || 2).values()) for (const it of items) for (const [c, q] of usedOf(dbf, it, prefix)) sum[c] = (sum[c] || 0) + q; for (const c of Object.keys(sum).sort()) { const v = row(c, half(sum[c], 2)); if (v) C.push(sh, { A: v.name, B: v.size, C: v.q, D: v.unit, E: c, _k: "item", _code: c }); } return form(sh, dbf, indx); } C.push(sh, { A: "공 종", B: "명 칭", C: "규 격", D: "수 량", E: "단위", F: "비 고", _k: "head", }); for (const { node, items } of groupBlocks(tree, opts.level || 2).values()) { const sum = {}; for (const it of items) for (const [c, q] of usedOf(dbf, it, prefix)) sum[c] = (sum[c] || 0) + q; const gname = node ? `${str(node.row.ITNUM).trim()} ${str(node.row.NAME1).trim()}`.trim() : ""; C.push(sh, { A: gname, _k: "T2" }); for (const c of Object.keys(sum).sort()) { const v = row(c, half(sum[c], 2)); if (v) C.push(sh, { B: v.name, C: v.size, D: v.q, E: v.unit, F: c, _k: "item", _code: c }); } } return form(sh, dbf, indx); } /** * 0695 간이공사비 산출명세서(Prn_Boq_Not · TPM_BOQ_NOT) — 엑셀판 = ST_P_XLS 0x55f168(같은 이름 · 머리 0x55dfe0) * 머리: A 「구 분」 · C~J 재료비·노무비·경비·일반관리비·이윤·소계·부가세·합계 · K 작성자 * B 「회사분」 줄 = 모두 0 · 「도급분」 = 원가 줄(DTP 202 재료비 · 200+201 노무비 · 300+301~399 경비 · 401 · 402 · * 소계 = SUM(C:G) · 502 부가세 · 합계 = 소계 + 부가세) · 「계」 = 회사분 + 도급분(열마다 위 두 줄 더하기 수식) · * K = 작성자(설정 +0x528 · 이름 미확인 → 빈칸) · 계 줄 K 「(인)」 / * 「 예정가격산출」 줄 C~J = 0 · 「 예정가격(결정권자)」 줄 D = 0 · H 「원」 · J = 설정 +0x428(미확인) · L 「(인)」 / * 아래 표 머리 A 「공사비 예산서」 · I 「공사비 정산서」 · 칸 A 자산단위번호 · C 품명및규격 · E 단위 · F~H 수량·단가·금액 · * I~K 수량·단가·금액 · L 증감(= K − H 수식) · 첫 줄 C 「" 설 계 서 붙 임 "」 */ function simpleSheet(dbf, stage) { const sh = C.newSheet("간이공사비산출명세서", "간이공사비 산출명세서", C.projectName(dbf), "M"); const cols = ["C", "D", "E", "F", "G", "H", "I", "J"]; const head = { A: "구 분", C: "재료비", D: "노무비", E: "경비", F: "일 반\n관리비" }; C.push( sh, Object.assign(head, { G: "이윤", H: "소계", I: "부가세", J: "합계", K: "작성자", _k: "head", }), ); const w = B.wonSheet(dbf, stage, null).rows.slice(4); const subt = w.filter((r) => /^소\s*계$/.test( String(r.C || "") .replace(/\s+/g, " ") .trim(), ), ); const pick = (re) => { const r = w.find((x) => re.test(String(x.A || "").replace(/\s/g, ""))); return r ? num(r.D) : 0; }; const five = [0, 1, 2].map((i) => (subt[i] ? num(subt[i].D) : 0)); five.push(pick(/^일반관리비/), pick(/^이윤/)); const sum5 = five.reduce((a, b) => a + b, 0); const vat = pick(/^부가가치세/); const dog = five.concat([sum5, vat, sum5 + vat]); const com = cols.map(() => 0); const line = (lbl, v, extra) => { const cells = { B: lbl, _k: "cost" }; cols.forEach((c, i) => (cells[c] = v[i])); return C.push(sh, Object.assign(cells, extra || {})); }; line("회사분", com, { K: "" }); line("도급분", dog); line( "계", dog.map((v, i) => v + com[i]), { K: "(인)" }, ); const est = { A: " 예정가격산출", _k: "pct", K: "" }; cols.forEach((c) => (est[c] = 0)); C.push(sh, est); C.push(sh, { A: " 예정가격(결정권자)", D: 0, H: "원", J: "", L: "(인)", _k: "pct" }); C.push(sh, { A: "공 사 비 예 산 서", I: "공 사 비 정 산 서", _k: "head", }); const low = { A: "자산단위번호", C: "품 명 및 규 격", E: "단위", F: "수량", G: "단가", H: "금액", }; C.push(sh, Object.assign(low, { I: "수량", J: "단가", K: "금액", L: "증감", _k: "head" })); C.push(sh, { C: ' " 설 계 서 붙 임 "', L: 0, _k: "note" }); return form(sh, dbf, "0695"); } /** * 0607 사·관급내역서(Prn_Bqm · TPM_BQM) — 설계내역서 칸 그대로 · 머리 띠 「사 급 분」(SET_REP FLD_N1) · * 관급 구역(C 로 닫힌 묶음) 줄 · 끝 = 관급 계 · 아래 표(공종번호 · 명칭 · 구분 · 금액 '#,# 원') */ function govSheet(design, tree, dbf) { const f = fields(dbf, "0607"); const sh = C.newSheet("관급내역서", "관 급 내 역 서", C.projectName(dbf), "O"); for (const r of design.rows.slice(2, 4)) C.push(sh, Object.assign({}, r)); C.push(sh, { A: f[0] || "사 급 분", _k: "band" }); const sum = { M: 0, L: 0, S: 0 }; const grid = []; // 설계내역서 행 중 관급 구역 — 제외 묶음 줄(_ex · 단 L)부터 같은 단 이상 묶음 줄·끝 줄 앞까지 let zone = 0; for (const r of design.rows.slice(4)) { const lv = Number(r._lv) || 0; if (zone && ((lv && lv >= zone && !r._ex) || r._k === "final")) zone = 0; if (!zone && r._ex) zone = lv; if (zone) C.push(sh, Object.assign({}, r, { _f: undefined })); } tree.out.forEach((n) => { if (n.kind === "T" && n.excluded && !excluded(n)) { for (const k of "MLS") sum[k] += n.sum[k]; grid.push({ A: str(n.row.ITNUM).trim(), B: str(n.row.NAME1).trim(), C: "관급" }); grid[grid.length - 1].D = `${won(T4(n.sum))} 원`; } }); const last = { A: f[1] || "계", _k: "final" }; const head = design.rows[2] || {}; const amt = { 합: T4(sum), 노: sum.L, 재: sum.M, 경: sum.S }; for (const [c, lbl] of Object.entries(head)) if (/^[FHJL]$/.test(c)) { const key = String(lbl).replace(/\s/g, "")[0]; last[String.fromCharCode(c.charCodeAt(0) + 1)] = amt[key] ?? 0; } C.push(sh, last); for (const gr of grid) C.push(sh, Object.assign(gr, { _k: "grid" })); return form(sh, dbf, "0607"); } // 제목만 바뀌는 변형 — 같은 시트 모형을 복사해 SET_REP 제목·이름 // (0613·0614 = Prn_Nae·Prn_Nae_P 같은 함수 · 0606 = 엑셀은 종양식 Prn_Nae_P_EXL · 0654·0656 굴착 · 0652·0653·0672 총괄) // 쪽 채움 빈 줄(pad)은 Prn_Nae·Prn_Nae_P 엑셀판만 — 굴착 Ex1 엑셀판(ST_P_XLS 0x59e818~)은 쪽 줄 수(0x612168)를 안 읽어 걷음 const NO_PAD = new Set(["0654", "0656"]); function retitle(sh, dbf, indx, id) { const copy = { name: sh.name, title: sh.title, cols: {} }; copy.rows = sh.rows .filter((r) => !(NO_PAD.has(indx) && r._k === "pad")) .map((r) => Object.assign({}, r)); const rep = ((dbf && dbf.set_rep_rows) || []).find((x) => String(x.INDX).trim() === indx); const title = rep ? str(rep.HEADTITLE).trim() : sh.title; copy.title = title; if (copy.rows[0] && copy.rows[0]._k === "title") copy.rows[0].A = title; copy.name = `${title.replace(/[\s,/().]+/g, "")}${indx}`; copy.id = id; copy.indx = indx; return copy; } /** 변형 묶음 — 보고서 화면 서식 목록에서 고를 때 만듦 · 돌려줌: { id: 시트 모형 } */ function buildFormSheets(u, dbf, state, computed, n = 1, opts = {}) { const stage = B.stageOf(state, computed, n); const design = B.designSheet(dbf, stage, opts); const tree = design._tree; const tree1 = slotTree(dbf, computed, 1), tree2 = slotTree(dbf, computed, 2); const summary = B.summarySheet(dbf, stage, design, opts); const out = { 일위대가표수공양식: iLwiHandSheet(u), 분할내역서: splitSheet(dbf, tree1, tree2), 총괄집계표: totalSheet(dbf, stage), 견적내역서: retitle(design, dbf, "0613", "견적내역서"), 견적내역서종양식: retitle(design, dbf, "0614", "견적내역서종양식"), 견적단가대비표: estimateCompareSheet(u, tree1, tree2), 내역서도공양식: retitle(design, dbf, "0606", "내역서도공양식"), 설계내역서굴착: retitle(design, dbf, "0654", "설계내역서굴착"), 설계내역서시도: retitle(design, dbf, "0656", "설계내역서시도"), 총괄내역서시도: retitle(summary, dbf, "0652", "총괄내역서시도"), 총괄내역서구도: retitle(summary, dbf, "0653", "총괄내역서구도"), 총괄내역서비교: retitle(summary, dbf, "0672", "총괄내역서비교"), 총괄내역서구시도: fundSheet(dbf, tree, "0651", opts), 설계내역서구시도: fundSheet(dbf, tree, "0655", opts), 재료비집계표: collSheet(u, tree, "0675", "M", opts), 노무비집계표: collSheet(u, tree, "0676", "L", opts), 중장비집계표: collSheet(u, tree, "0677", "X", opts), 장비투입계획: planSheet(u, tree, "0671", "X", opts), 자재사용계획: planSheet(u, tree, "0678", "M", opts), 인력투입계획: planSheet(u, tree, "0679", "L", opts), 간이명세서: simpleSheet(dbf, stage), 관급내역서: govSheet(design, tree, dbf), 제잡비요율표: In.rateSheet(dbf, opts), }; for (const [id, sh] of Object.entries(out)) Object.assign(sh, { id }); delete design._tree; return out; } // 서식 번호 → 변형 시트 id const FORM_SHEET = { "0303": "일위대가표수공양식", "0605": "분할내역서", "0609": "총괄집계표", "0613": "견적내역서", "0614": "견적내역서종양식", "0615": "견적단가대비표", "0606": "내역서도공양식", "0654": "설계내역서굴착", "0656": "설계내역서시도", "0652": "총괄내역서시도", "0653": "총괄내역서구도", "0672": "총괄내역서비교", "0651": "총괄내역서구시도", "0655": "설계내역서구시도", "0675": "재료비집계표", "0676": "노무비집계표", "0677": "중장비집계표", "0671": "장비투입계획", "0678": "자재사용계획", "0679": "인력투입계획", "0695": "간이명세서", "0607": "관급내역서", }; return { buildFormSheets, FORM_SHEET, FORM_PROC, fields, splitSheet, totalSheet, collSheet, planSheet, }; })(); if (typeof window !== "undefined") window.STMATE_REPORT_FORMS = StmateReportForms; if (typeof module !== "undefined" && module.exports) module.exports = StmateReportForms;