Files
Aislo/tools/stmate-test/report_forms.js
T
eomsangdonandClaude Opus 5.5 82742c0309 feat(stmate-test): REA MCP(Ghidra) 보고서 양식·수량산출서 — 인쇄 분기표 · 0609 총괄집계표 구성비 · 수량산출서 파일 바이트(IFORM) · 셀별명 읽기 · 장부 93.1%
- ST_Prt 0x44fc34 서식 번호 → 출력 함수 표 · 0609 = Prn_Won_A · 0608 = Prn_Wonga
- IFORM = VTS·XLS 파일 바이트 · .XLS·.VTS · 16MB 거절 · ReadDefName 「이름=값」 · QTYn 칸
- 사·관급내역서 관급 구역 품목 줄 누락 수정
- 장부 F3-23 · F3-26 · F5-22 구현 · 지식DB 35번 · stmate 시험 732건 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 13:54:48 +09:00

605 lines
26 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 · 값 출처 일부(분할 금회 수량 · 견적·기준 단가 · 회사분)는 후보
*/
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줄 「단 위 :」 단위 — 아래 구성 줄 = 명칭 · 규격 · 수량 · 단위 · 재·노·경 단가·금액 · 비고(코드) · 총액 칸 없음
*/
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);
}
}
return form(sh, dbf, "0303");
}
/**
* 0605 분할내역서(TPM_NAE_B) — 공종 · 명칭 · 규격 · 수량(전체·금회) · 단위 · 단가(총액) · 금액(전체·금회) · 비고
* 금회 수량 출처는 미확인 — 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), "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" }));
let tot = 0,
cur = 0;
const cutOf = new Map();
for (const n of tree.out) {
if (n.kind !== "item") continue;
const q2 = half((n.qty * rate) / 100, 3);
const a2 = n.pct ? trunc((T4(n.amt) * rate) / 100) : trunc(T4(n.unitP) * q2);
cutOf.set(n, { q2, a2 });
// 묶음 금회 합 — 제외(C) 묶음에서 멈춤(설계 묶음 합과 같은 범위)
for (let p = n.parent; p && p.row; p = p.parent) {
p._cut = (p._cut || 0) + a2;
if (p.excluded) break;
}
if (!excluded(n)) ((tot += T4(n.amt)), (cur += a2));
}
for (const n of tree.out) {
if (n.kind === "T") {
const cells = { A: str(n.row.ITNUM).trim(), B: str(n.row.NAME1), C: str(n.row.SIZE1) };
C.push(sh, Object.assign(cells, { H: T4(n.sum), I: n._cut || 0, _k: `T${n.level}` }));
continue;
}
if (n.kind !== "item") continue;
const { q2, a2 } = cutOf.get(n);
const cells = { B: str(n.row.NAME1), C: str(n.row.SIZE1), D: n.qty, E: q2 };
Object.assign(cells, { F: C.unitText(n.row.UNIT1), G: T4(n.unitP), H: T4(n.amt), I: a2 });
C.push(sh, Object.assign(cells, { J: n.code, _k: "item", _code: n.code }));
}
C.push(sh, { B: "합 계", H: tot, I: cur, _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 견적단가대비표(Prn_Nae_Est · TPM_NAE_EST) — 내역 품목 줄 · 공종 · 명칭 · 규격 · 단위 ·
* 재료비·노무비·경비·총액 마다 (견적단가 · 기준단가) · 비고(코드)
* 대상·값 후보: 견적(W) 코드 줄 · 견적단가 = 내역 단가 · 기준단가 = 그 코드 CODEA(변환 전 코드) 단가(없으면 빈칸)
*/
function estimateCompareSheet(u, tree) {
const { dbf, idx } = u;
const f = fields(dbf, "0615");
const sh = C.newSheet("견적단가대비표", "견 적 단 가 대 비 표", C.projectName(dbf), "M");
const head = { A: "공 종", B: "명 칭", C: "규 격", D: "단위", E: "재 료 비" };
C.push(
sh,
Object.assign(head, { G: "노 무 비", I: "경 비", K: "총 액", M: "비고", _k: "head" }),
);
const e = f[0] || "견적단가",
b = f[1] || "기준단가";
C.push(sh, { E: e, F: b, G: e, H: b, I: e, J: b, K: e, L: b, _k: "head" });
for (const n of tree.out) {
if (n.kind !== "item" || n.code[0] !== "W") continue;
const src = idx.by[n.code];
const base = src && str(src.CODEA).trim() ? idx.price(str(src.CODEA).trim(), 1) : null;
const p = n.unitP;
const cells = { A: "", B: str(n.row.NAME1), C: str(n.row.SIZE1) };
Object.assign(cells, { D: C.unitText(n.row.UNIT1), E: p.M, G: p.L, I: p.S, K: T4(p) });
if (base) Object.assign(cells, { F: base.M, H: base.L, J: base.S, L: T4(base) });
C.push(sh, Object.assign(cells, { M: n.code, _k: "item", _code: n.code }));
}
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 · 엑셀판 없음) — 고정 칸 양식
* 구분(회사분·도급분·계) × 재료비·노무비·경비·일반관리비·이윤·소계·부가세·합계 · 작성자(인) ·
* 예정가격산출(%) · 예정가격(결정권자) · 아래 「공사비 예산서 | 공사비 정산서」 머리
* 값 후보: 도급분 = 원가계산서 줄 · 회사분 = 관급자재대(재료비 칸) · 예정가격산출 % 출처 미확인(빈칸)
*/
function simpleSheet(dbf, stage) {
const sh = C.newSheet("간이공사비산출명세서", "간이공사비 산출명세서", C.projectName(dbf), "J");
const k = stage.kanjub || {};
const cols = ["B", "C", "D", "E", "F", "G", "H", "I"];
const head = { A: "구분", B: "재료비", C: "노무비", D: "경비", E: "일 반\n관리비", F: "이윤" };
C.push(sh, Object.assign(head, { G: "소계", H: "부가세", I: "합계", J: "작성자", _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 dog = [
subt[0] ? num(subt[0].D) : 0,
subt[1] ? num(subt[1].D) : 0,
subt[2] ? num(subt[2].D) : 0,
pick(/^일반관리비/),
pick(/^이윤/),
];
dog.push(pick(/^총원가/), pick(/^부가가치세/), pick(/^도급공사비/));
const gov = num(k.govFinal);
const com = [gov, 0, 0, 0, 0, gov, 0, gov];
const line = (lbl, v) => {
const cells = { A: lbl, _k: "cost" };
cols.forEach((c, i) => (cells[c] = v[i]));
return C.push(sh, cells);
};
line("회사분", com);
line("도급분", dog);
line(
"계",
dog.map((v, i) => v + com[i]),
);
C.push(sh, { A: " 예정가격산출", _k: "pct" });
C.push(sh, { A: " 예정가격(결정권자)", I: "(인)", _k: "pct" });
C.push(sh, {
A: "공 사 비 예 산 서",
G: "공 사 비 정 산 서",
_k: "head",
});
const low = {
A: "자산단위번호",
B: "품 명 및 규 격",
D: "단위",
E: "수량",
F: "단가",
G: "금액",
};
C.push(sh, Object.assign(low, { H: "수량", I: "단가", J: "금액", K: "증감", _k: "head" }));
C.push(sh, { B: '" 설 계 서 붙 임 "', _k: "note" });
C.push(sh, { A: "총공사비(한글)", B: In.amtToHan(stage.kanjub.grandTotal), _k: "final" });
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 총괄)
function retitle(sh, dbf, indx, id) {
const copy = { name: sh.name, title: sh.title, cols: {} };
copy.rows = sh.rows.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 summary = B.summarySheet(dbf, stage, design, opts);
const out = {
일위대가표수공양식: iLwiHandSheet(u),
분할내역서: splitSheet(dbf, tree, opts),
총괄집계표: totalSheet(dbf, stage),
견적내역서: retitle(design, dbf, "0613", "견적내역서"),
견적내역서종양식: retitle(design, dbf, "0614", "견적내역서종양식"),
견적단가대비표: estimateCompareSheet(u, tree),
내역서도공양식: 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;