// 원본 통합 검색(TFRMMULTISERCH) 화면 — 저장된 다른 작업에서 단가·호표를 찾아, 고른 자료를 하위 연결 자료와 함께 // 현재 공사로 가져온다(EstTransfer). 같은 코드는 현재 공사 자료를 그대로 둔다. 가져온 뒤 단가 고르기로 행에 넣는다. const EstMultiSearch = (() => { const kinds = [ [103, "일위대가"], [104, "단가산출"], [105, "기계경비"], [101, "견적단가"], [21, "실적단가"], [5, "자재단가"], [6, "노임단가"], ]; function form(state) { const { esc } = EstApp, saved = state.multiSearch || { query: "", kinds: kinds.map(([k]) => k), hits: [], total: 0 }; state.multiSearch = saved; const rows = saved.hits .map( (h, i) => `${[103, 104, 105].includes(h.kind) ? `` : ""}${esc(h.project)}${esc(h.label)}${esc(h.Code)}${esc(h.Name)}${esc(h.Size)}${esc(h.Unite)}${EstApp.number(h.price)}`, ) .join(""); EstApp.showDialog( "통합 검색", `

검색위치: · 검색어에 「10000~20000」처럼 쓰면 단가 범위

${kinds.map(([k, label]) => ``).join("")}
${rows}
작업구분코드품명규격단위단가

찾은 자료 ${saved.total}건${saved.total > saved.hits.length ? ` 중 앞 ${saved.hits.length}건` : ""}

`, ); } async function run(name, state, node) { if (name === "multi-search-detail") return detail(state, Number(node.dataset.i)); if (name === "multi-search-pick") (state.multiSearch.picked ||= new Set()).add(Number(node.dataset.i)); if (name === "multi-search-pick" || name === "multi-search-back") return form(state); if (name === "main-list") return mainForm(state); if (name === "main-list-save") { await mainSave(); return form(state); } if (name === "multi-search") form(state); else if (name === "multi-search-run") { const saved = state.multiSearch; saved.query = document.querySelector('#dialog-content [name="ms-query"]').value; saved.kinds = [...document.querySelectorAll('#dialog-content [name="ms-kind"]:checked')].map( (n) => Number(n.value), ); saved.where = document.querySelector('#dialog-content [name="ms-where"]:checked')?.value; const params = new URLSearchParams({ q: saved.query, kinds: saved.kinds.join(",") }); if (saved.where === "main") { const ids = await mainList(); if (!ids.length) throw Error("설계참고철에 등록한 작업이 없습니다. 설계참고철 관리에서 추가하세요."); params.set("only", ids.join(",")); } if (state.project.id) params.set("exclude", state.project.id); Object.assign(saved, await EstApp.api(`/api/search?${params}`)); saved.picked = new Set(); form(state); } else if (name === "multi-search-take") { const picked = [...document.querySelectorAll('#dialog-content [name="ms-hit"]:checked')].map( (n) => state.multiSearch.hits[Number(n.value)], ); if (!picked.length) throw Error("가져올 자료를 고르세요."); const projects = new Map(); for (const hit of picked) if (!projects.has(hit.id)) projects.set(hit.id, await EstApp.api(`/api/projects/${hit.id}`)); const deps = picked.map((hit) => EstTransfer.collect(projects.get(hit.id), [{ LinkIndex: hit.kind, Code: hit.Code }]), ); let added = 0, kept = 0; EstApp.change((project) => { for (const dep of deps) { const result = EstTransfer.merge(project, dep); added += result.added.length; kept += result.kept.length; } }); EstApp.byId("dialog").close(); state.notice = `자료 ${added}건을 가져왔습니다${kept ? `, 같은 코드 ${kept}건은 이 공사 자료를 그대로 씀` : ""}. 단가 고르기로 행에 넣으세요.`; EstApp.render(); } } // 설계참고철(TfrmMainTableList 「MAIN DATA 추가 및 삭제」): 저장된 작업 중 최대 10개 — 양식 보관함(종류 options, 이름 「설계참고철」) const MAIN = "설계참고철"; async function mainList() { return ( (await EstApp.api("/api/forms")).find((f) => f.kind === "options" && f.name === MAIN)?.data ?.ids || [] ); } async function mainForm(state) { const ids = await mainList(), projects = await EstApp.api("/api/projects"), { esc } = EstApp; EstApp.showDialog( "MAIN DATA(설계참고철) 추가 및 삭제", `

통합 검색 「설계참고철」에서 찾을 작업을 고르세요(최대 10개).

${projects.map((p) => ``).join("")}
`, ); } async function mainSave() { const ids = [...document.querySelectorAll('#dialog-content [name="ml-id"]:checked')].map( (n) => n.value, ); if (ids.length > 10) throw Error("Main Data는 10개 이상 추가할수없습니다."); await EstApp.api("/api/forms", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ kind: "options", name: MAIN, data: { ids } }), }); } // 근거자료보기(TfrmFoundDetail): 찾은 일위·단산·기계 호표의 세부 줄과 총액·노무·재료·경비, 「적용」 = 그 결과를 고름 const BODY = { 103: "HeadMainDat", 104: "CostMainDat", 105: "MachMainDat" }; async function detail(state, i) { remember(state); const hit = state.multiSearch.hits[i], project = await EstApp.api(`/api/projects/${hit.id}`), { esc, number } = EstApp, rows = project.tables[BODY[hit.kind]] || [], group = EstNative.groups(rows).get(hit.Code); if (!group) throw Error("근거 자료를 찾지 못했습니다."); const head = group.rows[0], body = group.rows .slice(1) .map((r) => hit.kind === 104 ? `${esc(EstText.clean(r.LineData || ""))}` : `${esc(r.Code)}${esc(r.Name)}${esc(r.Size)}${esc(r.Unite)}${number(r.GN)}`, ) .join(""); EstApp.showDialog( `[${hit.Name}]의 근거자료보기`, `

총 액 : ${number(head.TC)} · 노무비 : ${number(head.NC)} · 재료비 : ${number(head.GC)} · 경 비 : ${number(head.JC)}

${body}
`, ); } function remember(state) { const boxes = document.querySelectorAll('#dialog-content [name="ms-hit"]'); if (boxes.length) state.multiSearch.picked = new Set( [...boxes].filter((n) => n.checked).map((n) => Number(n.value)), ); } const names = new Set([ "multi-search", "multi-search-run", "multi-search-take", "main-list", "main-list-save", "multi-search-detail", "multi-search-pick", "multi-search-back", ]); function handle(name, state, node) { if (!names.has(name)) return false; run(name, state, node).catch((error) => { state.notice = error.message; EstApp.render(); }); return true; } return { handle }; })();