/** * STMATE w 8.0 — 단가목록표 화면(wM_JabPum) 끼워 넣기 (scr_ui_dan.js) * 근거: ui_form_catalog.txt wM_JabPum(cBoxCode 10종 · cBox_IFORM 명칭,규격/산근내용) · wM_Boxa(RG_MComp) · wM_Edt_Hal(wcBx_Up) * 후보 5칸 = COSTN AMT_11~51(페이지 PG_11~51) · 채택 = PG_1 · 가격지 이름 = 초기값 JMOK1~5/JMK10~50 * 기초단가(M·L·S) 고침 = COSTN 자기 비목 칸 + 계산 원천 자원(manualPrice) → 연쇄 재계산 */ (function (global) { "use strict"; const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || { screens: {}, actions: {}, menus: [], }); const U = global.STMATE_UI_DAN; const esc = (s) => String(s == null ? "" : s).replace( /[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c], ); const $ = (id) => document.getElementById(id); const prefs = () => (global.STMATE_UI_APP_MENU && global.STMATE_UI_APP_MENU.prefs) || {}; function st(ctx) { const u = ctx.uiState; u.dan = u.dan || { kind: u.danFilter && u.danFilter !== "ALL" ? u.danFilter : "ALL", kw: "", mode: "name", sort: "", code: "", }; return u.dan; } // 자원(계산 원천) 맞춤 — 기초단가 한 건 function syncRes(ctx, code, price, how) { const res = (ctx.state.resources || []).find((r) => r.code === code && !r.isSynthetic); if (res) Object.assign(res, { adoptMode: how || "manual", manualPrice: price }); } function render(ctx, api) { if (!ctx.dbfData) return `
단가목록표 [wM_JabPum]

열린 프로젝트 없음

`; const s = st(ctx), rows = ctx.dbfData.costn_rows || []; const ini = ctx.dbfData.start_ini || {}; const slots = U.slotNames(ini); const sort = s.sort || prefs().dflIndex || "C"; let list = U.filterRows(rows, { kind: s.kind, kw: s.kw, mode: s.mode, sort, memos: ctx.dbfData.costn_memos || {}, }); const UA = global.STMATE_UI_CODE_AMT; if (UA && s.pp) list = list.filter((r) => UA.ppMatch(r, s.pp)); // 속성 추려보기(rg_FLT_*_PP) if (s.nm) list = list.filter((r) => String(r.NAME || "").trim() === s.nm); // 명칭 추려보기 if (UA && s.called) { const called = UA.calledCodes(ctx.dbfData); // 불러간 목록(wM_Box_H) list = list.filter((r) => called.has(U.codeOf(r))); } const cnt = U.counts(rows); const n2 = prefs().useName2; const nm = (r, k) => (n2 && String(r[`${k}2`] || "").trim() ? r[`${k}2`] : r[k]) || ""; const slotName = (n) => { const x = slots.list.find((q) => q.slot === n); return x ? x.short || x.name : ""; }; const { fmt } = api; const body = list .map((r, i) => { const c = U.codeOf(r), base = U.isBase(c), cand = U.candidates(r), pg = U.pages(r), ad = U.adoptedSlot(r); const priceCell = (k) => base && k[0] === c[0] ? `` : `${U.num(r[k]) ? fmt(r[k]) : ""}`; return ` ${i + 1}${c} ${esc(nm(r, "NAME"))}${esc(nm(r, "SIZE"))}${esc(r.UNIT)} ${priceCell("MAMT1")}${priceCell("LAMT1")}${priceCell("SAMT1")} ${fmt(U.appliedPrice(r))} ${slots.list .map(({ slot: n }) => { const v = cand[n - 1]; const on = ad === n ? "background:#fde68a;font-weight:700;" : ""; const click = base && v > 0 ? ` class="num dan-adopt" data-code="${c}" data-slot="${n}" style="cursor:pointer;${on}" title="이 단가 채택"` : ` class="num" style="${on}"`; return `${v ? fmt(v) : ""}${pg[n - 1] ? `
p.${esc(pg[n - 1])}` : ""}`; }) .join("")} ${ad ? esc(slotName(ad)) : base && U.isUserEdited(r) ? "직접" : ""} ${/^[MLSW]/.test(c) ? `` : ""} ${esc(r.BQCODE || "")}${esc(r.EQCODE || "")} ${/^[BDX]/.test(c) ? `` : ""} `; }) .join(""); return `
단가목록표 [wM_JabPum · COSTN.DBF] — ${rows.length}건 중 ${list.length}건${esc(slots.applied)} = 재료비 + 노무비 + 경비
${esc(ctx.uiState.danMsg || "")}
${toolBar(ctx, s)}
${slots.list.map((x) => ``).join("")}${body}
No코드명칭규격단위재료비노무비경비${esc(slots.applied)}${esc(x.name)}채택보호분류번호시설코드
`; } // 코드 관리·단가 손질 단추줄 — scr_ui_code.js · scr_ui_dan2.js · 추려보기(속성·명칭·불러간 목록) function toolBar(ctx, s) { const UA = global.STMATE_UI_CODE_AMT; if (!UA || !global.STMATE_UI_CODE_SCR) return ""; const pp = s.kind === "B" ? UA.PP_B : s.kind === "M" ? UA.PP_M : []; const names = UA.nameList(ctx.dbfData.costn_rows, s.kind === "ALL" ? "" : s.kind, "NAME"); const b = (id, t, on) => ``; return `
${b("dan-new", "신규등록(A)")}${b("dan-del", "현재코드 삭제", Boolean(s.code))}${b("dan-chg", "코드번호 변경", Boolean(s.code))}${b("dan-rp", "현재코드 항목교체(내역포함)", Boolean(s.code))}${b("dan-jd", "호환규정코드 검색")}${b("dan-hal", "적용단가 일괄할증")}${b("dan-chgamt", "단가 일괄변경")}${b("dan-xr", "손료계수(F6)")}${b("dan-check", "성분 검사(TAMT=재+노+경)")} ${pp.length ? `` : ""}
`; } function boxa(ctx, api) { const slots = U.slotNames(ctx.dbfData.start_ini); const s = st(ctx); api.openModal( "단가목록표 가격지 비교 (wM_Boxa)", `
`, ); $("bx-ok").onclick = () => { const how = $("bx-how").value, scope = $("bx-scope").value; const rows = scope === "one" ? ctx.dbfData.costn_rows.filter((r) => U.codeOf(r) === s.code) : ctx.dbfData.costn_rows; const ch = U.adoptAll(rows, how, { kinds: scope === "one" ? "MLS" : scope, skipZero: $("bx-skip0").checked, keepUser: scope !== "one" && $("bx-keep").checked, }); ch.forEach((c) => syncRes(ctx, c.code, c.to, `slot${c.slot}`)); const kept = rows.filter(U.isProtected).length; ctx.uiState.danMsg = `${ch.length}건 채택 바꿈${kept ? ` · 보호 ${kept}건 그대로` : ""}`; api.closeModal(); api.cascadeAndRender(); }; } function edtHal(ctx, api) { const s = st(ctx); api.openModal( "기준단가 수정 (wM_Edt_Hal)", `
%

※ 단【 적용단가 】가 "0" 원인것은 수정안됨 · 기준단가 = 채택 물가지 단가(채택 없으면 지금 적용단가)

※ 보호표시(√)된 단가도 수정안됨

`, ); $("eh-ok").onclick = () => { const kind = $("eh-kind").value; const ch = U.edtHal(ctx.dbfData.costn_rows, { kinds: kind === "one" ? "MLS" : kind, codes: kind === "one" ? [s.code] : null, pct: $("eh-pct").value, digits: Number($("eh-dot").value), mode: $("eh-up").value, }); ch.forEach((c) => syncRes(ctx, c.code, c.to, "manual")); const kept = ctx.dbfData.costn_rows.filter(U.isProtected).length; ctx.uiState.danMsg = `기준단가 수정 ${ch.length}건${kept ? ` · 보호 ${kept}건 제외` : ""}`; api.closeModal(); api.cascadeAndRender(); }; } // 성분 검사(10 §3) — COSTN · 지금 내역 표 · 어긋난 칸 목록 function checkAll(ctx, api) { const CK = global.STMATE_UI_CODE_CHECK; if (!CK) return; const bad = CK.checkComponents(ctx.dbfData.costn_rows).concat( CK.checkBoq(ctx.dbfData.boq_rows), ); const { fmt } = api; api.openModal( `성분 검사 — 어긋남 ${bad.length}건`, `

TAMT = 재료비 + 노무비 + 경비 · L/M/S 코드는 자기 비목만 · 차수 칸 1~4 · 내역은 단가·금액 칸

${ bad.length ? `
${bad .map( (x) => `${["T", "M", "L", "S"].map((k) => ``).join("")}`, ) .join("")}
자리코드차수내용계재노경
${esc(x.where)}${esc(x.code)}${x.slot}${esc(x.msg)}${fmt(x.v[k])}
` : "

어긋난 칸 없음

" }
`, ); } function findUse(ctx, api) { const s = st(ctx), f = U.findUse(s.code, ctx.dbfData); const li = (a) => (a.length ? a.join("") : "
  • (없음)
  • "); api.openModal( `사용처 찾기 — ${s.code}`, `
    내역서 ${f.boq.length}곳 일위대가·중기 구성표 ${f.comp.length}곳 단가산출근거 식 ${f.memo.length}곳
    `, ); } function bind(ctx, api) { if (!ctx.dbfData) return; const s = st(ctx), rr = () => api.renderWorkspace(); $("dan-kind").onchange = (e) => ((s.kind = e.target.value), rr()); $("dan-mode").onchange = (e) => ((s.mode = e.target.value), rr()); $("dan-kw").onchange = (e) => ((s.kw = e.target.value), rr()); $("dan-sort").onchange = (e) => ((s.sort = e.target.value), rr()); $("dan-boxa").onclick = () => boxa(ctx, api); $("dan-edthal").onclick = () => edtHal(ctx, api); $("dan-use").onclick = () => findUse(ctx, api); const C = global.STMATE_UI_CODE_SCR, T2 = global.STMATE_UI_DAN2; const on = (id, fn) => $(id) && ($(id).onclick = fn); if (C) { on("dan-new", () => C.copyNew(ctx, api, s.code)); on("dan-del", () => C.delOne(ctx, api, s.code)); on("dan-chg", () => C.chgOne(ctx, api, s.code, false)); on("dan-rp", () => C.chgOne(ctx, api, s.code, true)); } if (T2) { on("dan-jd", () => T2.openJD(ctx, api)); on("dan-hal", () => T2.openHal(ctx, api)); on("dan-chgamt", () => T2.openChgAmt(ctx, api)); on("dan-xr", () => T2.openXRate(ctx, api, s.code)); } on("dan-check", () => checkAll(ctx, api)); if ($("dan-pp")) $("dan-pp").onchange = (e) => ((s.pp = e.target.value), rr()); if ($("dan-nm")) $("dan-nm").onchange = (e) => ((s.nm = e.target.value), rr()); if ($("dan-called")) $("dan-called").onchange = (e) => ((s.called = e.target.checked), rr()); if ($("dan-autohal")) $("dan-autohal").onchange = (e) => (s.autoHal = e.target.checked); document.querySelectorAll("#dan-form .dan-prot").forEach( (el) => (el.onchange = () => { const c = el.getAttribute("data-code"); const r = ctx.dbfData.costn_rows.find((x) => U.codeOf(x) === c); const hal = s.autoHal !== false && ctx.uiState.danHal; const res = U.setProtect(r, el.checked, { autoHal: hal || null }); if (res && res.hal) syncRes(ctx, c, res.hal.to, "manual"); ctx.uiState.danMsg = `${c} 보호${el.checked ? "" : " 해제"}${res && res.hal ? ` · 자동할증 ${res.hal.to}` : ""}`; api.cascadeAndRender(); }), ); const grid = $("dan-grid"); if (s.scroll) grid.scrollTop = s.scroll; grid.onscroll = () => (s.scroll = grid.scrollTop); document .querySelectorAll("#dan-form .dan-pick") .forEach((td) => (td.onclick = () => ((s.code = td.getAttribute("data-code")), rr()))); document.querySelectorAll("#dan-form .dan-price").forEach( (inp) => (inp.onchange = () => { const c = inp.getAttribute("data-code"); const r = ctx.dbfData.costn_rows.find((x) => U.codeOf(x) === c); const v = U.num(inp.value); U.writeBase(r, v); r.PG_1 = "0"; syncRes(ctx, c, v, "manual"); ctx.uiState.danMsg = `${c} 적용단가 ${v} 직접 수정`; api.cascadeAndRender(); }), ); document.querySelectorAll("#dan-form .dan-adopt").forEach( (td) => (td.onclick = () => { const c = td.getAttribute("data-code"); const r = ctx.dbfData.costn_rows.find((x) => U.codeOf(x) === c); const ch = U.adopt(r, td.getAttribute("data-slot"), { skipZero: true }); if (!ch) return; syncRes(ctx, c, ch.to, `slot${ch.slot}`); ctx.uiState.danMsg = `${c} ${ch.slot} 단가 채택`; api.cascadeAndRender(); }), ); document .querySelectorAll("#dan-form .dan-ilwi") .forEach( (b) => (b.onclick = () => ( (ctx.uiState.selectedIlwiCode = b.getAttribute("data-code")), (ctx.uiState.activeScreen = "wM_Edit_iLWi"), rr() )), ); } P.screens.wM_JabPum = { render, bind }; })(window);