// 원본 갑지(TFRMGABGI)·표지(TFRMCOVER)·공사설명서/시방서(TFRMESTEDITOR) 웹 편집 — project.docs. // 공사명은 열 때 공사 이름을 따르고 고치면 공사 이름에도 적는다(원본 TCommonOption.ProjectName 동기화). // 공사금액 자동입력: 원가계산서에서 고른 비목을 두 방식으로 적는다(원본 c_cb2e78·c_cb3080, 한글금액 c_931b44). // 1 = 「도급액 : 금 일천오백만원(₩15,000,000)」 · 2 = 「도급액 : 금15,000,000원(금일천오백만원)」, 「일」을 줄이지 않는다. // 설명서·시방서 붙여넣기는 갑지의 공사명·공사위치·공사개요·공사금액을 커서 자리에 넣는다. 서식·여백은 다루지 않는다. const EstDocs = (() => { const digits = ["", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구"]; const places = ["천", "백", "십", ""]; const groups = ["", "만", "억", "조"]; function korean(value) { const n = Math.round(Number(value) || 0); if (!n) return "영"; const text = String(Math.abs(n)), padded = text.padStart(Math.ceil(text.length / 4) * 4, " "), count = padded.length / 4; let out = ""; for (let g = 0; g < count; g++) { const part = padded.slice(g * 4, g * 4 + 4); let chunk = ""; [...part].forEach((ch, i) => { const d = Number(ch); if (ch !== " " && d) chunk += digits[d] + places[i]; }); if (chunk) out += chunk + groups[count - 1 - g]; } return out; } const comma = (value) => Math.round(Number(value) || 0).toLocaleString("en-US"); function costLine(name, cost, method) { return method === 2 ? `${name} : 금${comma(cost)}원(금${korean(cost)}원)` : `${name} : 금 ${korean(cost)}원(₩${comma(cost)})`; } function costText(project, indices, method) { const rows = project.tables.Wonga || []; const picked = indices.map((i) => rows[i]).filter(Boolean); if (!picked.length) throw Error("공사금액에 넣을 원가 비목을 고르세요."); const width = Math.max(...picked.map((r) => String(r.Name ?? "").trim().length)); return picked .map((r) => costLine( String(r.Name ?? "") .trim() .padEnd(width, " "), r.Cost, method, ), ) .join("\n"); } const defaults = () => ({ gabgi: { Name: "", Position: "", CompanyName: "", Explan: "", Cost: "", Gulga: ["과 장", "계 장", "심사자", "설계자"], Date: "", YearShow: true, DayShow: false, CompanyPos: 1, }, cover: { Name: "", Name2: "", CompanyName: "", Date: "", YearShow: true, CompanyPos: 1 }, explan: { text: "" }, sibang: { text: "" }, }); function doc(project, kind) { project.docs ||= {}; const value = { ...defaults()[kind], ...(project.docs[kind] || {}) }; if ((kind === "gabgi" || kind === "cover") && project.name) value.Name = project.name; return value; } function year(value) { const text = String(value ?? "") .split("년")[0] .trim(); return `${text || new Date().getFullYear()}년도`; } // 인쇄 미리보기(서식 8 갑지 · 9 표지 · 10 시방서 · 11 공사설명서) function html(form, project) { const esc = (v) => String(v ?? "").replace( /[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c], ); const align = (pos) => ["left", "center", "right"][Number(pos)] || "center"; if (form === 9) { const c = doc(project, "cover"); return `
${c.YearShow ? `

${esc(year(c.Date))}

` : ""}

${esc(c.Name)}

${c.Name2 ? `

${esc(c.Name2)}

` : ""}

${esc(c.CompanyName)}

`; } if (form === 8) { const g = doc(project, "gabgi"); const gulga = g.Gulga.length ? `${g.Gulga.map((n) => ``).join("")}${g.Gulga.map(() => "").join("")}
${esc(n)}
` : ""; return `
${gulga}

${esc(g.Date)}

${g.Position ? `

공사위치 : ${esc(g.Position)}

` : ""}

