676 lines
30 KiB
JavaScript
676 lines
30 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 · 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;
|