From 8d4d23e7830c077760d917753d15d81d48c58789 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 15:37:20 +0900 Subject: [PATCH] feat(est): save quantity item labels in ESTX --- resources/tester/test_est_native_export.py | 26 ++++++++++++++++++++++ tools/est-test/PLAN.md | 2 ++ tools/est-test/README.md | 2 ++ tools/est-test/export-quantity.cjs | 6 +++-- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/resources/tester/test_est_native_export.py b/resources/tester/test_est_native_export.py index 4876df03e..53ea53345 100644 --- a/resources/tester/test_est_native_export.py +++ b/resources/tester/test_est_native_export.py @@ -90,6 +90,32 @@ def test_quantity_document_edits_keep_styles_and_bookmarks_and_block_marker_edit assert result == {"same": True, "tags": True, "rejected": True} +def test_estx_quantity_item_labels_round_trip_when_sample_exists(): + sample = Path("C:/Dasansoft/Est Plus 2/Data/ProjectData/main.estx") + if not 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'); + const p=m.importEstx(fs.readFileSync(process.argv[1])),before=structuredClone(p.quantitySheets),sheet=p.quantitySheets[0]; + sheet.items[0].Name='changed name';sheet.items[0].Size='changed size'; + const q=m.importEstx(e.exportEstx(p)),item=q.quantitySheets[0].items[0]; + const other=JSON.stringify(q.quantitySheets[0].items.slice(1))===JSON.stringify(before[0].items.slice(1)); + const rest=JSON.stringify(q.quantitySheets.slice(1))===JSON.stringify(before.slice(1)); + p.quantitySheets[0].items[0].TargetCode='new target';let rejected=false;try{e.exportEstx(p)}catch{rejected=true} + console.log(JSON.stringify({name:item.Name,size:item.Size,other,rest,rejected}));""" + result = json.loads( + subprocess.check_output(["node", "-e", script, str(sample)], cwd=ROOT, timeout=45).decode( + "utf-8" + ) + ) + assert result == { + "name": "changed name", + "size": "changed size", + "other": True, + "rest": True, + "rejected": True, + } + + def test_quantity_document_edit_across_character_styles_preserves_tags(): script = r"""const q=require('./tools/est-test/native-quantity.cjs'),w=require('./tools/est-test/export-quantity.cjs'); const doc='
abcdefghi
'; diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 2757ad8be..2b8d70e0f 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -1,4 +1,6 @@ ## Codex · EST 독립 테스트 앱 (2026-11-04) +- [x] 기존 수량산출 항목의 품명·규격을 ESTX에 저장 → 재열기·다른 항목 보존·연결 변경 차단 확인. + - 자체 검증: 설치 main.estx 기존 항목의 품명·규격 저장·재열기, 다른 항목·문서 보존과 연결 변경 차단 확인. 저장·수량 관련 시험 22개 통과. - [x] 인쇄 서식의 상·하·좌·우 여백 개별 설정과 ESTX 저장 → 기존 단일 여백 호환·실제 서식 재읽기 확인. - 자체 검증: 거창 ESTX 여백 13·14·15·16mm 미리보기·JSON 저장과 기존 단일 여백 호환을 Edge에서 확인. 설치 샘플 ESTX 저장·재읽기 포함 관련 시험 24개 통과. - [x] 산출근거 계산 오류에서 해당 행으로 이동 → 실무 ESTX 오류와 중복 코드 안전 처리 확인. diff --git a/tools/est-test/README.md b/tools/est-test/README.md index 99cfd7fae..3b6ed378c 100644 --- a/tools/est-test/README.md +++ b/tools/est-test/README.md @@ -56,6 +56,8 @@ Ctrl+S는 입력 중인 셀도 확정한 뒤 저장합니다. 연속 저장 요 수량 화면에서 새 산출서를 만들고 이름과 산출 항목의 품명·규격을 편집할 수 있습니다. `산출 행 추가`로 결과 표지가 연결된 행과 항목을 함께 만든 뒤 수식을 입력하고, 연결 내역코드를 지정해 계산 수량을 내역에 반영합니다. 행 아래 삽입, 위·아래 이동과 산출 행·산출서 삭제도 가능하며, 이 구조 변경은 JSON 저장과 다시 열기를 지원합니다. 기존 연결은 내역코드를 비워 해제할 수 있습니다. +원본 ESTX의 기존 수량산출 항목은 품명·규격을 수정해 ESTX에 저장할 수 있습니다. 연결 변경과 새 항목 추가·삭제는 계속 JSON으로 보관합니다. + 재계산 메뉴의 수량 반영도 현재 선택한 산출서 또는 직접 작성 수식표를 사용합니다. 반영 전에 모든 연결 대상과 수식을 검사하며, 기존 원본 연결 데이터는 그대로 보관합니다. 재계산 메뉴의 `모든 수량산출 합산 반영`은 모든 산출서와 직접 작성 수식표의 연결 수량을 내역 행별로 합산합니다. 어느 산출서든 계산 오류가 있거나 연결 대상이 모호하면 전체 반영을 중단합니다. diff --git a/tools/est-test/export-quantity.cjs b/tools/est-test/export-quantity.cjs index 7167ae5c0..a3dfbc924 100644 --- a/tools/est-test/export-quantity.cjs +++ b/tools/est-test/export-quantity.cjs @@ -90,8 +90,10 @@ function editStyledRuns(div, before, after, parse) { // Change text inside an existing styled run, never WPTools tags or bookmarks. function editDocument(text, oldSheet, sheet) { + const withoutLabels = (items) => items.map(({ Name, Size, ...rest }) => rest); if ( - !same({ ...oldSheet, rows: [] }, { ...sheet, rows: [] }) || + !same({ ...oldSheet, rows: [], items: [] }, { ...sheet, rows: [], items: [] }) || + !same(withoutLabels(oldSheet.items), withoutLabels(sheet.items)) || oldSheet.rows.length !== sheet.rows.length ) throw Error("수량산출서 목록·연결·행 구조 변경은 ESTX 저장을 지원하지 않습니다."); @@ -193,7 +195,7 @@ function updateQuantities(project, base, cp949, memory) { if (current.rows[n].LineData !== desired) throw Error("수량산출 결과 재계산 검산 실패"); } if (!same(row.MulList, oldRow.MulList)) throw Error("수량산출서 목록 데이터 충돌"); - if (!same(calculated.items, current.items)) { + if (!same(calculated.items, originals[i].items)) { row.MulList = { binary: memory(Buffer.from(row.MulList.binary, "base64"), calculated.items).toString( "base64",