${esc(g.Name)}

${g.Explan ? `

공사개요 :

${esc(g.Explan)}
` : ""}${g.Cost ? `

공사금액 :

${esc(g.Cost)}
` : ""}

${esc(g.CompanyName)}

`; } if (form === 10 || form === 11) { const text = doc(project, form === 10 ? "sibang" : "explan").text; return text ? `
${esc(text)}
` : ""; } return ""; } // 화면 const field = (name) => document.querySelector(`#dialog-content [name="${name}"]`); function editor(state, kind) { const { esc } = EstApp, d = doc(state.project, kind); const input = (name, label, value) => ``; const area = (name, label, value, rows = 4) => ``; const pos = (value) => ``; let body = ""; if (kind === "cover") body = input("Name", "공사명", d.Name) + input("Name2", "부제", d.Name2) + input("Date", "설계년도", d.Date) + input("CompanyName", "발주처", d.CompanyName) + pos(d.CompanyPos); else if (kind === "gabgi") { const rows = (state.project.tables.Wonga || []) .map((r, i) => [r, i]) .filter(([r]) => Number(r.Cost) && String(r.Name ?? "").trim()); const fixed = rows.find(([r]) => Number(r.LinkIndex) === 55)?.[1]; body = input("Name", "공사명", d.Name) + input("Position", "공사위치", d.Position) + input("CompanyName", "발주처", d.CompanyName) + input("Date", "설계일자", d.Date) + pos(d.CompanyPos) + area("Explan", "공사개요", d.Explan) + area("Cost", "공사금액", d.Cost, 3) + `

공사금액 자동입력:

` + area("Gulga", "결재란(한 줄에 하나)", d.Gulga.join("\n"), 4); } else body = area("text", kind === "explan" ? "공사설명서" : "시방서", d.text, 14) + `

갑지에서 붙여넣기: ${[ ["Name", "공사명"], ["Position", "공사위치"], ["Explan", "공사개요"], ["Cost", "공사금액"], ] .map(([k, l]) => ``) .join(" ")}

`; const title = { gabgi: "갑지", cover: "표지", explan: "공사설명서", sibang: "시방서" }[kind]; EstApp.showDialog( `${title} 편집`, `
${body}
`, ); } function collect(kind) { const value = (name) => field(`doc-${name}`)?.value ?? ""; if (kind === "cover") return { Name: value("Name"), Name2: value("Name2"), Date: value("Date"), CompanyName: value("CompanyName"), CompanyPos: Number(value("CompanyPos")), }; if (kind === "gabgi") return { Name: value("Name"), Position: value("Position"), CompanyName: value("CompanyName"), Date: value("Date"), CompanyPos: Number(value("CompanyPos")), Explan: value("Explan"), Cost: value("Cost"), Gulga: value("Gulga") .split(/\r?\n/) .map((l) => l.trim()) .filter(Boolean), }; return { text: value("text") }; } function handle(name, state, node) { if (name === "doc-edit") editor(state, node.dataset.kind); else if (name === "doc-save") { const kind = node.dataset.kind, next = collect(kind); EstApp.byId("dialog").close(); EstApp.change((project) => { project.docs ||= {}; project.docs[kind] = { ...doc(project, kind), ...next }; if (next.Name && next.Name !== project.name) project.name = next.Name; }, false); state.notice = "문서를 저장했습니다."; } else if (name === "doc-cost") { const indices = [...field("doc-rows").selectedOptions].map((o) => Number(o.value)); field("doc-Cost").value = costText(state.project, indices, Number(field("doc-method").value)); } else if (name === "doc-paste") { const key = node.dataset.field, g = doc(state.project, "gabgi"), text = String(g[key] ?? "").trim(); if (!text) throw Error("갑지에서 입력을 하지 않았습니다."); const area = field("doc-text"), at = area.selectionStart ?? area.value.length; area.value = area.value.slice(0, at) + text + area.value.slice(area.selectionEnd ?? at); } else return false; return true; } return { korean, costLine, costText, html, doc, handle }; })();