feat(est-test): 인쇄 용지 A0·A1·B4 와 인쇄 기본양식 저장·열기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 13:00:30 +09:00
co-authored by Claude Opus 5.5
parent 21f49df818
commit 854e251129
5 changed files with 40 additions and 6 deletions
+2
View File
@@ -487,6 +487,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
- 남음: 구성비·요율 전환(CalcBase), 약어 표시(SName), 제비율 기준 문서 보기(원본 한글 파일).
- [x] 수량산출서 검색·전체보기(원본 TFRMMULLANG btnSerch·btnAllDisplay — 빈칸·|·괄호 무시 포함 검색) — 고르기 줄을 `native-quantity-sheets.js` 로 옮겨 700줄 제한 유지.
- 검증완료: `test_est_quantity_sheets.py` 검색 시험 포함 수량 시험 22개 통과. ORCA 브라우저에서 「집수」 검색 → 항목에 집수정이 있는 「배수」만·선택 이동, 전체보기 → 3개 복귀, 샘플 복구.
- [x] 인쇄 용지 A0·A1·B4 와 인쇄 기본양식(원본 TFRMSCHEDULE mnuA0~B4 · TFRMESTPREVIEW2 mnuFormatSave·mnuFromatOpen) — 용지 A4·A3·B4·A1·A0(A0·A1 은 mm 크기, 가로면 바꿔 씀), 인쇄 창 「기본양식저장」(용지·여백·글자·줄 수 등, 서식 고르기·본문 글 제외 — 양식 보관함 종류 print) · 「기본양식열기」.
- 검증완료: 바인딩·양식 시험 3개 통과. ORCA 브라우저에서 A1 가로 → @page 841mm 594mm, 기본양식 저장 뒤 A4 로 바꾸고 열기 → A1 복원, 양식·설정 지워 복구.
- [x] 원가계산서 내용 삭제·기본양식으로 저장(원본 TFRMWONGABASE popupContentDel A7 · popupSaveAsMain) — 원가계산서(W) 「선택 줄 내용 삭제」: 연결·금액·비율·요율·산출근거·자리 비움(이름·기호 유지, 재계산 없음). 양식 목록 「기본양식으로 저장」: 이름 「기본양식」 — 새 내역서를 만들 때 원가계산서로 씀.
- 검증완료: 양식·원가·바인딩 시험 8개 통과. ORCA 브라우저에서 간접노무비 줄 내용 삭제(이름 유지·식·요율·금액 비움), 기본양식 저장 → 새 내역서 원가 31줄, 양식 지우고 샘플 복구.
- [x] 갑지·표지 양식(원본 TFRMGABGI·TFRMCOVER popupSaveFormat·popupLoadFormat·popupFormatTableS·popupSaveToDefault·popupInit·popupPrint) — 편집 창 위 단추: 양식목록(불러오기·지우기), 양식저장하기(이름), 기본양식으로 저장(처음 여는 갑지·표지에 채움), 초기화(문서 지우고 다시 열기), 인쇄(서식 8·9 미리보기로). 양식은 서버 보관함 종류 document 에 { docKind, fields }, 공사명은 넣지 않음.
+1 -1
View File
@@ -3,7 +3,7 @@
const fs = require("node:fs");
const path = require("node:path");
const kinds = new Set(["wonga", "document", "machformula", "options"]);
const kinds = new Set(["wonga", "document", "machformula", "options", "print"]);
const LIMIT = 2 * 1024 * 1024;
function file(directory) {
+2
View File
@@ -284,6 +284,8 @@ const EstActions = (() => {
else if (name === "import-xlsx") EstApp.byId("excel-file").click();
else if (name === "print") await EstReport.open();
else if (name === "print-document") EstReport.print();
else if (name === "print-default-save") await EstReport.saveDefault();
else if (name === "print-default-open") await EstReport.openDefault();
else if (name === "csv") {
const rows = activeRows(),
keys = EstModel.columns(state.view);
+1 -1
View File
@@ -337,7 +337,7 @@ const EstModel = (() => {
throw Error("인쇄 설정 범위 오류");
}
if (
(s.paper != null && !["A3", "A4"].includes(s.paper)) ||
(s.paper != null && !["A0", "A1", "A3", "A4", "B4"].includes(s.paper)) ||
(s.orientation != null && !["landscape", "portrait"].includes(s.orientation))
)
throw Error("인쇄 용지/방향 오류");
+34 -4
View File
@@ -23,6 +23,13 @@ const EstReport = (() => {
marginRight: settings.marginRight ?? settings.margin ?? 12,
};
}
// 원본 공정표 용지(mnuA0·A1·A3·A4·B4) — CSS 낱말이 없는 A0·A1 은 mm 로
function pageSize(paper, orientation) {
const mm = { A0: [841, 1189], A1: [594, 841] }[paper];
if (!mm) return `${paper} ${orientation}`;
const [w, h] = orientation === "landscape" ? [mm[1], mm[0]] : mm;
return `${w}mm ${h}mm`;
}
function preview(all = false) {
const { state, esc } = EstApp;
const settings = setting(),
@@ -106,7 +113,7 @@ const EstReport = (() => {
: state.project.calculation;
const errors = [...(totals?.errors || []), ...(output?.errors || [])];
const source = `<!doctype html><html lang="ko"><head><meta charset="UTF-8"><title>${esc(state.project.name)} · ${esc(title)}</title>
<style>@page{size:${settings.paper} ${settings.orientation};margin:${settings.marginTop}mm ${settings.marginRight}mm ${settings.marginBottom}mm ${settings.marginLeft}mm}*{box-sizing:border-box}body{font:${settings.font}pt "Malgun Gothic",sans-serif;color:#111;margin:0}
<style>@page{size:${pageSize(settings.paper, settings.orientation)};margin:${settings.marginTop}mm ${settings.marginRight}mm ${settings.marginBottom}mm ${settings.marginLeft}mm}*{box-sizing:border-box}body{font:${settings.font}pt "Malgun Gothic",sans-serif;color:#111;margin:0}
h1{font-size:17pt;margin:0 0 4mm}h2{font-size:12pt;margin:0 0 3mm}.summary{margin:2mm 0 4mm}
table{width:100%;border-collapse:collapse;font-size:inherit}thead{display:table-header-group}tr{break-inside:avoid}
th,td{border:1px solid #777;padding:1.5mm;vertical-align:top;overflow-wrap:anywhere}th{background:#eee}
@@ -179,7 +186,7 @@ const EstReport = (() => {
`<option value="${row.Index}" ${String(row.Index) === s.form ? "selected" : ""}>${esc(row.Name)}</option>`,
)
.join("")}</select></label>
<label>용지 <select id="print-paper">${["A4", "A3"].map((v) => `<option ${v === s.paper ? "selected" : ""}>${v}</option>`).join("")}</select></label>
<label>용지 <select id="print-paper">${["A4", "A3", "B4", "A1", "A0"].map((v) => `<option ${v === s.paper ? "selected" : ""}>${v}</option>`).join("")}</select></label>
<label>방향 <select id="print-orientation"><option value="landscape" ${s.orientation === "landscape" ? "selected" : ""}>가로</option><option value="portrait" ${s.orientation === "portrait" ? "selected" : ""}>세로</option></select></label>
${[
["marginTop", "위 여백(mm)", 0, 50],
@@ -198,7 +205,7 @@ const EstReport = (() => {
<label id="print-gong-wrap" ${["115", "116", "117", "118"].includes(String(s.form)) ? "" : "hidden"}>공람용 방법 <select id="print-gong"><option value="0" ${Number(s.PrintGongMethod) === 0 ? "selected" : ""}>수량만</option><option value="1" ${Number(s.PrintGongMethod) === 1 ? "selected" : ""}>단가만</option><option value="2" ${Number(s.PrintGongMethod) === 2 ? "selected" : ""}>수량·단가</option></select></label>
<label>업체명<input data-print="company" value="${esc(s.company)}"></label>
${table ? '<label>출력 범위 <select id="print-scope"><option value="current">현재 호표·검색 결과 전체 행</option><option value="all">현재 자료의 모든 호표·행</option></select></label>' : ""}
<button data-action="print-document">인쇄 / PDF 저장</button><button data-action="export-report-xlsx">서식 Excel 저장</button><span id="print-error" role="status"></span></div>
<button data-action="print-document">인쇄 / PDF 저장</button><button data-action="export-report-xlsx">서식 Excel 저장</button><button data-action="print-default-save">기본양식저장</button><button data-action="print-default-open">기본양식열기</button><span id="print-error" role="status"></span></div>
<label>문서 본문(표지·갑지·설명서·시방서·심사·공정 문서)<textarea data-print="notes" rows="3" style="width:100%">${esc(s.notes)}</textarea></label>
<iframe id="print-frame" title="인쇄할 문서" style="width:100%;height:65vh;border:1px solid #aaa"></iframe>`,
);
@@ -353,5 +360,28 @@ const EstReport = (() => {
EstApp.byId("print-error").textContent = error.message;
}
}
return { open, preview, print, exportExcel };
// 원본 미리보기 기본양식저장·기본양식열기(TFRMESTPREVIEW2 mnuFormatSave·mnuFromatOpen) — 서식 고르기·본문 글은 빼고 용지·여백·글자·줄 수 등
const POST = (body) => ({
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
async function saveDefault() {
const { form, notes, ...data } = setting();
await EstApp.api("/api/forms", POST({ kind: "print", name: "기본양식", data }));
EstApp.byId("print-error").textContent = "인쇄 기본양식을 저장했습니다.";
}
async function openDefault() {
const found = (await EstApp.api("/api/forms")).find(
(f) => f.kind === "print" && f.name === "기본양식",
);
if (!found) throw Error("저장한 인쇄 기본양식이 없습니다.");
const { form, notes, ...data } = found.data;
EstApp.change((project) => {
project.printSettings = { ...(project.printSettings || {}), ...data };
}, false);
await open();
}
return { open, preview, print, exportExcel, saveDefault, openDefault, pageSize };
})();