- 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>
605 lines
26 KiB
JavaScript
605 lines
26 KiB
JavaScript
/* -*- 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;
|