Files
Aislo/tools/stmate-test/report_forms.js
T

676 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -*- 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;