Files
Aislo/tools/stmate-test/report_book.js
T
eomsangdonandClaude Opus 5.5 3d8fddfc34 feat(stmate-test): 보고서·제잡비·식언어·수량산출·화면 묶음 합침 · 재계산 후 저장 무변경
- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면
- 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면
- 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림
- 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표
- 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0)
- DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인)
- 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 19:12:04 +09:00

151 lines
6.3 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 { 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 = po.remarkHopyo && !po.codeMode ? lbl : str(r.BQCODE).trim();
if (rm) cells.Q = rm;
C.push(sh, cells);
});
return sh;
}
// 시트 차례 — 원본 STMATE 엑셀 내보내기 순서(목차 번호)
const ORDER = [
"공사원가계산서",
"총괄내역서",
"설계내역서",
"일위대가목록표",
"일위대가표",
"단가산출근거목록표",
"환율및기초자료",
"중기목록표",
"중기사용료",
"재료비목록표",
"노무비목록표",
"경비목록표",
"자재단가대비표",
];
// 목차 시트(〓 목 차 〓) — 시트 이름과 바로가기(엑셀에서 하이퍼링크)
function tocSheet(names, pname) {
const sh = { name: "〓 목 차 〓", title: "목 차", rows: [], cols: {} };
C.put(sh, 2, {
B: "목 차",
D: "※ 본 엑셀자료는 STMATE 재현 앱에서 만든 출력본입니다.",
_k: "title",
});
C.put(sh, 3, { D: ` 공사명 : ${pname}` });
["※※안내※※"]
.concat(names)
.forEach((n, i) => C.put(sh, 4 + i, { B: `${i + 1}. ${n}`, D: `${n} →`, _link: n }));
return sh;
}
// 안내 시트(※※안내※※) — 원본 안내문 요지 + 금액 확인 줄
function guideSheet(k) {
const sh = { name: "※※안내※※", title: "안내", rows: [], cols: {} };
C.put(sh, 2, {
A: "목차 →",
B: "=== 엑셀파일에 작성에 대한 안내문(반드시 읽어보세요) ===",
_link: "〓 목 차 〓",
});
C.put(sh, 4, { B: "◈ 금액은 STMATE 재현 앱 계산 엔진(calc.js) 결과를 값으로 적었습니다." });
C.put(sh, 5, { B: "◈ 각 시트의 비목 열 순서는 설정(BMKNM)을 따릅니다." });
C.put(sh, 6, { B: "◈ [목차] 및 [안내] 시트의 이름을 바꾸지 마십시요." });
if (k) {
C.put(sh, 8, { B: "※※※ 아래의 내용(안내 및 오류)을 확인/검토 하세요 ※※※" });
C.put(sh, 9, {
C: `【총괄내역서】 총공사비 = ${C.commas(k.grandTotal)}원 (이윤 보정액 ${C.commas(k.pretaxProfitAdj || 0)}원)`,
});
}
return sh;
}
/**
* 전체 묶음 — 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");
const all = Object.assign({}, unit, boq, { 자재단가대비표: pc });
const sheets = ORDER.filter((id) => all[id]).map((id) => Object.assign(all[id], { id }));
const names = sheets.map((s) => s.name);
const pname = C.projectName(dbf);
const book = [tocSheet(names, pname), guideSheet(boq.stage.kanjub)].concat(sheets);
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": "단가산출근거목록표",
"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;