feat(est): configure separate print margins
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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] 다른 이름으로 저장할 때 작업 이름 입력 → 원본·복사본 구분과 저장 취소 확인.
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
인쇄 미리보기에서 `서식 Excel 저장`을 누르면 선택한 표의 계산 결과를 별도 Excel 파일로 내려받습니다. 결과 요약과 계산 오류가 있으면 각각 별도 시트에 표시합니다. 이 파일은 출력용이며 전체 작업을 다시 가져오려면 전체 자료 Excel 또는 JSON 저장을 사용합니다.
|
||||
|
||||
인쇄 여백은 위·아래·왼쪽·오른쪽을 각각 설정합니다. 원본 인쇄 서식의 여백을 불러오고, 변경한 방향만 ESTX 수정 저장에 반영합니다.
|
||||
|
||||
저장은 이 폴더의 `data/`에 작업을 보관하고 `.estweb.json` 파일도 내려받습니다. 브라우저 임시 저장은 같은 탭에서 복원하는 용도입니다. JSON은 모든 웹 업무와 원본 압축 파일·바이너리를 보관하는 전체 저장 형식입니다. ESTX 수정 저장은 아래 지원 조건을 따릅니다.
|
||||
|
||||
`다른 이름으로 저장`은 작업 이름을 입력받아 별도 작업으로 저장하며 기존 작업은 유지합니다.
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user