feat(est): configure separate print margins

This commit is contained in:
2026-11-07 15:33:06 +09:00
parent 7d9837a081
commit 1d1f27bdff
6 changed files with 58 additions and 13 deletions
+22
View File
@@ -1,6 +1,28 @@
from test_est_rea_parity import run
def test_print_margins_validate_independently_and_keep_legacy_setting():
result = run(
"math",
"""(()=>{
const p=EstNative.empty(); p.printSettings={margin:10,marginTop:11,marginBottom:12,marginLeft:13,marginRight:14};
const valid=EstModel.validate(p).printSettings; let rejected=0;
for (const key of ['marginTop','marginBottom','marginLeft','marginRight']) {
const q=structuredClone(p);q.printSettings[key]=51;try{EstModel.validate(q)}catch{rejected++}
}
return {valid,rejected};
})()""",
)
assert result["valid"] == {
"margin": 10,
"marginTop": 11,
"marginBottom": 12,
"marginLeft": 13,
"marginRight": 14,
}
assert result["rejected"] == 4
def test_custom_print_form_documents_are_saved_and_unknown_keys_rejected():
result = run(
"math",
+4 -4
View File
@@ -52,7 +52,7 @@ def test_estx_print_edits_patch_only_requested_fields_when_sample_exists():
pytest.skip("installed EST sample unavailable")
script = """const fs=require('fs'),m=require('./tools/est-test/import-estx.cjs'),e=require('./tools/est-test/export-estx.cjs'),s=require('./tools/est-test/server.cjs');
const bytes=fs.readFileSync(process.argv[1]),p=m.importEstx(bytes),before=structuredClone(p.printCatalog);
p.nativePrintChanges={'1':{PrintTop:13,PrintBottom:13,PrintLeft:13,PrintRight:13,PrintLineCount:19}};
p.nativePrintChanges={'1':{PrintTop:13,PrintBottom:14,PrintLeft:15,PrintRight:16,PrintLineCount:19}};
const retained=s.calculate(structuredClone(p)).nativePrintChanges['1'].PrintLineCount===19;
const out=e.exportEstx(p),q=m.importEstx(out),a=m.unzip(bytes),b=m.unzip(out),form=q.printCatalog.find(r=>r.Index===1);
const other=before.filter(r=>r.Index!==1).every(r=>JSON.stringify(r)===JSON.stringify(q.printCatalog.find(x=>x.Index===r.Index)));
@@ -65,9 +65,9 @@ def test_estx_print_edits_patch_only_requested_fields_when_sample_exists():
)
assert result == {
"top": 13,
"bottom": 13,
"left": 13,
"right": 13,
"bottom": 14,
"left": 15,
"right": 16,
"lines": 19,
"other": True,
"sameForm": True,
+2
View File
@@ -1,4 +1,6 @@
## Codex · EST 독립 테스트 앱 (2026-11-04)
- [x] 인쇄 서식의 상·하·좌·우 여백 개별 설정과 ESTX 저장 → 기존 단일 여백 호환·실제 서식 재읽기 확인.
- 자체 검증: 거창 ESTX 여백 13·14·15·16mm 미리보기·JSON 저장과 기존 단일 여백 호환을 Edge에서 확인. 설치 샘플 ESTX 저장·재읽기 포함 관련 시험 24개 통과.
- [x] 산출근거 계산 오류에서 해당 행으로 이동 → 실무 ESTX 오류와 중복 코드 안전 처리 확인.
- 자체 검증: 거창 실무 ESTX의 D0000000194:18 계산 오류에서 원본 해당 행으로 Edge 이동 확인. 중복 코드·범위 오류 차단 포함 시험 12개 통과.
- [x] 다른 이름으로 저장할 때 작업 이름 입력 → 원본·복사본 구분과 저장 취소 확인.
+2
View File
@@ -22,6 +22,8 @@
인쇄 미리보기에서 `서식 Excel 저장`을 누르면 선택한 표의 계산 결과를 별도 Excel 파일로 내려받습니다. 결과 요약과 계산 오류가 있으면 각각 별도 시트에 표시합니다. 이 파일은 출력용이며 전체 작업을 다시 가져오려면 전체 자료 Excel 또는 JSON 저장을 사용합니다.
인쇄 여백은 위·아래·왼쪽·오른쪽을 각각 설정합니다. 원본 인쇄 서식의 여백을 불러오고, 변경한 방향만 ESTX 수정 저장에 반영합니다.
저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. JSON은 모든 웹 업무와 원본 압축 파일·바이너리를 보관하는 전체 저장 형식입니다. ESTX 수정 저장은 아래 지원 조건을 따릅니다.
`다른 이름으로 저장`은 작업 이름을 입력받아 별도 작업으로 저장하며 기존 작업은 유지합니다.
+4
View File
@@ -319,6 +319,10 @@ const EstModel = (() => {
if (typeof s !== "object" || Array.isArray(s)) throw Error("인쇄 설정 형식 오류");
for (const [key, min, max] of [
["margin", 0, 50],
["marginTop", 0, 50],
["marginBottom", 0, 50],
["marginLeft", 0, 50],
["marginRight", 0, 50],
["font", 6, 20],
["rowsPerPage", 1, 100],
["startPage", 1, 99999],
+24 -9
View File
@@ -14,6 +14,10 @@ const EstReport = (() => {
rowsPerPage: 30,
notes: "",
...settings,
marginTop: settings.marginTop ?? settings.margin ?? 12,
marginBottom: settings.marginBottom ?? settings.margin ?? 12,
marginLeft: settings.marginLeft ?? settings.margin ?? 12,
marginRight: settings.marginRight ?? settings.margin ?? 12,
};
}
function preview(all = false) {
@@ -95,7 +99,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.margin}mm}*{box-sizing:border-box}body{font:${settings.font}pt "Malgun Gothic",sans-serif;color:#111;margin:0}
<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}
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}
@@ -162,7 +166,10 @@ const EstReport = (() => {
<label>용지 <select id="print-paper">${["A4", "A3"].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>
${[
["margin", "여백(mm)", 0, 50],
["marginTop", "위 여백(mm)", 0, 50],
["marginBottom", "아래 여백(mm)", 0, 50],
["marginLeft", "왼쪽 여백(mm)", 0, 50],
["marginRight", "오른쪽 여백(mm)", 0, 50],
["font", "글자(pt)", 6, 20],
["rowsPerPage", "페이지당 행", 1, 100],
["startPage", "시작 쪽", 1, 99999],
@@ -206,8 +213,9 @@ const EstReport = (() => {
1,
Math.min(100, Number(original.PrintLineCount) || 30),
);
if (Number.isFinite(original.PrintLeft))
next.margin = Math.max(0, Math.min(50, original.PrintLeft));
for (const side of ["Top", "Bottom", "Left", "Right"])
if (Number.isFinite(original[`Print${side}`]))
next[`margin${side}`] = Math.max(0, Math.min(50, original[`Print${side}`]));
}
}
if (key === "notes") {
@@ -215,15 +223,15 @@ const EstReport = (() => {
EstApp.state.project.documents[next.form] = value;
}
if (
["margin", "rowsPerPage"].includes(key) &&
["marginTop", "marginBottom", "marginLeft", "marginRight", "rowsPerPage"].includes(
key,
) &&
next.form !== "current" &&
EstApp.state.project.printCatalog?.some((row) => String(row.Index) === next.form)
) {
EstApp.state.project.nativePrintChanges ||= {};
const changes = (EstApp.state.project.nativePrintChanges[next.form] ||= {});
if (key === "margin")
for (const side of ["PrintTop", "PrintBottom", "PrintLeft", "PrintRight"])
changes[side] = value;
if (key.startsWith("margin")) changes[`Print${key.slice(6)}`] = value;
else changes.PrintLineCount = value;
}
if (key === "form") next.notes = EstApp.state.project.documents?.[value] || "";
@@ -232,7 +240,14 @@ const EstReport = (() => {
if (key === "form") {
const content = EstApp.byId("dialog-content"),
current = EstApp.state.project.printSettings;
for (const name of ["notes", "margin", "rowsPerPage"])
for (const name of [
"notes",
"marginTop",
"marginBottom",
"marginLeft",
"marginRight",
"rowsPerPage",
])
content.querySelector(`[data-print=${name}]`).value = current[name];
}
preview(EstApp.byId("print-scope")?.value === "all");