Files
Aislo/tools/stmate-test/scr_ui_dan.js
T

203 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[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 `<div class="win-form"><div class="win-form-header"><span>단가목록표 [wM_JabPum]</span></div><p class="stm-note" style="padding:12px;">열린 프로젝트 없음</p></div>`;
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";
const list = U.filterRows(rows, { kind: s.kind, kw: s.kw, mode: s.mode, sort, memos: ctx.dbfData.costn_memos || {} });
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]
? `<input class="cell-input num dan-price" data-code="${c}" value="${U.num(r[k])}" />`
: `${U.num(r[k]) ? fmt(r[k]) : ""}`;
return `<tr class="${s.code === c ? "boq-sel" : ""}" data-code="${c}">
<td class="center rowhead dan-pick" data-code="${c}">${i + 1}</td><td class="center"><strong>${c}</strong></td>
<td>${esc(nm(r, "NAME"))}</td><td>${esc(nm(r, "SIZE"))}</td><td class="center">${esc(r.UNIT)}</td>
<td class="num">${priceCell("MAMT1")}</td><td class="num">${priceCell("LAMT1")}</td><td class="num">${priceCell("SAMT1")}</td>
<td class="num"><strong>${fmt(U.appliedPrice(r))}</strong></td>
${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 `<td${click}>${v ? fmt(v) : ""}${pg[n - 1] ? `<br/><small>p.${esc(pg[n - 1])}</small>` : ""}</td>`;
})
.join("")}
<td class="center">${ad ? esc(slotName(ad)) : base && U.isUserEdited(r) ? "직접" : ""}</td>
<td class="center">${esc(r.BQCODE || "")}</td><td class="center">${esc(r.EQCODE || "")}</td>
<td class="center">${/^[BDX]/.test(c) ? `<button class="win-btn dan-ilwi" data-code="${c}">대가표</button>` : ""}</td>
</tr>`;
})
.join("");
return `<div class="win-form" id="dan-form">
<div class="win-form-header"><span>단가목록표 [wM_JabPum · COSTN.DBF] — ${rows.length}건 중 ${list.length}건</span><span>${esc(slots.applied)} = 재료비 + 노무비 + 경비</span></div>
<div class="win-subbar">
<label>코드종류(cBoxCode)</label><select id="dan-kind" class="win-select">${U.KINDS.map(([k, t]) => `<option value="${k}" ${s.kind === k ? "selected" : ""}>${t} (${cnt[k]})</option>`).join("")}</select>
<select id="dan-mode" class="win-select"><option value="name">명칭,규격 으로검색</option><option value="memo" ${s.mode === "memo" ? "selected" : ""}>산근내용 으로검색</option></select>
<input id="dan-kw" class="win-input" value="${esc(s.kw)}" placeholder="찾을 글자" style="width:150px;" />
<select id="dan-sort" class="win-select">${[["C", "코드번호순"], ["N", "명 칭 순"], ["I", "분류번호순"], ["E", "시설코드순"]].map(([k, t]) => `<option value="${k}" ${sort === k ? "selected" : ""}>${t}</option>`).join("")}</select>
<button class="win-btn" id="dan-boxa">가격지 비교·채택 (wM_Boxa)</button>
<button class="win-btn" id="dan-edthal">기준단가 수정 (wM_Edt_Hal)</button>
<button class="win-btn" id="dan-use" ${s.code ? "" : "disabled"}>사용처 찾기${s.code ? ` (${esc(s.code)})` : ""}</button>
<span class="stm-note">${esc(ctx.uiState.danMsg || "")}</span>
</div>
<div class="split-vert"><div class="pane-box" style="flex:1;"><div class="grid-scroll" id="dan-grid"><table class="dbgrid"><thead><tr>
<th>No</th><th>코드</th><th>명칭</th><th>규격</th><th>단위</th><th>재료비</th><th>노무비</th><th>경비</th><th>${esc(slots.applied)}</th>
${slots.list.map((x) => `<th title="${esc(x.name)}">${esc(x.name)}</th>`).join("")}<th>채택</th><th>분류번호</th><th>시설코드</th><th></th>
</tr></thead><tbody>${body}</tbody></table></div></div></div>
</div>`;
}
function boxa(ctx, api) {
const slots = U.slotNames(ctx.dbfData.start_ini);
const s = st(ctx);
api.openModal(
"단가목록표 가격지 비교 (wM_Boxa)",
`<div class="stm-form"><div class="stm-row"><label>채택(RG_MComp)</label><select id="bx-how" class="win-select"><option value="none">변동없음</option>${slots.list.map((x) => `<option value="${x.slot}">${x.slot} 단가 — ${esc(x.name)}</option>`).join("")}<option value="min">최소단가</option></select></div>
<div class="stm-row"><label>대상</label><select id="bx-scope" class="win-select"><option value="M">재료비 전체</option><option value="MLS">기초단가 전체(재·노·경)</option>${s.code ? `<option value="one">선택 코드 ${esc(s.code)}</option>` : ""}</select></div>
<div class="stm-row"><label><input type="checkbox" id="bx-skip0" checked /> 단가가 0원인 것은 적용안함</label><label><input type="checkbox" id="bx-keep" checked /> 직접 수정한 단가 유지</label></div>
<div class="stm-row"><button class="win-btn primary" id="bx-ok">적용</button></div></div>`,
);
$("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}`));
ctx.uiState.danMsg = `${ch.length}건 채택 바꿈`;
api.closeModal();
api.cascadeAndRender();
};
}
function edtHal(ctx, api) {
const s = st(ctx);
api.openModal(
"기준단가 수정 (wM_Edt_Hal)",
`<div class="stm-form"><div class="stm-row"><label>적용단가 = 기준단가 ×</label><input id="eh-pct" type="number" class="win-input" value="100" style="width:80px;" /> %</div>
<div class="stm-row"><label>소수</label><select id="eh-dot" class="win-select">${[0, 1, 2].map((d) => `<option value="${d}">${d} 자리 미만</option>`).join("")}<option value="-1">1원 단위 미만(10원)</option></select><select id="eh-up" class="win-select"><option value="trunc">절사</option><option value="round">반올림</option></select></div>
<div class="stm-row"><label>대상</label><select id="eh-kind" class="win-select"><option value="M">재료비</option><option value="L">노무비</option><option value="S">경비</option><option value="MLS">기초단가 전체</option>${s.code ? `<option value="one">선택 코드 ${esc(s.code)}</option>` : ""}</select></div>
<p class="stm-note">※ 단【 적용단가 】가 "0" 원인것은 수정안됨 · 기준단가 = 채택 물가지 단가(채택 없으면 지금 적용단가)</p>
<p class="stm-warn">보호표시 칸은 COSTN 자료에서 확인되지 않아 걸러내지 못함</p>
<div class="stm-row"><button class="win-btn primary" id="eh-ok">수정</button></div></div>`,
);
$("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"));
ctx.uiState.danMsg = `기준단가 수정 ${ch.length}건`;
api.closeModal();
api.cascadeAndRender();
};
}
function findUse(ctx, api) {
const s = st(ctx),
f = U.findUse(s.code, ctx.dbfData);
const li = (a) => (a.length ? a.join("") : "<li>(없음)</li>");
api.openModal(
`사용처 찾기 — ${s.code}`,
`<div class="stm-form"><strong>내역서 ${f.boq.length}곳</strong><ul>${li(f.boq.map((b) => `<li>${esc(b.table)} · ${esc(b.itnum)} ${esc(b.name)} · 수량 ${b.qty}</li>`))}</ul>
<strong>일위대가·중기 구성표 ${f.comp.length}곳</strong><ul>${li(f.comp.map((c) => `<li>${esc(c.parent)} · 수량 ${c.qty}</li>`))}</ul>
<strong>단가산출근거 식 ${f.memo.length}곳</strong><ul>${li(f.memo.map((m) => `<li>${esc(m)}</li>`))}</ul></div>`,
);
}
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 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);