/* -*- coding: utf-8 -*- */ /** * 보고서 양식 변형 (report_forms.js) — 원본 출력 견본이 없는 SET_REP 서식(머리 칸·제목은 SET_REP · 값은 계산 엔진) * 0303 일위대가표(수공양식) · 0605 분할내역서 · 0609 총괄집계표 · 0613·0614 견적내역서 · 0615 견적단가대비표 * 0606·0654·0656 내역서(도공·굴착·시도) · 0651·0655 구.시도(시기금·구기금) · 0652·0653·0672 총괄 변형 * 0675~0677 재료비·노무비·중장비 집계표 · 0671·0678·0679 투입계획 · 0695 간이명세서 · 0206 실적단가목록표 * 근거: 지식DB STmate/old/20_분석 20 §3(SET_REP FLD_N·FLD_W) · old/30_원자료/print_form_map.txt · 24 §2.4(AMTTOHAN) * 판정: 부분 — 원본 STMATE 출력 견본이 없어 열 배치는 SET_REP 칸 이름 차례를 따름(셀 대조 없음) */ 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; // 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; }; /** 0303 일위대가표(수공양식) — 호표마다 「항목번호 · 제 n 호표 · 공종 · 단위」 머리 + 구성 줄(재·노·경 단가·금액) */ function iLwiHandSheet(u) { const { dbf, idx, evals, hopyoNo } = u; const f = fields(dbf, "0303"); const sh = C.newSheet("일위대가표수공", "일 위 대 가 표", C.projectName(dbf), "O"); const head = { A: "명 칭", B: "규 격", C: "단위", D: "수 량", _k: "head" }; Object.assign(head, { E: "재료비 단가", F: "재료비 금액", G: "노무비 단가", H: "노무비 금액" }); Object.assign(head, { I: "경비 단가", J: "경비 금액", K: "합계 금액", L: "비 고" }); for (const r of idx.ofPrefix("B")) { const c = code(r); const ev = evals[c]; C.push(sh, { A: `${f[0] || "항 목 번 호 :"} ${c}`, E: `${f[1] || "제"} ${hopyoNo[c]} ${f[2] || "호표"}`, _k: "hopyo", }); C.push(sh, { A: `${f[3] || "공 종 :"} ${str(r.NAME)} ${str(r.SIZE)}`.trim(), _k: "parent", }); C.push(sh, { A: `${f[4] || "단 위 :"} ${C.unitText(r.UNIT)}`, _k: "parent" }); C.push(sh, Object.assign({}, head)); 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 || "", C: ln.pct ? "%" : ref.unitDisp || ln.unit, }; Object.assign(cells, { D: ln.qty, E: C.fix(ln.u.M), F: C.fix(ln.a.M, 1), 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: C.fix(T4(ln.a), 1), L: ln.code, _k: "line", }); C.push(sh, cells); } C.push(sh, { A: "합 계", F: ev.tot.M, H: ev.tot.L, J: ev.tot.S, K: ev.T, _k: "total" }); } return form(sh, dbf, "0303"); } /** * 0605 분할내역서(횡양식) — 「전체」(설계 수량·금액) · 「금회」(분할 비율 opts.splitRate %) 두 묶음 * 금회 수량 = 전체 × 비율(소수 셋째 자리 사사오입) · 금액 = 단가 × 금회 수량 원 미만 절사 */ function splitSheet(dbf, tree, opts = {}) { const f = fields(dbf, "0605"); const rate = opts.splitRate == null ? 100 : num(opts.splitRate); const sh = C.newSheet("분할내역서", "분 할 내 역 서", C.projectName(dbf), "L"); C.push(sh, { A: "공 종", B: "명 칭", C: "규 격", D: "단위", E: f[0] || "전 체", H: f[1] || "금 회", K: "비 고", _k: "head", }); C.push(sh, { E: "수 량", F: "단 가", G: "금 액", H: "수 량", I: "금 액", J: "비율(%)", _k: "head", }); let tot = 0, cur = 0; for (const n of tree.out) { if (n.kind === "T") { C.push(sh, { A: str(n.row.ITNUM).trim(), B: str(n.row.NAME1), G: T4(n.sum), _k: `T${n.level}`, }); continue; } if (n.kind !== "item") continue; const unit = T4(n.unitP); const q2 = half((n.qty * rate) / 100, 3); const a2 = n.pct ? trunc((T4(n.amt) * rate) / 100) : trunc(unit * q2); if (!excluded(n)) ((tot += T4(n.amt)), (cur += a2)); const cells = { B: str(n.row.NAME1), C: str(n.row.SIZE1), D: C.unitText(n.row.UNIT1), E: n.qty, F: unit, }; Object.assign(cells, { G: T4(n.amt), H: q2, I: a2, J: rate, _k: "item", _code: n.code }); C.push(sh, cells); } C.push(sh, { B: "합 계", G: tot, I: cur, J: tot ? C.fix((cur / tot) * 100, 2) : 0, _k: "final", }); return form(sh, dbf, "0605"); } /** * 0609 총괄집계표 — 구분(공종 묶음 · 원가 항목) · 금액 · 구성비(총공사비 대비 %) · 비고 * 공종 = opts.level 단(기본 2) 묶음의 직접공사비 · 원가 항목 = 공사원가계산서 줄 */ function totalSheet(dbf, stage, tree, opts = {}) { const f = fields(dbf, "0609"); const sh = C.newSheet("총괄집계표", "총 괄 집 계 표", C.projectName(dbf), "F"); C.push(sh, { A: f[0] || "구 분", C: f[1] || "금 액", D: f[2] || "구 성 비", E: f[3] || "비 고", _k: "head", }); const grand = stage.kanjub.grandTotal || 1; const level = opts.level || 2; for (const n of tree.out) if (n.kind === "T" && n.level === level && !n.excluded) { const v = T4(n.sum); C.push(sh, { A: str(n.row.NAME1).trim(), C: v, D: C.fix((v / grand) * 100, 4), E: "직접공사비", _k: "item", }); } const won = B.wonSheet(dbf, stage, null); for (const r of won.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(), C: r.D, D: C.fix((r.D / grand) * 100, 4), E: r.F || "", _k: "cost", }); } C.push(sh, { A: In.amtToHan(stage.kanjub.grandTotal), _k: "final" }); return form(sh, dbf, "0609"); } /** * 0615 견적단가대비표 — 재료(M) 중 견적 가격지(5칸 AMT_51 · JMOK5) 값이 있는 것 · 견적단가 ↔ 기준(적용) 단가 · 차이 · 비율 */ function estimateCompareSheet(u) { const { dbf, idx, ini } = u; const f = fields(dbf, "0615"); const sh = C.newSheet("견적단가대비표", "견 적 단 가 대 비 표", C.projectName(dbf), "J"); const est = str(ini.JMOK5 || "견적단가").trim(); C.push(sh, { A: "코드번호", B: "명 칭", C: "규 격", D: "단위", E: f[0] || est, F: f[1] || "기준단가", G: "차 이", H: "비율(%)", I: "비 고", _k: "head", }); for (const r of idx.ofPrefix("M")) { const e = num(r.AMT_51); if (!e) continue; const c = code(r); const p = idx.price(c, 1); const base = T4(p); const cells = { A: c, B: str(r.NAME), C: str(r.SIZE), D: C.unitText(r.UNIT), E: e, F: base, G: C.fix(e - base, 2), }; Object.assign(cells, { H: base ? C.fix((e / base) * 100, 2) : 0, I: str(r.PG_51).trim(), _k: "item", _code: c, }); C.push(sh, cells); } return form(sh, dbf, "0615"); } /** * 0651·0655 구.시도 양식 — 금액을 시기금·구기금으로 나눔(opts.fundRate = 시기금 %, 기본 100) · 합계 * 0655 = 품목 줄 · 0651 = 공종 묶음 줄 */ 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 sh = C.newSheet( id, items ? "설 계 내 역 서 (구.시도)" : "총 괄 내 역 서 (구.시도)", C.projectName(dbf), "J", ); C.push(sh, { A: "공 종", B: "명 칭", C: "규 격", D: items ? "수 량" : "", E: f[0] || "시 기 금", F: f[1] || "구 기 금", G: f[2] || "합 계", _k: "head", }); 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); } /** * 공종별 집계(0675 재료비 · 0676 노무비 · 0677 중장비) · 투입계획(0678 자재 · 0679 인력 · 0671 장비) * 공종 = 설계내역 opts.level 단(기본 2) 묶음 · 작업명 = 품목 명칭 · 구분 = 투입 자원 · 수량 = 투입 수량 전개(report_coll) */ function groupCollSheet(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 sh = C.newSheet(title.replace(/[\s,/]+/g, ""), title, C.projectName(dbf), "J"); const plan = ["0671", "0678", "0679"].includes(indx); C.push(sh, { A: f[0] || "공 종", B: f[1] || "작 업 명", C: f[2] || "구 분", D: "규 격", E: "단위", F: "수 량", G: "단 가", H: plan ? "투입 시기" : "금 액", _k: "head", }); const level = opts.level || 2; 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); } for (const { node, items } of blocks.values()) { C.push(sh, { A: node ? str(node.row.NAME1).trim() : "", _k: "T2" }); for (const it of items) { 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); for (const c of used) { const r = idx.by[c]; if (!r) continue; const q = half(ex.qty[c], 2); const p = idx.price(c, 1); const cells = { B: str(it.row.NAME1), C: str(r.NAME), D: str(r.SIZE), E: C.unitText(r.UNIT), F: q, G: T4(p), }; if (!plan) cells.H = half(q * T4(p)); C.push(sh, Object.assign(cells, { _k: "item", _code: c })); } } } return form(sh, dbf, indx); } /** 0695 간이공사비 산출명세서 — 원가 항목 · 금액 · 한글 금액 */ function simpleSheet(dbf, stage) { const sh = C.newSheet("간이공사비산출명세서", "간이공사비 산출명세서", C.projectName(dbf), "E"); C.push(sh, { A: "항 목", B: "금 액", C: "산 출 근 거", _k: "head" }); const won = B.wonSheet(dbf, stage, null); for (const r of won.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.F || "", _k: "cost" }); } C.push(sh, { A: "총공사비(한글)", B: In.amtToHan(stage.kanjub.grandTotal), _k: "final" }); return form(sh, dbf, "0695"); } // 제목만 바뀌는 변형 — 같은 시트 모형을 복사해 SET_REP 제목·이름(0606 도공 · 0654 굴착 · 0656 시도 · 0613 견적 · 0652·0653·0672 총괄) function retitle(sh, dbf, indx, id) { const copy = { name: sh.name, title: sh.title, rows: sh.rows.map((r) => Object.assign({}, r)), cols: {}, }; 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 summary = B.summarySheet(dbf, stage, design, opts); const out = { 일위대가표수공양식: iLwiHandSheet(u), 분할내역서: splitSheet(dbf, tree, opts), 총괄집계표: totalSheet(dbf, stage, tree, opts), 견적내역서: retitle(design, dbf, "0613", "견적내역서"), 견적내역서종양식: retitle(design, dbf, "0614", "견적내역서종양식"), 견적단가대비표: estimateCompareSheet(u), 내역서도공양식: 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), 재료비집계표: groupCollSheet(u, tree, "0675", "M", opts), 노무비집계표: groupCollSheet(u, tree, "0676", "L", opts), 중장비집계표: groupCollSheet(u, tree, "0677", "X", opts), 장비투입계획: groupCollSheet(u, tree, "0671", "X", opts), 자재사용계획: groupCollSheet(u, tree, "0678", "M", opts), 인력투입계획: groupCollSheet(u, tree, "0679", "L", opts), 간이명세서: simpleSheet(dbf, stage), 제잡비요율표: 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": "간이명세서", }; return { buildFormSheets, FORM_SHEET, fields, splitSheet, totalSheet, groupCollSheet }; })(); if (typeof window !== "undefined") window.STMATE_REPORT_FORMS = StmateReportForms; if (typeof module !== "undefined" && module.exports) module.exports = StmateReportForms;