feat(est-test): 견적서 엑셀변환
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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) — 「삽입」: 현재 산출서 앞에 공종명칭·규격을 물어 새 산출서, 「선택공종 삭제」: 고른 여러 산출서를 한 번에 지움.
|
||||
|
||||
@@ -171,7 +171,7 @@ const EstEstimatePrint = (() => {
|
||||
`<label><input name="ep-${k}" type="checkbox" ${s[k] ? "checked" : ""}>${label}</label>`;
|
||||
EstApp.showDialog(
|
||||
"견적서 작성",
|
||||
`<div class="form-fields">${input("Title_Name", "제목")}${input("Project_Name", "공사명(| 줄바꿈)")}${input("Order_name", "발주처")}${input("Esti_Date", "견적일자")}<label>견적일자 선택<input type="date" data-date-target="ep-Esti_Date" data-date-format="estimate"></label>${input("Level", "설계내역 레벨표시(0~9)", "number")}${input("F_PageCount", "첫 페이지 줄수", "number")}${input("L_PageCount", "나머지 페이지 줄수", "number")}<label>품명·규격 정렬<select name="ep-Aligment"><option value="0" ${num(s.Aligment) ? "" : "selected"}>왼쪽</option><option value="1" ${num(s.Aligment) ? "selected" : ""}>가운데</option></select></label>${check("Bigo_Show", "비고란 보기")}${check("Company_Show", "공급자 정보 인쇄")}</div><h4>공급자</h4><div class="form-fields">${input("Reg_Num", "등록번호")}${input("ComPany_Name", "상호")}${input("Ceo_Name", "대표자")}${input("Addr", "사업장주소")}${input("Uptae", "업태")}${input("Jongmog", "종목")}${input("Tel", "전화,(FAX)")}</div><button data-action="estimate-print-preview">미리보기</button> <button data-action="estimate-print-print">인쇄</button> <button data-action="estimate-print-save">공급자정보 저장하기(.einf)</button> <button data-action="estimate-print-load">공급자정보 불러오기</button><iframe id="estimate-print-frame" style="width:100%;height:420px;border:1px solid #ccc;margin-top:8px"></iframe>`,
|
||||
`<div class="form-fields">${input("Title_Name", "제목")}${input("Project_Name", "공사명(| 줄바꿈)")}${input("Order_name", "발주처")}${input("Esti_Date", "견적일자")}<label>견적일자 선택<input type="date" data-date-target="ep-Esti_Date" data-date-format="estimate"></label>${input("Level", "설계내역 레벨표시(0~9)", "number")}${input("F_PageCount", "첫 페이지 줄수", "number")}${input("L_PageCount", "나머지 페이지 줄수", "number")}<label>품명·규격 정렬<select name="ep-Aligment"><option value="0" ${num(s.Aligment) ? "" : "selected"}>왼쪽</option><option value="1" ${num(s.Aligment) ? "selected" : ""}>가운데</option></select></label>${check("Bigo_Show", "비고란 보기")}${check("Company_Show", "공급자 정보 인쇄")}</div><h4>공급자</h4><div class="form-fields">${input("Reg_Num", "등록번호")}${input("ComPany_Name", "상호")}${input("Ceo_Name", "대표자")}${input("Addr", "사업장주소")}${input("Uptae", "업태")}${input("Jongmog", "종목")}${input("Tel", "전화,(FAX)")}</div><button data-action="estimate-print-preview">미리보기</button> <button data-action="estimate-print-print">인쇄</button> <button data-action="estimate-print-excel">엑셀변환</button> <button data-action="estimate-print-save">공급자정보 저장하기(.einf)</button> <button data-action="estimate-print-load">공급자정보 불러오기</button><iframe id="estimate-print-frame" style="width:100%;height:420px;border:1px solid #ccc;margin-top:8px"></iframe>`,
|
||||
);
|
||||
}
|
||||
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") {
|
||||
|
||||
Reference in New Issue
Block a user