// 원본 견적서작성(mnuEstiMatePrn → TfrmEstiMatePrn 0x10c1994, 그리기 0x10c4768). // 설정은 공사마다 project.estimatePrint(원본 EstiPrintInfo.einf 세 구역), 공급자정보는 원본과 같은 INI(.einf)로 내보내기·불러오기 // (불러오기는 EstiPrnCompanyInfo·EstiPrnInfo 두 구역만). 행: 레벨 0 = 전 행, N = Level ≥ N 공종 머리 + LinkIndex 54·55·58. // 열: 품명(Pr+Name)·규격·수량·단위·단가·공급가액·비고(선택). 1쪽만 머리·공급자·합계금액, 1쪽 F_PageCount·이후 L_PageCount 줄. // 합계금액 = 원가계산서를 아래에서 위로 훑어 Checked 이고 금액 있는 첫 행(없으면 도급액 55 행) — 「일금: …원정 (₩…원정)」. const EstEstimatePrint = (() => { const num = (value) => Number(value) || 0; const defaults = { Title_Name: "견 적 서", Project_Name: "", Order_name: "", Esti_Date: "", Reg_Num: "", ComPany_Name: "", Ceo_Name: "", Addr: "", Uptae: "", Jongmog: "", Tel: "", Company_Show: true, F_PageCount: 27, L_PageCount: 36, Aligment: 0, Bigo_Show: false, Level: 0, }; const sections = { EstiProjectInfo: ["Title_Name", "Project_Name", "Order_name", "Esti_Date"], EstiPrnCompanyInfo: [ "Reg_Num", "ComPany_Name", "Ceo_Name", "Addr", "Uptae", "Jongmog", "Tel", "Company_Show", ], EstiPrnInfo: ["F_PageCount", "L_PageCount", "Aligment", "Bigo_Show"], }; function settings(project) { const today = new Date(); return { ...defaults, Project_Name: project.name || "", Esti_Date: `${today.getFullYear()}년 ${String(today.getMonth() + 1).padStart(2, "0")}월 ${String(today.getDate()).padStart(2, "0")}일`, ...(project.estimatePrint || {}), }; } function rows(project, level) { const list = project.tables.EstMainDat || []; return level > 0 ? list.filter((r) => num(r.Level) >= level || [54, 55, 58].includes(num(r.LinkIndex))) : list; } function total(project) { const wonga = project.tables.Wonga || []; for (let i = wonga.length - 1; i >= 0; i--) if (wonga[i].Checked === true && num(wonga[i].Cost)) return num(wonga[i].Cost); return num(wonga.find((r) => num(r.LinkIndex) === 55)?.Cost ?? project.calculation?.total); } function totalText(value) { return ` 일금:${EstDocs.korean(value)}원정 (₩${Math.round(value).toLocaleString("en-US")}원정)`; } function pages(list, first, rest) { if (!(first > 0 && rest > 0)) throw Error("쪽당 줄 수는 1 이상입니다."); const out = [list.slice(0, first)]; for (let at = first; at < list.length; at += rest) out.push(list.slice(at, at + rest)); return out; } // 원본 TIniFile 형식(.einf) function toIni(s, keys = Object.keys(sections)) { return keys .map( (section) => `[${section}]\r\n${sections[section].map((key) => `${key}=${typeof s[key] === "boolean" ? (s[key] ? "1" : "0") : (s[key] ?? "")}`).join("\r\n")}`, ) .join("\r\n\r\n"); } function fromIni(text, keys = ["EstiPrnCompanyInfo", "EstiPrnInfo"]) { const out = {}; let section = ""; for (const line of String(text).split(/\r?\n/)) { const head = line.match(/^\s*\[([^\]]+)\]\s*$/); if (head) { section = head[1]; continue; } const pair = line.match(/^\s*([^=]+?)\s*=(.*)$/); if (!pair || !keys.includes(section) || !sections[section].includes(pair[1])) continue; const key = pair[1], value = pair[2].trim(), base = defaults[key]; out[key] = typeof base === "boolean" ? ["1", "true", "-1"].includes(value.toLowerCase()) : typeof base === "number" ? Number(value) || 0 : value; } return out; } function html(project, s) { const esc = (v) => String(v ?? "").replace( /[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c], ); const fmt = (v) => (num(v) ? num(v).toLocaleString("ko-KR", { maximumFractionDigits: 4 }) : ""); const lines = (text) => esc(text).split("|").join("
"); const align = num(s.Aligment) ? "center" : "left"; const head = [ "품 명", "규 격", "수 량", "단위", "단 가", "공급가액", ...(s.Bigo_Show ? ["비 고"] : []), ]; const table = (list) => `${head.map((h) => ``).join("")}${list .map((r) => { const bold = num(r.Level) > 0 || num(r.LinkIndex) === 54 ? ' style="font-weight:bold"' : ""; return `${s.Bigo_Show ? `` : ""}`; }) .join("")}
${h}
${esc(`${r.Pr ?? ""}${r.Name ?? ""}`)}${esc(r.Size)}${fmt(r.GN)}${esc(r.Unite)}${fmt(r.TU)}${fmt(r.TC)}${esc(typeof r.Bigo === "string" ? r.Bigo : "")}
`; const company = s.Company_Show ? `${[ ["상 호", s.ComPany_Name], ["성 명", s.Ceo_Name], ["사업장주소", s.Addr], ["업 태", s.Uptae], ["종 목", s.Jongmog], ["전화(FAX)", s.Tel], ] .map(([k, v]) => ``) .join("")}
공
급
자
등록번호${esc(s.Reg_Num)}
${k}${esc(v)}
` : ""; const all = pages(rows(project, num(s.Level)), num(s.F_PageCount), num(s.L_PageCount)); const sum = total(project); const body = all .map( (list, i) => `
${ i === 0 ? `

${esc(s.Title_Name)}

공사명: ${lines(s.Project_Name)}

${lines(s.Order_name)} 귀하

${esc(s.Esti_Date)}

아래와 같이 견적합니다

${company}

합계금액 ${esc(totalText(sum))}

` : "" }${table(list)}
`, ) .join(""); return `${body}`; } // 화면 const field = (name) => document.querySelector(`#dialog-content [name="ep-${name}"]`); function open(state) { const s = settings(state.project), { esc } = EstApp; const input = (k, label, type = "text") => ``; const check = (k, label) => ``; 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)")}
`, ); } function read(state) { const s = settings(state.project); for (const key of Object.keys(defaults)) { const node = field(key); if (!node) continue; s[key] = node.type === "checkbox" ? node.checked : typeof defaults[key] === "number" ? Number(node.value) || 0 : node.value; } if (!(s.Level >= 0 && s.Level <= 9)) throw Error("레벨은 0~9 입니다."); if (JSON.stringify(s) !== JSON.stringify(settings(state.project))) EstApp.change((project) => (project.estimatePrint = s), false); return s; } function preview(state) { const s = read(state), frame = EstApp.byId("estimate-print-frame"); frame.srcdoc = html(state.project, s); return frame; } function handle(name, state) { if (name === "estimate-print") open(state); else if (name === "estimate-print-preview") preview(state); else if (name === "estimate-print-print") { const frame = preview(state); frame.onload = () => frame.contentWindow.print(); } else if (name === "estimate-print-save") EstApp.download("견적서 기본정보.einf", toIni(read(state)), "text/plain"); else if (name === "estimate-print-load") { const picker = document.createElement("input"); picker.type = "file"; picker.accept = ".einf,.ini,.txt"; picker.addEventListener("change", async () => { const file = picker.files[0]; if (!file) return; const bytes = new Uint8Array(await file.arrayBuffer()); let text; try { text = new TextDecoder("utf-8", { fatal: true }).decode(bytes); } catch { text = new TextDecoder("euc-kr").decode(bytes); } const next = { ...read(state), ...fromIni(text) }; EstApp.change((project) => (project.estimatePrint = next), false); open(state); }); picker.click(); } else return false; return true; } return { settings, rows, total, totalText, pages, toIni, fromIni, html, handle }; })();