diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index 571bb77fb..36f9ae411 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -489,6 +489,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] 견적서 엑셀변환(원본 TFRMEXCELCONVERTESTIPRN btnConvert) — 견적서 창 「엑셀변환」: 머리(제목·공사명·귀하·견적일자·합계금액 한글·공급자)와 레벨에 맞춘 내역 줄(품명·규격·단위·수량·재료/노무/경비/합계 단가·금액·비고)을 .xlsx 로 내려받기. 원본의 인쇄비율·셀 수식은 하지 않음(값만). + - 검증완료: 견적서·바인딩 시험 통과. ORCA 브라우저에서 「EST REA 보관 샘플_견적서.xlsx」(7,490 바이트) 내려받기, 샘플 복구. - [x] 품셈 보기·품셈자료 붙여넣기(원본 barbtnBook → ProductBook.exe/TfrmBookViewer, popupPasteBook 10d6e28 — spec_book) — 서버 `book-store.cjs` 가 원본 설치 BookData(7권, DBISAM 암호 표: MD5 키 Blowfish-CBC, BLOB zlib RTF)를 읽음(Node 에 Blowfish 가 없어 π 자리로 상수 만들어 직접 구현, `EST_BOOK_DIR` 로 경로 변경). 화면: 품셈 고르기·목차 트리(펼치기·접기)·본문(표 칸 나눔)·검색(제목 / 제목+본문 / 키워드, 검색어 빈칸·'|' 지움)·본문 검색 강조·복사하기. 붙여넣기(복사한 뒤만): 단가산출은 첫 줄을 마지막 산출근거 칸 커서 자리, 둘째 줄부터 아래 새 줄, 내역·일위·기계는 마지막 칸에 마지막 줄(수치 칸 검사). 사용자 결정: 원본처럼 잠긴 품셈 파일을 읽음. - 검증완료: `test_est_book.py`(Blowfish 상수 첫·끝 값, RTF 글자, 설치 7권·산림 563마디·교목굴취 표 줄·제목·본문 검색) 및 바인딩 시험 통과. ORCA 브라우저에서 산림품셈 목차 펼치기 → 5-1-2 교목굴취 표 34칸, 「보통인부」 강조, 복사 → 단가산출 붙여넣기 363→385줄, 샘플 복구. - [x] 수량산출서 삽입·선택공종 삭제(원본 TFRMMULLANG btnInsert·btnSelectDel) — 「삽입」: 현재 산출서 앞에 공종명칭·규격을 물어 새 산출서, 「선택공종 삭제」: 고른 여러 산출서를 한 번에 지움. diff --git a/tools/est-test/native-estimate-print.js b/tools/est-test/native-estimate-print.js index f7012f84f..8cdf77218 100644 --- a/tools/est-test/native-estimate-print.js +++ b/tools/est-test/native-estimate-print.js @@ -171,7 +171,7 @@ const EstEstimatePrint = (() => { ``; EstApp.showDialog( "견적서 작성", - `
${input("Title_Name", "제목")}${input("Project_Name", "공사명(| 줄바꿈)")}${input("Order_name", "발주처")}${input("Esti_Date", "견적일자")}${input("Level", "설계내역 레벨표시(0~9)", "number")}${input("F_PageCount", "첫 페이지 줄수", "number")}${input("L_PageCount", "나머지 페이지 줄수", "number")}${check("Bigo_Show", "비고란 보기")}${check("Company_Show", "공급자 정보 인쇄")}

공급자

${input("Reg_Num", "등록번호")}${input("ComPany_Name", "상호")}${input("Ceo_Name", "대표자")}${input("Addr", "사업장주소")}${input("Uptae", "업태")}${input("Jongmog", "종목")}${input("Tel", "전화,(FAX)")}
`, + `
${input("Title_Name", "제목")}${input("Project_Name", "공사명(| 줄바꿈)")}${input("Order_name", "발주처")}${input("Esti_Date", "견적일자")}${input("Level", "설계내역 레벨표시(0~9)", "number")}${input("F_PageCount", "첫 페이지 줄수", "number")}${input("L_PageCount", "나머지 페이지 줄수", "number")}${check("Bigo_Show", "비고란 보기")}${check("Company_Show", "공급자 정보 인쇄")}

공급자

${input("Reg_Num", "등록번호")}${input("ComPany_Name", "상호")}${input("Ceo_Name", "대표자")}${input("Addr", "사업장주소")}${input("Uptae", "업태")}${input("Jongmog", "종목")}${input("Tel", "전화,(FAX)")}
`, ); } function read(state) { @@ -198,7 +198,83 @@ const EstEstimatePrint = (() => { return frame; } + // 엑셀변환(TFRMEXCELCONVERTESTIPRN btnConvert): 견적서 머리(제목·공사명·귀하·견적일자·합계금액·공급자)와 내역 줄을 .xlsx 로. + // 원본의 인쇄비율·셀 수식(TRUNC·합) 은 웹 Excel 내보내기가 값만 쓰므로 하지 않는다. + async function excel(state) { + const s = read(state), + list = rows(state.project, num(s.Level)).filter((r) => r.Name || r.Code), + sum = total(state.project); + const columns = [ + "품명", + "규격", + "단위", + "수량", + "재료단가", + "재료금액", + "노무단가", + "노무금액", + "경비단가", + "경비금액", + "합계단가", + "합계금액", + "비고", + ]; + const v = (x) => (typeof x === "number" ? x : Number(x) || 0); + const body = list.map((r) => [ + String(r.Name ?? ""), + String(r.Size ?? ""), + String(r.Unite ?? ""), + v(r.GN), + v(r.GU), + v(r.GC), + v(r.NU), + v(r.NC), + v(r.JU), + v(r.JC), + v(r.TU), + v(r.TC), + s.Bigo_Show ? String(r.Bigo ?? "") : "", + ]); + const head = [ + s.Title_Name, + `공사명 : ${String(s.Project_Name).replaceAll("|", " ")}`, + `${s.Order_name} 귀하`, + s.Esti_Date, + "아래와 같이 견적합니다", + `합계금액${totalText(sum)}`, + s.Company_Show + ? `공급자 등록번호 ${s.Reg_Num} · 상호 ${s.ComPany_Name} · 성명 ${s.Ceo_Name} · 사업장주소 ${s.Addr} · 업태 ${s.Uptae} · 종목 ${s.Jongmog} · 전화 ${s.Tel}` + : "", + ] + .filter(Boolean) + .join(String.fromCharCode(10)); + const response = await fetch("/api/export-report-xlsx", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: `${state.project.name}_견적서`, + columns, + rows: body, + detail: head, + errors: [], + }), + }); + if (!response.ok) throw Error((await response.json()).error); + EstApp.download( + `${state.project.name || "견적서"}_견적서.xlsx`, + await response.blob(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ); + } + function handle(name, state) { + if (name === "estimate-print-excel") { + excel(state).catch((error) => { + state.notice = error.message; + EstApp.render(); + }); + return true; + } if (name === "estimate-print") open(state); else if (name === "estimate-print-preview") preview(state); else if (name === "estimate-print-print") {