- 코드 등록·삭제·변경·병합·데이타병합·일괄변경·검색·동일코드·풀어넣기 끝전 등(ui_code · ui_merge · api_ui_code) - 코드 붙은 % 줄 = 같은 상위 공종 앞 말단 줄 비목 합 × 율(server pct_row_base · calc.js) · 저장 때 다시 셈(stc_save _pct_rows) — 실무 7건 저장값 그대로 - 내역 줄 단가 ≠ 단가목록이면 줄 고정 단가(fixedUnit) - 메뉴 94개 눌러 오류 0 · 시험 502 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
296 lines
13 KiB
JavaScript
296 lines
13 KiB
JavaScript
/**
|
||
* 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) =>
|
||
`<button class="win-btn cm-kind${s.kind === k ? " primary" : ""}" data-k="${k}">${UC.KIND_NAME[k]}(${cnt(k)})</button>`,
|
||
).join("");
|
||
const col = s.mode === "delete" ? "선 택" : s.mode === "replace" ? "대상코드" : "바뀔코드";
|
||
const body = rows
|
||
.map((r) => {
|
||
const c = UC.codeOf(r);
|
||
const cell =
|
||
s.mode === "delete"
|
||
? `<input type="checkbox" class="cm-sel" data-c="${c}" ${s.sel[c] ? "checked" : ""} />`
|
||
: `<input type="checkbox" class="cm-sel" data-c="${c}" ${s.sel[c] ? "checked" : ""} /> <input class="cell-input cm-map" data-c="${c}" value="${esc(s.map[c] || "")}" style="width:70px;" />`;
|
||
return `<tr><td class="center"><strong>${c}</strong></td><td class="center">${cell}</td><td>${esc(r.NAME)}</td><td>${esc(r.SIZE)}</td><td class="center">${esc(r.UNIT)}</td><td class="center">${esc(r.BQCODE || "")}</td><td class="center">${esc(r.EQCODE || "")}</td><td class="num">${api.fmt(priceOf(r, s.kind))}</td><td class="center">${used.has(c) ? "*" : ""}</td></tr>`;
|
||
})
|
||
.join("");
|
||
const btn = (id, t) => `<button class="win-btn" id="${id}">${t}</button>`;
|
||
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 `<div class="stm-form"><div class="stm-row">${tabs}</div>
|
||
<div class="stm-row"><input id="cm-kw" class="win-input" value="${esc(s.kw)}" placeholder="코드,명칭,분류번호" style="width:180px;" />${acts.join("")}</div>
|
||
<div class="grid-scroll" style="max-height:52vh;"><table class="dbgrid"><thead><tr><th>현재코드</th><th>${col}</th><th>명칭</th><th>규격</th><th>단위</th><th>분류번호</th><th>시설코드</th><th>단가</th><th>*</th></tr></thead><tbody>${body}</tbody></table></div>
|
||
<p class="stm-note">* = 내역서·구성표·산출근거 식에 쓰임 · 쓰이는 코드는 지우지 않음 · 바꾼 뒤 [계산 완료] 저장 때 작업본에 씀</p></div>`;
|
||
}
|
||
|
||
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]",
|
||
`<div class="stm-form">${kinds
|
||
.map(
|
||
(k) =>
|
||
`<div class="stm-row"><label style="width:260px;">${UC.KIND_NAME[k]} ( 현재최종 : ${last[k] || "-"} ) +</label><input id="cp-${k}" type="number" class="win-input" value="0" style="width:90px;" /></div>`,
|
||
)
|
||
.join(
|
||
"",
|
||
)}<div class="stm-row"><button class="win-btn primary" id="cp-ok">코드번호 변경(O)</button><button class="win-btn" id="cp-no">취 소(C)</button></div></div>`,
|
||
);
|
||
$("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);
|