// 원본 TFRMSERCHPROJECT(검색어·여러개선택·동시검색·적용)와 TFRMDBLCLICKRTN(데이타 변경 확인)을 옮긴 단가 고르기. // 내역서·일위대가 행에 기초자료를 골라 넣거나 선택 행의 연결을 바꾼다. 단가산출 산출근거(LineData)는 대상이 아니다. const EstPick = (() => { const kinds = [ [5, "StuffcDat", "자재"], [6, "LaborcDat", "노임"], [105, "MachTitleDat", "기계경비"], [103, "HeadTitleDat", "일위대가"], [104, "CostTitleDat", "단가산출"], [101, "EstiMateDat", "견적"], [21, "SilJukDat", "실적"], ]; const views = ["EstMainDat", "HeadMainDat"]; const limit = 300; const text = (value) => String(value ?? "") .normalize("NFKC") .toLowerCase() .replace(/\s+/g, ""); const amount = (row) => { const total = Number(row.TC) || 0; return total || ["NC", "GC", "JC", "SC"].reduce((sum, key) => sum + (Number(row[key]) || 0), 0); }; function items(project, kind) { const list = []; for (const [k, table, label] of kinds) { if (kind && k !== kind) continue; for (const row of project.tables[table] || []) if (row.Code && Number(row.Index ?? 1) >= 0) list.push({ kind: k, label, Code: row.Code, Name: row.Name ?? "", Size: row.Size ?? "", Unite: row.Unite ?? "", price: amount(row), }); } return list; } function search(project, kind, query) { const words = String(query || "") .split(/\s+/) .map(text) .filter(Boolean); const found = items(project, kind).filter((item) => { const haystack = text(`${item.Code} ${item.Name} ${item.Size}`); return words.every((word) => haystack.includes(word)); }); return { rows: found.slice(0, limit), total: found.length }; } function guard(view, group, picks) { if (!views.includes(view)) throw Error("내역서·일위대가 화면에서만 단가를 골라 넣을 수 있습니다."); if (!picks.length) throw Error("넣을 자료를 하나 이상 고르세요."); if (view === "HeadMainDat" && picks.some((p) => p.kind === 103 && p.Code === group)) throw Error("일위대가 자신을 하위 자료로 넣을 수 없습니다."); } function linked(base, pick) { return { ...base, Code: pick.Code, Name: pick.Name, Size: pick.Size, Unite: pick.Unite, LinkIndex: pick.kind, }; } function insert(project, view, at, group, picks) { guard(view, group, picks); const rows = project.tables[view]; const added = []; for (const pick of picks) { const row = linked(EstModel.newRow(view, rows, project), pick); rows.splice(at + 1 + added.length, 0, row); added.push(at + 1 + added.length); } try { refresh(project, view, added); } catch (error) { rows.splice(at + 1, added.length); throw error; } return added; } function refresh(project, view, indices) { // 내역·일위대가 행은 요청한 행만 원천 단가를 다시 읽는다(행 비목 지정과 같은 경로). let failures = null; const done = EstNative.calculate( EstModel.validate(structuredClone(project)), view === "HeadMainDat" ? { refreshHeadRows: indices, onHeadRefresh: (errors) => (failures = errors) } : { refreshEstimateRows: indices, onEstimateRefresh: (errors) => (failures = errors) }, ); if (!failures) throw Error("고른 자료의 단가를 다시 읽지 못했습니다."); if (failures.length) throw Error(failures.join(" / ")); for (const index of indices) for (const key of EstMath.units) project.tables[view][index][key] = done.tables[view][index][key]; } function replaceable(target) { if ( !target || Number(target.Index) > 0 || [54, 55, 57, 58, 61, 62, 67, 68].includes(Number(target.LinkIndex)) ) throw Error("일반 내역 행 하나를 선택하세요. 제목·요율·순공사비 행은 바꿀 수 없습니다."); } function replace(project, view, index, group, pick, all) { guard(view, group, [pick]); const rows = project.tables[view], target = rows[index]; replaceable(target); const old = { kind: Number(target.LinkIndex) || 0, Code: target.Code }; const changed = [], backup = structuredClone(rows); rows.forEach((row, i) => { const same = i === index || (all && old.Code && row.Code === old.Code && Number(row.LinkIndex) === old.kind && Number(row.Index ?? 0) <= 0); if (!same) return; Object.assign(row, linked({}, pick)); changed.push(i); }); try { refresh(project, view, changed); } catch (error) { project.tables[view] = backup; throw error; } return changed; } function cell(value) { return EstApp.esc(value); } function results(state) { const pick = state.pick, { rows, total } = search(state.project, pick.kind, pick.query); pick.rows = rows; const body = rows .map( (item, i) => `
| 구분 | 코드 | 품명 | 규격 | 단위 | 단가 |
|---|
찾은 자료 ${total}건${total > limit ? ` 중 앞 ${limit}건 표시 — 검색어를 더 넣으세요` : ""}
`; } function open(state) { if (!views.includes(state.view)) throw Error("내역서·일위대가 화면에서만 단가를 골라 넣을 수 있습니다."); state.pick ||= { kind: 0, query: "" }; const options = [[0, "전체 동시검색"], ...kinds.map(([k, , label]) => [k, label])] .map( ([k, label]) => ``, ) .join(""); EstApp.showDialog( "단가 고르기", `