/**
* STMATE w 8.0 — 코드 관리 창 끼워 넣기 (scr_ui_code.js) · 코드변경 · 항목교체 · 코드삭제 · +α · 신규등록
* 근거: DFM 원문 — uM_ChgCode(현재코드 · 바뀔코드 · * 사용 · 코드변환실행 · 코드일괄변환 · 현재이후 자동입력 · 바뀔코드 초기화)
* · uM_ReplCode(대상코드 · 항목교체실행 · 동일항목(명칭, 규격... 단가) 대상코드 자동입력) · uM_DelCode(선 택 · 선택코드
* 삭제실행 · 코드일괄삭제(중기비제외/포함) · 사용안된 "천원" "인" 일위대가 전체선택 · 동일항목 자동선택)
* · uM_Chg_Plus(종류별 현재최종 + α) · wM_Boxa Act_CopyNew(신규등록) · Act_DEL_CODE · Act_CH_CODE · Act_RP_CODE
* 계산 = ui_code.js · 저장 = app.js postSave(/api/ui/save · api_ui_code.py)
*/
(function (global) {
"use strict";
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
screens: {},
actions: {},
menus: [],
});
const UC = global.STMATE_UI_CODE;
const esc = (s) =>
String(s == null ? "" : s).replace(
/[&<>"]/g,
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c],
);
const $ = (id) => document.getElementById(id);
const KINDS = ["L", "M", "S", "B", "X", "D", "W"];
const env = (ctx) => ({
d: ctx.dbfData,
state: ctx.state,
memo: ctx.uiState.editedSmtMap || (ctx.uiState.editedSmtMap = {}),
});
const need = (ctx) => (ctx.dbfData ? true : (alert("열린 프로젝트가 없음"), false));
const done = (ctx, api, msg) => {
ctx.uiState.danMsg = msg;
api.closeModal();
api.cascadeAndRender();
};
const fail = (res) => alert((res.errors || [res.error || "실패"]).slice(0, 12).join("\n"));
const TITLE = {
change: "코드변경 [uM_ChgCode] — 현재코드 → 바뀔코드",
replace: "항목교체(내역포함) [uM_ReplCode] — 현재코드 → 대상코드",
delete: "코드삭제 [uM_DelCode]",
};
function st(ctx, mode) {
const u = ctx.uiState;
if (!u.codeMgr || u.codeMgr.mode !== mode)
u.codeMgr = { mode, kind: "L", map: {}, sel: {}, kw: "" };
return u.codeMgr;
}
function priceOf(r, k) {
const f = { L: "LAMT1", M: "MAMT1", S: "SAMT1" }[k] || "TAMT1";
return Number(r[f]) || 0;
}
function mgrHtml(ctx, api) {
const s = ctx.uiState.codeMgr,
e = env(ctx),
used = UC.usedSet(e);
const rows = (ctx.dbfData.costn_rows || []).filter((r) => {
const c = UC.codeOf(r);
if (c[0] !== s.kind) return false;
const kw = String(s.kw || "")
.trim()
.toLowerCase();
return (
!kw || `${c} ${r.NAME || ""} ${r.SIZE || ""} ${r.BQCODE || ""}`.toLowerCase().includes(kw)
);
});
const cnt = (k) => (ctx.dbfData.costn_rows || []).filter((r) => UC.codeOf(r)[0] === k).length;
const tabs = KINDS.map(
(k) =>
``,
).join("");
const col = s.mode === "delete" ? "선 택" : s.mode === "replace" ? "대상코드" : "바뀔코드";
const body = rows
.map((r) => {
const c = UC.codeOf(r);
const cell =
s.mode === "delete"
? ``
: ` `;
return `
| ${c} | ${cell} | ${esc(r.NAME)} | ${esc(r.SIZE)} | ${esc(r.UNIT)} | ${esc(r.BQCODE || "")} | ${esc(r.EQCODE || "")} | ${api.fmt(priceOf(r, s.kind))} | ${used.has(c) ? "*" : ""} |
`;
})
.join("");
const btn = (id, t) => ``;
const acts = {
change: [
btn("cm-run-sel", "코드변환실행(선택)"),
btn("cm-run-all", "코드일괄변환"),
btn("cm-auto", "현재이후 자동입력"),
btn("cm-clear", "바뀔코드 초기화"),
btn("cm-plus", "현재코드 + α"),
],
replace: [
btn("cm-run-all", "항목교체실행"),
btn("cm-same", "동일항목 대상코드 자동입력"),
btn("cm-clear", "대상코드 초기화"),
],
delete: [
btn("cm-del", "선택코드 삭제실행"),
btn("cm-del-x", "코드일괄삭제 (중기비제외)"),
btn("cm-del-t", "코드일괄삭제 (중기비포함)"),
btn("cm-1000", '사용안된 "천원" "인" 일위대가 선택'),
btn("cm-same", "동일항목 자동선택"),
btn("cm-clear", "선택코드 초기화"),
],
}[s.mode];
return ``;
}
function openMgr(ctx, api, mode) {
if (!need(ctx)) return;
const s = st(ctx, mode);
api.openModal(TITLE[mode], mgrHtml(ctx, api));
const re = () => openMgr(ctx, api, mode);
document
.querySelectorAll(".cm-kind")
.forEach((b) => (b.onclick = () => ((s.kind = b.getAttribute("data-k")), re())));
$("cm-kw").onchange = (e) => ((s.kw = e.target.value), re());
document
.querySelectorAll(".cm-map")
.forEach(
(i) =>
(i.onchange = () => (s.map[i.getAttribute("data-c")] = i.value.trim().toUpperCase())),
);
document
.querySelectorAll(".cm-sel")
.forEach((i) => (i.onchange = () => (s.sel[i.getAttribute("data-c")] = i.checked)));
const on = (id, fn) => $(id) && ($(id).onclick = fn);
const pick = (onlySel) =>
Object.fromEntries(Object.entries(s.map).filter(([c, v]) => v && (!onlySel || s.sel[c])));
const run = (onlySel) => {
const m = pick(onlySel);
const res = mode === "replace" ? UC.replaceCodes(env(ctx), m) : UC.changeCodes(env(ctx), m);
if (!res.ok) return fail(res);
s.map = {};
s.sel = {};
done(
ctx,
api,
`${mode === "replace" ? "항목교체" : "코드변경"} ${res.count}건 · 내역 ${res.refs.boq} · 구성 ${res.refs.comp} · 식 ${res.refs.memo}곳`,
);
};
on("cm-run-sel", () => run(true));
on("cm-run-all", () => run(false));
on("cm-clear", () => {
const k = s.kind;
Object.keys(s.map).forEach((c) => c[0] === k && delete s.map[c]);
Object.keys(s.sel).forEach((c) => c[0] === k && delete s.sel[c]);
re();
});
on("cm-auto", () => {
const list = (ctx.dbfData.costn_rows || []).map(UC.codeOf).filter((c) => c[0] === s.kind);
const from = list.find((c) => s.sel[c]) || list[0];
const start = prompt(`${from} 부터 차례로 넣을 첫 바뀔코드`, from);
if (!start || !UC.CODE_RE.test(start.trim().toUpperCase())) return;
let n = UC.codeNum(start.trim().toUpperCase());
list.slice(list.indexOf(from)).forEach((c) => (s.map[c] = UC.mkCode(s.kind, n++)));
re();
});
on("cm-plus", () => openPlus(ctx, api));
on("cm-same", () => {
const m = UC.autoReplaceMap(ctx.dbfData.costn_rows, s.kind);
if (mode === "delete") Object.keys(m).forEach((c) => (s.sel[c] = true));
else Object.assign(s.map, m);
if (!Object.keys(m).length) alert("동일항목 없음");
re();
});
on(
"cm-1000",
() => (UC.unusedBUnit(env(ctx)).forEach((c) => (s.sel[c] = true)), (s.kind = "B"), re()),
);
const del = (codes, what) => {
if (!codes.length) return alert(`${what} 없음`);
if (
!confirm(
`${what} ${codes.length}건을 지움\n${codes.slice(0, 20).join(", ")}${codes.length > 20 ? " …" : ""}`,
)
)
return;
const res = UC.deleteCodes(env(ctx), codes);
if (!res.ok) return fail(res);
s.sel = {};
done(
ctx,
api,
`코드삭제 ${res.count}건${res.kept.length ? ` · 쓰이는 코드 ${res.kept.length}건은 남김` : ""}`,
);
};
on("cm-del", () =>
del(
Object.keys(s.sel).filter((c) => s.sel[c]),
"선택코드",
),
);
on("cm-del-x", () => del(UC.unusedCodes(env(ctx), false), "안 쓰이는 코드(중기비제외)"));
on("cm-del-t", () => del(UC.unusedCodes(env(ctx), true), "안 쓰이는 코드(중기비포함)"));
}
// 코드번호 변경(현재코드 + α)
function openPlus(ctx, api) {
if (!need(ctx)) return;
const last = UC.lastCodes(ctx.dbfData.costn_rows);
const kinds = ["L", "M", "S", "B", "X", "D", "W"];
api.openModal(
"코드번호 변경 ( 현재코드 + α ) [uM_Chg_Plus]",
``,
);
$("cp-no").onclick = api.closeModal;
$("cp-ok").onclick = () => {
const alpha = Object.fromEntries(kinds.map((k) => [k, Number($(`cp-${k}`).value) || 0]));
const res = UC.changeCodes(env(ctx), UC.plusMap(ctx.dbfData.costn_rows, alpha));
if (!res.ok) return fail(res);
done(ctx, api, `코드번호 + α ${res.count}건`);
};
}
// ── 단가목록표 한 코드 동작(신규등록 · 현재코드 삭제 · 코드번호 변경 · 항목교체) ──
function copyNew(ctx, api, code) {
if (!need(ctx)) return;
const kind = code
? code[0]
: (prompt("새 코드 종류 글자(L M S B X D W)", "M") || "").trim().toUpperCase();
if (!kind) return;
const def = UC.nextCode(ctx.dbfData.costn_rows, kind);
const nc = (prompt(code ? `${code} 를 복사한 새 코드` : "새 코드", def) || "")
.trim()
.toUpperCase();
if (!nc) return;
const fields = code
? {}
: {
NAME: prompt("명칭", "") || "",
SIZE: prompt("규격", "") || "",
UNIT: prompt("단위", "") || "",
};
const res = UC.addCode(env(ctx), { from: code || null, kind, code: nc, fields });
if (!res.ok) return fail(res);
const s = ctx.uiState.dan || (ctx.uiState.dan = {});
Object.assign(s, { code: res.code, kind: "ALL", kw: res.code });
ctx.uiState.danMsg = `${res.code} 신규등록${code ? ` (${code} 복사)` : ""}`;
api.cascadeAndRender();
}
function delOne(ctx, api, code) {
if (!code || !confirm(`${code} 삭제?`)) return;
const res = UC.deleteCodes(env(ctx), [code]);
if (!res.ok) return fail(res);
ctx.uiState.dan.code = "";
ctx.uiState.danMsg = `${code} 삭제`;
api.cascadeAndRender();
}
function chgOne(ctx, api, code, replace) {
if (!code) return;
const to = (
prompt(replace ? `${code} 를 쓰는 자리를 바꿀 대상코드` : `${code} → 바뀔코드`, "") || ""
)
.trim()
.toUpperCase();
if (!to) return;
const res = replace
? UC.replaceCodes(env(ctx), { [code]: to })
: UC.changeCodes(env(ctx), { [code]: to });
if (!res.ok) return fail(res);
ctx.uiState.dan.code = replace ? code : to;
ctx.uiState.danMsg = `${code} → ${to} ${replace ? "항목교체" : "코드변경"} · 내역 ${res.refs.boq} · 구성 ${res.refs.comp} · 식 ${res.refs.memo}곳`;
api.cascadeAndRender();
}
Object.assign(P.actions, {
Act_Chg_Code_Sel: (ctx, api) => openMgr(ctx, api, "change"),
Act_Chg_Code_All: (ctx, api) => openMgr(ctx, api, "change"),
Act_Chg_Plus: (ctx, api) => openPlus(ctx, api),
Act_Del_Code_Sel: (ctx, api) => openMgr(ctx, api, "delete"),
Act_Del_Code_All_Minus: (ctx, api) => (
openMgr(ctx, api, "delete"),
$("cm-del-x") && $("cm-del-x").click()
),
Act_Del_Code_All_Plus: (ctx, api) => (
openMgr(ctx, api, "delete"),
$("cm-del-t") && $("cm-del-t").click()
),
Act_Chg_Item: (ctx, api) => openMgr(ctx, api, "replace"),
});
global.STMATE_UI_CODE_SCR = { openMgr, openPlus, copyNew, delOne, chgOne, env };
})(window);