// 원본 수량 목록(TFRMMULLANGLIST — spec_pa §2·3)·수량붙여넣기(연결)(TFRMEST ActionMullangInput §8.2)· // 품명·규격 지정(TFRMMULNAMEINPUT btnSetName·btnSetSize §4)·공종명칭 입력(TFRMMULTITLENAMEINPUT §5). // 수량 목록: 왼쪽 모든 산출서(검색 = 명칭·규격에서 빈칸·|·괄호 뺀 부분일치, 전체보기) · 가운데 고른 산출서 수량 항목 · 오른쪽 원문. // 선택수량복사: 고른 수량 항목을 앱 안에 담아 둠(클립보드 아님). 내역서 「수량붙여넣기(연결)」: 고른 줄부터 아래로 차례로 // 수량을 넣고 그 항목을 내역 줄에 연결(「품명,규격입력」 체크면 품명·규격·단위도). 행은 더하지 않음. const EstQuantityList = (() => { const norm = (s) => String(s ?? "").replace(/[\s|()]/g, ""); const RESULT_NAMES = { name: ["\x0e", "품명"], size: ["\x13", "규격"] }; function open(state) { const l = (state.qList ||= { word: "", code: null, nameSize: false, picked: new Set() }); const { esc, number } = EstApp, sheets = (state.project.quantitySheets || []).filter( (s) => !l.word || norm(s.Name).includes(l.word) || norm(s.Size).includes(l.word), ), sheet = state.project.quantitySheets?.find((s) => s.Code === l.code) || sheets[0]; l.code = sheet?.Code; const items = sheet ? EstQuantity.calculate(sheet).items : []; const preview = sheet ? sheet.rows .map((r) => r.table ? `${r.table.map((tr) => `${tr.map((c) => ``).join("")}`).join("")}
${esc(c.text)}
` : `
${esc(EstText.clean(r.LineData || ""))}
`, ) .join("") : ""; EstApp.showDialog( "수량 목록", `
${sheets.map((s) => `
${esc(s.Name)} ${esc(s.Size || "")}
`).join("")}
${items.map((i, k) => ``).join("")}
명칭규격단위수량
${esc(i.Name)}${esc(i.Size)}${esc(i.Unite)}${number(i.GN)}
${preview}
`, ); } function paste(state) { const clip = state.mulClip; if (!clip?.items.length) throw Error("수량 목록에서 선택수량복사를 먼저 하세요."); if (state.view !== "EstMainDat") throw Error("내역서에서 붙여넣을 줄을 고르세요."); const rows = state.project.tables.EstMainDat, start = [...state.selection].sort((a, b) => a - b)[0]; if (!Number.isInteger(start)) throw Error("내역서에서 붙여넣을 줄을 고르세요."); EstApp.change((project) => { const sheet = project.quantitySheets.find((s) => s.Code === clip.code); clip.items.forEach((src, k) => { const row = project.tables.EstMainDat[Math.min(start + k, rows.length - 1)], item = sheet?.items.find((i) => i.Code === src.Code); if (clip.nameSize) Object.assign(row, { Name: src.Name, Size: src.Size, Unite: src.Unite }); row.GN = Number(src.GN) || 0; row.MullangIndex = 9999; if (item && row.PkID != null) Object.assign(item, { TargetKey: `id:${row.PkID}`, TargetCode: row.Code || "" }); }); }); state.notice = `수량 ${clip.items.length}개를 붙여넣고 연결했습니다.`; } // 품명·규격 지정: 산출 줄 글에서 고른 글자를 ⟦품명⟧·⟦규격⟧ 표지로(한 줄에 하나, 40자 이하, 제목 표지 위 금지) function nameSize(state, kind, index) { const sheet = state.project.quantitySheets?.find((s) => s.Code === state.quantitySheet), editor = document.querySelector(`[data-quantity-line="${index}"]`); if (!sheet?.rows[index] || !editor) throw Error("산출 줄을 고르세요."); const [mark, label] = RESULT_NAMES[kind], text = editor.value; let a = editor.selectionStart ?? 0, b = editor.selectionEnd ?? a; if (a === b) { while (a > 0 && /[^\s⟦⟧]/.test(text[a - 1])) a--; while (b < text.length && /[^\s⟦⟧]/.test(text[b])) b++; } const picked = text.slice(a, b); if (EstText.encode(text).includes(mark)) throw Error(`한라인에 ${label}을두번이상 지정할수 없습니다.`); if (sheet.rows[index].title) throw Error("제목표시에는 지정할수 없습니다."); if (picked.length > 40) throw Error("선택한 범위가 너무 큽니다."); if (!picked.trim()) throw Error( `${label}으로 지정할 문자를 드래그하여 선택하던지커서를 선택할위치로 옮겨 놓으세요.`, ); const token = `⟦${label}⟧`; EstApp.change(() => { sheet.rows[index].LineData = EstText.encode( text.slice(0, a) + token + picked + token + text.slice(b), ); sheet.markerEdited = true; }, false); } // 공종명칭 입력: 목록 명칭·규격을 바꾸고 문서 머리에 「공백5 + 명칭(규격) + 공백5」 제목 줄(굵게 큰 글자)을 넣거나 바꿈 function title(state) { const sheet = state.project.quantitySheets?.find((s) => s.Code === state.quantitySheet); if (!sheet) throw Error("수량산출서를 고르세요."); const head = sheet.rows[0]?.title ? sheet.rows[0] : null; const name = prompt("명칭", sheet.Name ?? ""); if (name == null) return; const size = prompt("규격", sheet.Size ?? "") ?? ""; EstApp.change(() => { sheet.Name = name.trim(); sheet.Size = size.trim(); const text = ` ${name.trim()}${size.trim() ? `(${size.trim()})` : ""} `; if (head) head.LineData = text; else sheet.rows.unshift({ LineData: text, markers: [], title: true, para: { align: "center" } }); sheet.tableEdited = true; }, false); } function install() { document.addEventListener("click", (event) => { const a = event.target.closest("[data-ql-sheet]"); if (!a) return; event.preventDefault(); const l = EstApp.state.qList; l.code = a.dataset.qlSheet; l.picked = new Set(); open(EstApp.state); }); document.addEventListener("change", (event) => { const t = event.target, l = EstApp.state.qList; if (!l) return; if (t.dataset.qlItem !== undefined) { const k = Number(t.dataset.qlItem); if (t.checked) l.picked.add(k); else l.picked.delete(k); } else if (t.dataset.qlNamesize !== undefined) l.nameSize = t.checked; else if (t.dataset.qlWord !== undefined) { l.word = norm(t.value); open(EstApp.state); } }); } if (typeof document !== "undefined" && document.addEventListener) install(); function handle(name, state, node) { if (name === "quantity-list") open(state); else if (name === "ql-search") { state.qList.word = norm(document.querySelector("[data-ql-word]")?.value); open(state); } else if (name === "ql-all") { state.qList.word = ""; open(state); } else if (name === "ql-copy") { const l = state.qList, sheet = state.project.quantitySheets.find((s) => s.Code === l.code), items = EstQuantity.calculate(sheet).items.filter((_, k) => l.picked.has(k)); if (!items.length) throw Error("복사할 수량 항목을 고르세요."); state.mulClip = { code: sheet.Code, items, nameSize: l.nameSize }; state.notice = `수량 ${items.length}개를 담았습니다. 내역서에서 수량붙여넣기(연결)를 누르세요.`; EstApp.render(); } else if (name === "mul-paste") paste(state); else if (name === "quantity-name" || name === "quantity-size") nameSize(state, name === "quantity-name" ? "name" : "size", Number(node.dataset.index)); else if (name === "quantity-title") title(state); else return false; return true; } return { handle }; })();