346 lines
15 KiB
JavaScript
346 lines
15 KiB
JavaScript
/* -*- coding: utf-8 -*- */
|
|
/**
|
|
* 보고서 묶음 (report_book.js) — 자재단가대비표(0105) · 목차 · 안내 · 시트 차례 · SET_REP 서식 표
|
|
* 근거: 지식DB STmate/old/20_분석 20 §2.3(가격지 슬롯 이름 JMOK · 축약 JMK) · §3(SET_REP 75종)
|
|
* 19 §3(목차·안내 시트) · old/30_원자료/print_form_map.txt(서식 번호 ↔ 출력 함수)
|
|
* 정답 대조: resources/tester/test_stmate_report_sheets.py
|
|
*/
|
|
const StmateReportBook = (() => {
|
|
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
const C = g.STMATE_REPORT_CORE || require("./report_core.js");
|
|
const U = g.STMATE_REPORT_UNIT || require("./report_unit.js");
|
|
const B = g.STMATE_REPORT_BOQ || require("./report_boq.js");
|
|
const Co = g.STMATE_REPORT_COLL || require("./report_coll.js");
|
|
const Sa = g.STMATE_REPORT_SAN || require("./report_san.js");
|
|
const In = g.STMATE_REPORT_INIT || require("./report_init.js");
|
|
const Lk = g.STMATE_REPORT_LINK || require("./report_link.js");
|
|
const { num, str, code } = C;
|
|
|
|
/**
|
|
* 자재단가대비표(0105) — 재료비(M) 전수 · 가격지 5칸(단가·페이지) · 적용 단가와 적용 가격지 축약
|
|
* 가격지 이름 = Start8x.Ini JMOK1~6 · 축약 = JMK10~50 · 적용 슬롯 = COSTN PG_1
|
|
*/
|
|
function priceCompareSheet(ctx) {
|
|
const { dbf, idx, ini, po } = ctx;
|
|
const sh = C.newSheet("자재단가대비표", "자재단가 대비표", C.projectName(dbf), "R");
|
|
const slots = ["E", "G", "I", "K", "M"];
|
|
const h3 = {
|
|
A: po.codeMode ? "코드번호" : "호표",
|
|
B: "명 칭",
|
|
C: "규 격",
|
|
D: "단위",
|
|
};
|
|
const h4 = { _k: "head" };
|
|
slots.forEach((c, i) => {
|
|
h3[c] = str(ini[`JMOK${i + 1}`] || "").trim() || `가격${i + 1}`;
|
|
h4[c] = "단 가";
|
|
h4[String.fromCharCode(c.charCodeAt(0) + 1)] = "페이지";
|
|
});
|
|
Object.assign(h3, { O: str(ini.JMOK6 || "적용 단가").trim(), Q: "비 고", _k: "head" });
|
|
Object.assign(h4, { O: "단 가", P: "페이지" });
|
|
C.push(sh, h3);
|
|
C.push(sh, h4);
|
|
idx.ofPrefix("M").forEach((r, i) => {
|
|
const c = code(r);
|
|
const lbl = po.codeMode ? c : po.padNo ? String(i + 1).padStart(4) : String(i + 1);
|
|
const cells = { A: lbl, B: str(r.NAME), C: str(r.SIZE), D: C.unitText(r.UNIT) };
|
|
Object.assign(cells, { _k: "item", _code: c });
|
|
slots.forEach((col, j) => {
|
|
cells[col] = num(r[`AMT_${j + 1}1`]);
|
|
const pg = str(r[`PG_${j + 1}1`]).trim();
|
|
if (pg) cells[String.fromCharCode(col.charCodeAt(0) + 1)] = pg;
|
|
});
|
|
const p = idx.price(c, 1);
|
|
cells.O = p.M + p.L + p.S;
|
|
const s = num(r.PG_1);
|
|
if (s >= 1 && s <= 5) cells.P = str(ini[`JMK${s}0`] || "").trim();
|
|
const rm = U.remarkOf(po, c, lbl, r);
|
|
if (rm) cells.Q = rm;
|
|
C.push(sh, cells);
|
|
});
|
|
return sh;
|
|
}
|
|
|
|
// 시트 차례 — 원본 STMATE 엑셀 내보내기 기본 순서(영월·울진 출력 목차) · 인쇄순서(F1-52)로 바꿈
|
|
const ORDER = [
|
|
"공사원가계산서",
|
|
"총괄내역서",
|
|
"설계내역서",
|
|
"일위대가목록표",
|
|
"일위대가표",
|
|
"일위대가수량금액집계표",
|
|
"단가산출근거목록표",
|
|
"단가산출근거",
|
|
"단가산출근거수량금액집계표",
|
|
"환율및기초자료",
|
|
"중기목록표",
|
|
"중기사용료",
|
|
"재료비목록표",
|
|
"노무비목록표",
|
|
"경비목록표",
|
|
"일식견적목록표",
|
|
"표준시장목록표",
|
|
"실적단가목록표",
|
|
"자재단가대비표",
|
|
"재료비수량금액집계표",
|
|
"노무비수량금액집계표",
|
|
"경비수량금액집계표",
|
|
"일식견적수량금액집계표",
|
|
"표준시장수량금액집계표",
|
|
"중기시간금액집계표",
|
|
];
|
|
|
|
const TOC = "〓 목 차 〓";
|
|
const GUIDE = "※※안내※※";
|
|
// 재등록 최소 판 — 24 판 출력은 「w24.02」 · 25 판부터 프로그램 판(원본 목차 D2 실측 · 울진 25.07 은 출력 프로그램 판)
|
|
function regVersion(ini) {
|
|
const v = str(ini.PRG_V || "").trim();
|
|
return num(v) >= 25 ? `w${v}` : "w24.02";
|
|
}
|
|
|
|
/**
|
|
* 목차 시트(〓 목 차 〓) — 원본 출력 그대로: D1 INITIAL 지움 검사 수식 · B2 제목 · D2~D3 재등록 안내 ·
|
|
* 4행부터 「n. 시트이름」 + 바로가기(→) · 안내 시트가 1번 · INITIAL 은 목록 밖
|
|
*/
|
|
function tocSheet(sheets, dbf) {
|
|
const ini = (dbf && dbf.start_ini) || {};
|
|
const sh = {
|
|
name: TOC,
|
|
title: "목 차",
|
|
rows: [],
|
|
cols: { B: 25.625, D: 23.625 },
|
|
tab: "0000FF",
|
|
};
|
|
const chk = `=IF('${Lk.INIT}'!C4,"※※ 기본정보의 일부가 삭제되어 Stmate로 변환할 수 없습니다 ※※","")`;
|
|
C.put(sh, 1, { _f: { D: chk }, _sz: { D: 12 }, _bold: { D: true } });
|
|
C.put(sh, 2, {
|
|
B: "목 차",
|
|
D: `※ 본 엑셀자료는 【STmate ${regVersion(ini)}】버전 이상에서 STmate 자료로 다시 등록할 수 있습니다.`,
|
|
_sz: { B: 20, D: 9 },
|
|
});
|
|
C.put(sh, 3, {
|
|
D: " 단, 임의로 수정한 내용(Sheet삭제, 산근내용변경, 기타계산식변경..)은 반영되지 않을 수 있습니다.",
|
|
_sz: { D: 9 },
|
|
});
|
|
sheets.forEach((s, i) => {
|
|
const cells = { B: `${i + 1}. ${s.name}`, D: `${s.name} →`, _sz: { B: 11, D: 9 } };
|
|
cells._links = { D: `${Lk.q(s.name)}!A1` };
|
|
const tag = Lk.tagOf(s);
|
|
cells._f = {
|
|
D: tag
|
|
? `=HYPERLINK("#"&${Lk.q(s.name)}!${tag}2&"!A1","${s.name} →")`
|
|
: `=HYPERLINK("#${Lk.q(s.name)}!A1","${s.name} →")`,
|
|
};
|
|
C.put(sh, 4 + i, cells);
|
|
});
|
|
return sh;
|
|
}
|
|
|
|
/**
|
|
* 안내 시트(※※안내※※) — 원본 출력 글 · 판·산근 수식 갈래 · 아래 알림(안내 및 오류) 줄
|
|
* 알림 차례 = 설계내역서 % 단위 → 총괄 이윤보정액(I) · 총공사비(D) → 일위대가표 → 단가산출근거 → 중기사용료
|
|
*/
|
|
function guideSheet(sheets, dbf, k) {
|
|
const ini = (dbf && dbf.start_ini) || {};
|
|
const sh = { name: GUIDE, title: "안내", rows: [], cols: { A: 9.125, B: 4.75 }, tab: "FF0000" };
|
|
const by = {};
|
|
for (const s of sheets) by[s.id || s.name] = s;
|
|
C.put(sh, 2, {
|
|
A: "목차 →",
|
|
B: "=== 엑셀파일에 작성에 대한 안내문(반드시 읽어보세요) ===",
|
|
_links: { A: `${Lk.q(TOC)}!B2` },
|
|
_f: { A: `=HYPERLINK("#${Lk.q(TOC)}!B2","목차 →")` },
|
|
_sz: { A: 9, B: 12 },
|
|
_bold: { B: true },
|
|
});
|
|
const line = (r, t, extra) =>
|
|
C.put(sh, r, Object.assign({ B: t, _sz: { B: 10 } }, extra || {}));
|
|
line(4, "엑셀파일 수정후 계산식 반영이 안될경우 :【 Ctrl+Alt(+Shift)+F9 】", {
|
|
_sz: { B: 14 },
|
|
_bold: { B: true },
|
|
});
|
|
[
|
|
"◈ 엑셀파일내의 산식은 기초단가, 복합단가, 내역서 및 총괄내역서까지 연결됩니다.",
|
|
" (모든산식은 단가만을 불러오며, 명칭, 규격, 단위, 비고.. 등은 TEXT로 입력됩니다)",
|
|
"◈ 각 Sheet우측에는 해당 항목을 찾아가는 위치이동(→) 링크가 있으며, 클릭하면 이동됩니다.",
|
|
" (Alt + ← 키를 이용하여 찾아갔던 항목을 되돌아 올 수 있습니다.)",
|
|
"◈ 자재단가 대비표의 적용단가는 수식이 적용되지 않았으나, 우측에 최소단가 수식을 입력하였습니다.",
|
|
"◈ 일위대가표 및 중기사용료 및 총괄내역서의 양식은 일반양식을 적용합니다.",
|
|
].forEach((t, i) => line(6 + i, t));
|
|
// 산근 수량 산식 갈래 — report_san 수치 글 다듬기와 같은 판단(FLT_JAB=1 · 원본 5건 실측 · 추정)
|
|
const sanFml = String(ini.FLT_JAB ?? "2").trim() === "1";
|
|
const san = by.단가산출근거;
|
|
const t12 = "◈ 단가산출근거의 수량부분은 산출근거 우측 연두색 부분에서 수정합니다. →";
|
|
if (sanFml && san && Lk.tagOf(san))
|
|
line(12, t12, {
|
|
_links: { B: `${Lk.q(san.name)}!Y3` },
|
|
_f: { B: `=HYPERLINK("#"&${Lk.q(san.name)}!${Lk.tagOf(san)}2&"!Y3","${t12}")` },
|
|
});
|
|
else line(12, "◈ 단가산출근거의 수량부분은 계산식이 입력되지 않았습니다.");
|
|
let r = 13;
|
|
const v25 = num(ini.PRG_V) >= 25;
|
|
if (v25 || sanFml)
|
|
line(
|
|
r++,
|
|
`◈ [목차] 및 [안내]${v25 ? " " : ""}Sheet의 이름을 변경하지 마시고, Sheet이름을 변경할때 공백문자 및 특수문자를 사용하지 마십시요.`,
|
|
);
|
|
r += 1;
|
|
line(r++, "※※※ 아래의 내용(안내 및 오류)을 확인/검토 하세요 ※※※", {
|
|
_bold: { B: true },
|
|
});
|
|
for (const w of guideNotes(by, dbf, k)) {
|
|
const ref = `${Lk.q(w.sh.name)}!${w.col}${w.row}`;
|
|
C.put(sh, r++, {
|
|
B: " → ",
|
|
C: w.text,
|
|
_links: { B: ref },
|
|
_f: { B: Lk.jump(w.sh, w.col, w.row, " → ") },
|
|
_sz: { B: 9, C: 9 },
|
|
_bold: w.bold ? { C: true } : undefined,
|
|
});
|
|
}
|
|
return sh;
|
|
}
|
|
|
|
/**
|
|
* 안내 알림 줄 — % 단위 코드 줄(산식 못 넣음 · 손료계산 형식 권고) · 총괄 이윤보정액 · 총공사비 확인
|
|
* 총공사비 확인 줄 = 이윤보정액이 천 원 끝전 맞춤 범위(1,000 미만)일 때(원본 5건 실측 · 대흥 905,980 = 없음 · 추정)
|
|
* 중기 % 줄 = 규격 「주연료의 %」(잡품·잡유)는 연료 줄 밑수 산식이 있어 알림 없음(대흥 실측)
|
|
*/
|
|
function guideNotes(by, dbf, k) {
|
|
const unit = {};
|
|
for (const r of (dbf && dbf.costn_rows) || []) unit[code(r)] = str(r.UNIT).trim();
|
|
const isP = (c) => /%$/.test(unit[c] || "");
|
|
const out = [];
|
|
const pctMsg = (s, row, c) =>
|
|
`【${s.name}】${row}번째줄 에서 사용된【${c}】코드의 %단위(${unit[c]}) 는 산식을 입력할 수 없으니 손료계산 형식으로 변경하십시요.`;
|
|
const scan = (s, kind, get, col) =>
|
|
s &&
|
|
s.rows.forEach((r, i) => {
|
|
const c = r._k === kind && get(r);
|
|
if (c && isP(c)) out.push({ sh: s, col: col(c), row: i + 1, text: pctMsg(s, i + 1, c) });
|
|
});
|
|
scan(
|
|
by.설계내역서,
|
|
"item",
|
|
(r) => r._code,
|
|
() => "A",
|
|
);
|
|
const sm = by.총괄내역서;
|
|
if (sm) {
|
|
// 변경 양식(당초·변경 두 줄)이면 둘 다 — 변경 줄 = 이름 칸 빈 다음 줄(영덕 실측)
|
|
const pair = (rn) => {
|
|
const nx = sm.rows[rn];
|
|
return nx && !nx.A && !nx.B && nx.D != null ? [rn, rn + 1] : [rn];
|
|
};
|
|
const iy = sm.rows.findIndex((r) => r._k === "cost" && /이\s*윤/.test(String(r.B || ""))) + 1;
|
|
const n = sm.rows.length;
|
|
const last = n > 1 && !sm.rows[n - 1].A && !sm.rows[n - 1].B ? [n - 1, n] : [n];
|
|
if (iy > 0)
|
|
for (const r of pair(iy))
|
|
out.push({
|
|
sh: sm,
|
|
col: "I",
|
|
row: r,
|
|
bold: true,
|
|
text: `【${sm.name}】${r}번째줄의 I열 이윤보정액은 총공사비 금액이 다를경우 임의조정(±α) 하십시요.`,
|
|
});
|
|
if (k && Math.abs(num(k.pretaxProfitAdj)) < 1000)
|
|
for (const r of last)
|
|
out.push({
|
|
sh: sm,
|
|
col: "D",
|
|
row: r,
|
|
text: `【${sm.name}】${r}번째줄의 D열에 계산된 총공사비 금액을 확인하십시요.`,
|
|
});
|
|
}
|
|
const own = { M: "I", L: "G", S: "K" };
|
|
scan(
|
|
by.일위대가표,
|
|
"line",
|
|
(r) => r._code,
|
|
(c) => own[c[0]] || "C",
|
|
);
|
|
scan(
|
|
by.단가산출근거,
|
|
"line",
|
|
(r) => r._ref,
|
|
() => "B",
|
|
);
|
|
const spec = Lk.specOf({ _dbf: dbf });
|
|
const jm = by.중기사용료;
|
|
if (jm)
|
|
jm.rows.forEach((r, i) => {
|
|
if (r._k === "line" && isP(r._code) && !/주연료/.test(spec[r._code] || ""))
|
|
out.push({
|
|
sh: jm,
|
|
col: own[r._code[0]] || "C",
|
|
row: i + 1,
|
|
text: pctMsg(jm, i + 1, r._code),
|
|
});
|
|
});
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* 전체 묶음 — dbf(불러온 자료) · state(sim_state) · computed(calculateAll) · n 차수
|
|
* 돌려줌: { sheets:[시트 모형…], byName }
|
|
*/
|
|
function buildBook(dbf, state, computed, n = 1, opts = {}) {
|
|
const unit = U.buildUnitSheets(dbf);
|
|
const boq = B.buildBoqSheets(dbf, state, computed, n, opts);
|
|
const pc = C.applyForm(priceCompareSheet(unit.ctx), dbf, "0105");
|
|
// 집계표(0501~0508) — 변경 차수면 당초·변경 두 줄 양식 · 단가산출근거(0402) 펼침
|
|
const base = n > 1 ? B.boqTree(dbf, B.stageOf(state, computed, 1).boq, 1) : null;
|
|
const coll = Co.buildCollSheets(
|
|
unit.ctx,
|
|
boq.설계내역서._tree,
|
|
Object.assign({ baseTree: base }, opts),
|
|
);
|
|
const san = C.applyForm(Sa.sanSheet(unit.ctx, opts), dbf, "0402");
|
|
const all = Object.assign({}, unit, boq, coll, { 자재단가대비표: pc, 단가산출근거: san });
|
|
const order = opts.order && opts.order.length ? opts.order : ORDER;
|
|
const sheets = order.filter((id) => all[id]).map((id) => Object.assign(all[id], { id }));
|
|
const init = In.initialSheet(dbf, state, sheets, n, opts);
|
|
Lk.linkBook(sheets, dbf); // 시트 간 참조 수식 · 바로가기(→) · 이름표 수식
|
|
const guide = guideSheet(sheets, dbf, boq.stage.kanjub);
|
|
const book = [tocSheet([guide].concat(sheets), dbf), guide].concat(sheets, [init]);
|
|
for (const s of book) delete s._tree;
|
|
return { sheets: book, byName: Object.fromEntries(book.map((s) => [s.name, s])) };
|
|
}
|
|
|
|
// SET_REP 서식 → 시트 이름(구현된 것) — 없으면 null
|
|
const FORM_SHEET = {
|
|
"0101": "재료비목록표",
|
|
"0102": "노무비목록표",
|
|
"0103": "경비목록표",
|
|
"0105": "자재단가대비표",
|
|
"0201": "환율및기초자료",
|
|
"0202": "중기목록표",
|
|
"0203": "중기사용료",
|
|
"0301": "일위대가목록표",
|
|
"0302": "일위대가표",
|
|
"0401": "단가산출근거목록표",
|
|
"0402": "단가산출근거",
|
|
"0104": "일식견적목록표",
|
|
"0106": "표준시장목록표",
|
|
"0206": "실적단가목록표",
|
|
"0501": "재료비수량금액집계표",
|
|
"0502": "노무비수량금액집계표",
|
|
"0503": "경비수량금액집계표",
|
|
"0504": "일식견적수량금액집계표",
|
|
"0505": "중기시간금액집계표",
|
|
"0506": "일위대가수량금액집계표",
|
|
"0507": "단가산출근거수량금액집계표",
|
|
"0508": "표준시장수량금액집계표",
|
|
"0601": "총괄내역서",
|
|
"0603": "설계내역서",
|
|
"0608": "공사원가계산서",
|
|
};
|
|
|
|
return { buildBook, priceCompareSheet, tocSheet, guideSheet, ORDER, FORM_SHEET };
|
|
})();
|
|
|
|
if (typeof window !== "undefined") window.STMATE_REPORT_BOOK = StmateReportBook;
|
|
if (typeof module !== "undefined" && module.exports) module.exports = StmateReportBook;
|