/** * STMATE w 8.0 — 설계내역서 편집 화면(wBoq) 끼워 넣기 (scr_ui_boq.js) * 근거: ui_form_catalog.txt wBoq(cBox01 「= 이상」~「====== 이상」 · RGrpTo 내역서/열린 보관함 · cBox02) * 편집 계산 = ui_boq.js · 도구 대화상자 = scr_ui_boq_dlg.js · 금액 = view_prj_boq.evaluateFullBoqRows * 행 추가·삭제·이동·복사·계표시는 dbfData.boq_rows(저장 원천)와 state.boqItems(계산 원천)를 함께 고침 */ (function (global) { "use strict"; const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || { screens: {}, actions: {}, menus: [], }); const B = global.STMATE_UI_BOQ; const LV = ["= 이상", "== 이상", "=== 이상", "==== 이상", "===== 이상", "====== 이상"]; const esc = (s) => String(s == null ? "" : s).replace( /[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c], ); // 편집 모델 — 지운 원본 행 번호는 dbfData.boq_deleted(저장 묶음 boq_deleted 로 나감) function model(ctx) { const d = ctx.dbfData; d.boq_deleted = d.boq_deleted || []; return { rows: d.boq_rows, items: ctx.state.boqItems, deleted: d.boq_deleted }; } const costnByCode = (ctx) => Object.fromEntries( ((ctx.dbfData && ctx.dbfData.costn_rows) || []).map((r) => [String(r.CODE || "").trim(), r]), ); const sel = (ctx) => (ctx.uiState.boqSel || []).map(Number); const selIdx = (ctx) => B.indexesOf(ctx.dbfData.boq_rows, sel(ctx)); // 편집 뒤 — 결과 알림 · 내역 표 묶음 맞춤 · 다시 그림 function after(ctx, api, res, keepSel) { if (res && res.ok === false) alert(res.error); const d = ctx.dbfData; d.boq_tables = d.boq_tables || {}; d.boq_tables[d.boq_table_name] = d.boq_rows; d.boq_count = d.boq_rows.length; if (!keepSel) ctx.uiState.boqSel = sel(ctx).filter((r) => d.boq_rows.some((x) => Number(x._recno) === r)); api.renderWorkspace(); return res; } const insertAt = (ctx) => { const ix = selIdx(ctx); return ix.length ? ix[0] : ctx.dbfData.boq_rows.length; }; // 화면 동작 표 — 메뉴줄·단추·단축키가 함께 씀 const ACTS = { insBlank: (ctx, api) => { const m = model(ctx); const row = B.makeRow(m, {}); const res = B.insertRows(m, insertAt(ctx), [row]); ctx.uiState.boqSel = [row._recno]; return after(ctx, api, res, true); }, del: (ctx, api) => { if (!sel(ctx).length) return alert("지울 행을 고르세요"); if (!confirm(`선택한 ${sel(ctx).length}행을 지움(계표시는 짝도 함께) — 계속?`)) return; return after(ctx, api, B.deleteRows(model(ctx), sel(ctx))); }, up: (ctx, api) => after(ctx, api, B.moveRows(model(ctx), sel(ctx), -1), true), down: (ctx, api) => after(ctx, api, B.moveRows(model(ctx), sel(ctx), 1), true), copy: (ctx) => { if (!sel(ctx).length) return alert("복사할 행을 고르세요"); ctx.uiState.boqClip = B.copyRows(model(ctx), sel(ctx)); ctx.uiState.boqMsg = `복사 ${ctx.uiState.boqClip.rows.length}행(계표시는 짝 구간 전체)`; global.STMATE_APP.renderWorkspace(); }, cut: (ctx, api) => { if (!sel(ctx).length) return alert("잘라낼 행을 고르세요"); ctx.uiState.boqClip = B.copyRows(model(ctx), sel(ctx)); const all = ctx.uiState.boqClip.rows.length; const res = B.deleteRows(model(ctx), sel(ctx)); ctx.uiState.boqMsg = `잘라내기 ${all}행`; return after(ctx, api, res); }, paste: (ctx, api) => after(ctx, api, B.pasteRows(model(ctx), insertAt(ctx), ctx.uiState.boqClip)), lvUp: (ctx, api) => shift(ctx, api, 1), lvDn: (ctx, api) => shift(ctx, api, -1), note2grp: (ctx, api) => { const r = sel(ctx)[0]; const n = Number( prompt("계시작 레벨(1~9) — 1 소계 · 2 계 · 3 합계 · 4 총계 · 5 총합계 · 6 총대계", "2"), ); if (!(n >= 1 && n <= 9)) return; return after(ctx, api, B.noteToGroup(model(ctx), r, n, ctx.dbfData.start_ini)); }, find: (ctx, api) => { const kw = String(ctx.uiState.boqFind || "") .trim() .toLowerCase(); if (!kw) return alert("찾을 글자를 넣으세요"); const rows = ctx.dbfData.boq_rows; const from = (selIdx(ctx)[0] ?? -1) + 1; const hay = (r) => `${r.ITNUM || ""} ${r.BDCODE1 || ""} ${r.NAME1 || ""} ${r.SIZE1 || ""}`.toLowerCase(); let k = rows.findIndex((r, i) => i >= from && hay(r).includes(kw)); if (k < 0) k = rows.findIndex((r) => hay(r).includes(kw)); if (k < 0) return alert(`「${kw}」 없음`); Object.assign(ctx.uiState, { boqSel: [Number(rows[k]._recno)], boqLevelMin: 0, boqScrollTo: rows[k]._recno, }); api.renderWorkspace(); }, pocketPut: (ctx, api) => { if (!sel(ctx).length) return alert("보관함에 담을 행을 고르세요"); const u = ctx.uiState, n = u.activePocket || 1; u.boqPockets = u.boqPockets || { 1: [], 2: [], 3: [] }; const clip = B.copyRows(model(ctx), sel(ctx)); clip.title = clip.rows.map((r) => r.NAME1 || r.BDCODE1).filter(Boolean)[0] || "(빈 행)"; u.boqPockets[n].push(clip); api.renderWorkspace(); }, // 호표 코드 → 단가목록표(기초단가·견적) · 대가표(일위대가·산근·중기) jump: (ctx, api) => { const r = ctx.dbfData.boq_rows.find((x) => Number(x._recno) === sel(ctx)[0]); const c = r && B.isLeaf(r) ? B.codeOf(r) : ""; if (!c) return alert("단가코드 행을 고르세요"); if (/^[MLSW]/.test(c)) { ctx.uiState.dan = Object.assign(ctx.uiState.dan || {}, { kind: "ALL", kw: c, mode: "name", code: c, }); ctx.uiState.activeScreen = "wM_JabPum"; } else Object.assign(ctx.uiState, { selectedIlwiCode: c, activeScreen: "wM_Edit_iLWi" }); api.renderWorkspace(); }, qtyList: (ctx, api) => { if (P.screens.wQtyList) return ((ctx.uiState.activeScreen = "wQtyList"), api.renderWorkspace()); api.openMappedModal("Act_QtyModal"); }, }; function shift(ctx, api, d) { const r = sel(ctx)[0]; if (r == null) return alert("계표시(머리·계) 행을 고르세요"); return after(ctx, api, B.shiftLevel(model(ctx), r, d, ctx.dbfData.start_ini), true); } const MENUS = [ [ "편집", [ ["insBlank", "행 삽입 (Ins)"], ["kongjong", "공종목록에서 코드 넣기"], ["del", "행 삭제 (Del)"], ["jump", "코드의 단가·대가표로 이동"], null, ["copy", "복사 (Ctrl+C)"], ["cut", "잘라내기 (Ctrl+X)"], ["paste", "붙여넣기 (Ctrl+V)"], null, ["up", "위로 이동 (Alt+↑)"], ["down", "아래로 이동 (Alt+↓)"], ], ], [ "계표시", [ ["wrap", "계표시 삽입(선택 행 묶음)"], ["lvUp", "계표시 레벨 상향"], ["lvDn", "계표시 레벨 하향"], ["note2grp", "주석 → 계시작 변환"], ], ], [ "도구", [ ["itnum", "공종번호 자동입력 (wITNum)"], ["boqRate", "할증율 적용 (wBoqRate)"], ["mRate", "단수 일괄보정 (wM_Rate)"], ["expnd", "일위대가표 풀기 (wExpnd)"], null, ["mboq", "주공종 집계표 (wMBoq)"], ["qtyList", "수량산출서 (wQtyList)"], ], ], ["보관함", [["pocketPut", "선택 행 보관함에 담기"]]], ]; function menuBar() { return `
`; } const money = (fmt, v) => (Number(v) ? fmt(v) : ""); function rowHtml(r, ctx, api, selSet) { const k = B.rowKind(r), bd = B.codeOf(r), rec = r._recno; const cls = [ r._rowClass || "", selSet.has(Number(rec)) ? "boq-sel" : "", r._new ? "boq-new" : "", ].join(" "); const inp = (f, v, extra) => ``; const leaf = k.kind === "LEAF"; const free = leaf || k.kind === "NOTE" || k.kind === "BLANK"; const hal = [r.RM, r.RL, r.RS].map(Number); const halTxt = Number(r.RATE) ? `할증 ${Number(r.RATE)}%` : hal.some(Boolean) ? `할증 재${hal[0]}·노${hal[1]}·경${hal[2]}` : ""; const { fmt } = api; return `| 담은 내용 | 행 | |
|---|---|---|
| ${esc(p.title || "")} | ${p.rows.length} |
보관함 (${n}) 비어 있음 — 행을 고르고 [보관함 › 선택 행 보관함에 담기]
`; return `열린 프로젝트 없음 — [프로젝트 관리]에서 여세요.
| No | ${cap("CAP_ITNUM", "공종")} | ${cap("CAP_CODE", "코드번호")} | ${cap("CAP_NAME", "명칭")} | ${cap("CAP_SIZE", "규격")} | ${cap("CAP_QTY", "수량")} | ${cap("CAP_SUNIT", "단위")} | ${["BMK_STR1", "BMK_STR2", "BMK_STR3", "BMK_STR4"].map((k, i) => `${cap(k, ["재료비", "노무비", "경비", "합계"][i])} | `).join("")}${cap("CAP_BIGO", "비고")} | |
|---|---|---|---|---|---|---|---|---|---|
| ${cap("CAP_AMT", "단가")} | ${cap("CAP_AMOUNT", "금액")} | `) .join("")}||||||||