feat(est-test): 엑셀형 산출서 셀 소수자리 서식과 인쇄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 13:17:02 +09:00
co-authored by Claude Opus 5.5
parent d515875322
commit 04cba1082c
2 changed files with 36 additions and 3 deletions
+3 -1
View File
@@ -476,7 +476,9 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
- 검증완료: `test_est_excel_sheet.py` 2개(수식·함수·순환 오류·셀 합, 설치 main.estx 수식 221개 해석·토적계산표 항목 V000001~3 추정 연결 E33·G33·I33·수량 17·12·58·거리 바꾸면 수량 변함·원본 재저장 동일·편집 시 ESTX 거부) 통과. 다시 계산한 값이 Excel 저장값(0, 갱신 안 됨)이 아니라 EST 저장 수량(17·12·58·25.16·32.76·15.92·2.17)과 맞음. ORCA 브라우저에서 거리 C7 20→40 → 항목 17→34·12→24·58→116, 위 합 → SUM(E6:E33), 샘플 복구.
- [x] 3단계 편집 동작(원본 TFRMMULEXCEL 단추·팝업) — 범위 고르기(Shift), 행·열 삽입/삭제(수식 참조 이동·지운 칸 #REF!·범위 줄임·병합·수량 연결 함께 이동), 잘라내기·복사·붙여넣기(상대 참조 이동, Ctrl+X·C·V·Delete), 내용지우기, 굵게·기울임·밑줄·정렬, 병합·분리, 왼쪽·오른쪽 대각선·삭제, 다시 계산, 품명·규격·단위로 지정(셀 글자를 항목에). 원본 .xls 병합 정보(MERGEDCELLS) 읽어 머리글 그대로 표시. 되돌리기는 앱 취소(↶).
- 검증완료: `test_est_excel_sheet.py` 4개(행 넣기·지우기·열 넣기 참조, 붙여넣기 상대 참조, 병합 이동·분리, 서식 켜고 끄기, 설치 main.estx 병합 14개) 등 44개 통과. ORCA 브라우저에서 설치 main.estx 토적계산표: 병합 머리글 14개 표시, 행 삽입·삭제 뒤 수량 연결 E33 유지, 범위 굵게·병합, 복사·붙여넣기 =(F33+F34)/2*E34, 임시 작업 삭제.
- 남음: 원본 셀↔항목 대응 근거, 셀 서식 대화(글꼴·테두리·표시형식), .xls 저장.
- [x] 4단계 — 셀 서식 소수자리(actFormatCell 일부: 0~4자리·해제), 미리보기·인쇄(tbsPreView·tbsPrint — 고른 시트 격자만 새 창에서 인쇄).
- 검증완료: 바인딩 시험 통과. ORCA 브라우저에서 =10/3 → 소수 2자리 「3.33」, 인쇄 창 html 에 표(도구 단추 없음), 샘플 복구.
- 남음: 원본 셀↔항목 대응 근거, 셀 서식의 글꼴·테두리, .xls 저장.
- [x] 수량산출 누계(원본 TfrmMulWord ActionAdd Ctrl+U → 0x11155c4, 재계산 0x111c0b0 — 디스어셈블 근거) — 원본 F·a 표지를 `` 쌍·`row.totals` 로 읽고, 위쪽 품명 줄(포함)/앞 누계(제외)부터 줄마다 첫 결과 합을 표지 반올림(F+6자리+방식+자리)으로 매 계산 갱신, 커서 자리 누계 넣기. 원본 TFRMMULNAMEINPUT 「누계계산」 단추는 실제로 지정해제와 같은 처리라 따로 만들지 않음.
- 검증완료: `test_est_quantity_total.py`(소계 범위·앞 누계 제외·반올림 표지·값 갱신 6,003·넣기 이름·표지 안 거부·원본 F 표지 읽기) 통과, 수량 시험 포함 26개 통과. ORCA 브라우저에서 「소계」 줄 끝에 누계 → ⟦누계⟧9⟦누계⟧(6+3), 샘플 복구.
- [x] 갑지·표지·공사설명서·시방서(원본 TFRMGABGI·TFRMCOVER·TFRMESTEDITOR, DatGabgi/DatCover/DatExplean/DatSiBang.opt — 디스어셈블 근거) — `midas-read.cjs` MIDAS 레코드·WPTools 글자 읽기 → `project.docs`, 편집 창, 공사명↔공사 이름 동기화, 공사금액 자동입력 두 방식·한글금액(일 생략 없음), 설명서·시방서 갑지 붙여넣기, 인쇄 미리보기 8·9·10·11. 문서 편집 작업은 ESTX 저장 거부.
+33 -2
View File
@@ -29,6 +29,7 @@ const EstExcelView = (() => {
.join("");
return [
btn("excel-recalc", "다시 계산"),
btn("excel-print", "미리보기·인쇄"),
btn("excel-cut", "잘라내기"),
btn("excel-copy", "복사"),
btn("excel-paste", "붙여넣기"),
@@ -45,6 +46,7 @@ const EstExcelView = (() => {
btn("excel-style", "오른쪽", 'data-name="align" data-value="right"'),
btn("excel-merge", "병합"),
btn("excel-split", "분리"),
`<select data-excel-digits aria-label="셀 서식 소수자리"><option value="">셀 서식(소수자리)</option>${[0, 1, 2, 3, 4].map((d) => `<option value="${d}">소수 ${d}자리</option>`).join("")}<option value="-">서식 해제</option></select>`,
btn("excel-style", "왼쪽대각선", 'data-name="diag" data-value="left"'),
btn("excel-style", "오른쪽대각선", 'data-name="diag" data-value="right"'),
btn("excel-style", "대각선 삭제", 'data-name="diag" data-value=""'),
@@ -104,7 +106,12 @@ const EstExcelView = (() => {
cell == null
? ""
: typeof cell.value === "number"
? number(cell.value)
? s.digits != null && s.digits !== ""
? cell.value.toLocaleString("ko-KR", {
minimumFractionDigits: Number(s.digits),
maximumFractionDigits: Number(s.digits),
})
: number(cell.value)
: esc(cell.value);
const style = [
cell?.formula || s.b ? "font-weight:bold" : "",
@@ -173,6 +180,18 @@ const EstExcelView = (() => {
EstApp.render();
});
document.addEventListener("change", (event) => {
if (event.target.dataset.excelDigits !== undefined) {
const v = event.target.value,
state = EstApp.state,
sheet = sheetOf(state),
sel = state.excelCell;
if (!v || !sheet?.excel || !sel) return;
EstApp.change(() => {
EstExcelEdit.style(sheet.excel[sel.n], range(sel), "digits", v === "-" ? "" : v);
sheet.excelEdited = true;
}, false);
return;
}
if (event.target.dataset.excelBar === undefined) return;
const state = EstApp.state,
sel = state.excelCell,
@@ -217,6 +236,7 @@ const EstExcelView = (() => {
"excel-style",
"excel-merge",
"excel-split",
"excel-print",
]);
function handle(name, state, node) {
if (!names.has(name)) return false;
@@ -266,7 +286,18 @@ const EstExcelView = (() => {
edit(() => EstExcelEdit.style(page, box, node.dataset.name, node.dataset.value));
else if (name === "excel-merge") edit(() => EstExcelEdit.merge(page, box));
else if (name === "excel-split") edit(() => EstExcelEdit.split(page, box));
else {
else if (name === "excel-print") {
// 미리보기·인쇄(tbsPreView·tbsPrint): 고른 시트 격자만 새 창에
const html = render({ ...sheet, excel: [page], excelLinks: {} }, { excelCell: null })
.replace(/<div class="business-tools">[\s\S]*?<\/div>/, "")
.replace(/ data-excel-pick="[^"]*"/g, "");
const win = open("", "_blank");
if (!win) throw Error("새 창을 열 수 없습니다. 팝업 차단을 확인하세요.");
win.document.write(
`<!doctype html><meta charset="utf-8"><title>${EstApp.esc(sheet.Name || page.name)}</title><style>table{border-collapse:collapse;font:10pt sans-serif}td,th{border:1px solid #888;padding:2px 4px}th{background:#eee}</style>${html}<script>print()<\/script>`,
);
win.document.close();
} else {
const code = document.querySelector("[data-excel-item]")?.value,
item = sheet.items?.find((i) => i.Code === code);
if (name === "excel-field") {