- 코드 등록·삭제·변경·병합·데이타병합·일괄변경·검색·동일코드·풀어넣기 끝전 등(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>
495 lines
18 KiB
JavaScript
495 lines
18 KiB
JavaScript
/**
|
||
* STMATE w 8.0 — 코드 관리 순수 계산 (ui_code.js) · 신규등록 · 삭제 · 코드변경 · +α · 항목교체 · 동일항목
|
||
* 근거: DFM 원문(C:/경영정보(주)/System/ST_CPM) — ST_U_ChgCode(uM_ChgCode 현재코드→바뀔코드 · uM_Chg_Plus 현재최종+α
|
||
* · uM_ReplCode 현재코드→대상코드 · 동일항목(명칭, 규격... 단가) 대상코드 자동입력) · ST_U_DelCode(uM_DelCode 선택 ·
|
||
* 코드일괄삭제(중기비제외/포함) · 사용안된 "천원" "인" 일위대가 · *[USE_] 사용표시) · STmate.Cpm wM_Boxa
|
||
* Act_CopyNew(선택된 코드를 복사하여 새로운코드 생성)
|
||
* env = { d: dbfData, state: sim_state, memo: uiState.editedSmtMap } — 저장 원천(d)과 계산 원천(state)을 함께 고침
|
||
* 저장 표시: d.ui_dirty · 새 행 _new(_from = 복사원 _recno) · 지운 원본 d.costn_deleted · 다른 표 코드 r._bd0
|
||
* 구성표는 원래 행 그대로 바꿔 둠 — 저장은 기본 묶음의 bdqty_rows(stc_save_extra.save_bdqty)
|
||
*/
|
||
(function (global) {
|
||
"use strict";
|
||
|
||
const CODE_RE = /^[A-Z]\d{5}$/;
|
||
const KIND_NAME = { L: "노무비", M: "재료비", S: "경비", B: "일위대가", X: "중기비" };
|
||
Object.assign(KIND_NAME, { D: "단가산출", W: "견적단가", I: "그림코드", A: "실적단가" });
|
||
const txt = (v) => String(v == null ? "" : v).trim();
|
||
const num = (v) => {
|
||
const n = parseFloat(txt(v).replace(/,/g, ""));
|
||
return Number.isFinite(n) ? n : 0;
|
||
};
|
||
const codeOf = (r) => txt(r && r.CODE);
|
||
const BDK = ["BDCODE1", "BDCODE2", "BDCODE3", "BDCODE4"];
|
||
|
||
// 내역 표 행 — 열린 표(boq_rows)와 표 묶음(boq_tables)이 같은 배열이 아닐 때도 한 번씩
|
||
function boqRowSets(d) {
|
||
const out = [];
|
||
const seen = new Set();
|
||
const add = (table, rows) => {
|
||
if (!rows || seen.has(rows)) return;
|
||
seen.add(rows);
|
||
out.push([table, rows]);
|
||
};
|
||
add(d.boq_table_name || "", d.boq_rows);
|
||
Object.entries(d.boq_tables || {}).forEach(([t, rows]) => add(t, rows));
|
||
return out;
|
||
}
|
||
|
||
const memoText = (env, c) => {
|
||
const m = env.memo || {};
|
||
if (Object.prototype.hasOwnProperty.call(m, c)) return m[c] || "";
|
||
const x = ((env.d && env.d.costn_memos) || {})[c];
|
||
return (x && x.text) || "";
|
||
};
|
||
const REF_RE = /&([A-Z]\d{5})([LMS]?(?::[LMS])?)&/g;
|
||
const memoRefs = (t) => [...String(t || "").matchAll(REF_RE)].map((m) => m[1]);
|
||
|
||
/** 사용표시(*) — 내역서(모든 표 · 1~4차 코드) · 구성표(BDQTY) · 산출근거 식 참조 */
|
||
function usedSet(env) {
|
||
const d = env.d,
|
||
used = new Set();
|
||
boqRowSets(d).forEach(([, rows]) =>
|
||
rows.forEach((r) => BDK.forEach((k) => txt(r[k]) && used.add(txt(r[k])))),
|
||
);
|
||
(d.bdqty_rows || []).forEach((r) => txt(r.CODE) && used.add(txt(r.CODE)));
|
||
(d.costn_rows || []).forEach((r) =>
|
||
memoRefs(memoText(env, codeOf(r))).forEach((c) => used.add(c)),
|
||
);
|
||
return used;
|
||
}
|
||
|
||
// 현재최종 — 종류(첫 글자)별 가장 큰 코드
|
||
function lastCodes(rows) {
|
||
const out = {};
|
||
(rows || []).forEach((r) => {
|
||
const c = codeOf(r);
|
||
if (CODE_RE.test(c) && (!out[c[0]] || c > out[c[0]])) out[c[0]] = c;
|
||
});
|
||
return out;
|
||
}
|
||
const codeNum = (c) => parseInt(String(c).slice(1), 10) || 0;
|
||
const mkCode = (k, n) => `${k}${String(n).padStart(5, "0")}`;
|
||
function nextCode(rows, kind, taken) {
|
||
const last = lastCodes(rows)[kind];
|
||
let n = last ? codeNum(last) + 1 : 1;
|
||
while (taken && taken.has(mkCode(kind, n))) n += 1;
|
||
return n > 99999 ? "" : mkCode(kind, n);
|
||
}
|
||
|
||
/** 동일항목 — 명칭+규격+단위+단가(계) 같은 종류 안 묶음 · 첫 코드(작은 번호)가 대표 */
|
||
function sameItems(rows, kinds) {
|
||
const g = new Map();
|
||
(rows || []).forEach((r) => {
|
||
const c = codeOf(r);
|
||
if (!CODE_RE.test(c) || (kinds && !kinds.includes(c[0]))) return;
|
||
const k = [c[0], txt(r.NAME), txt(r.SIZE), txt(r.UNIT), num(r.TAMT1)].join("\u0001");
|
||
g.set(k, (g.get(k) || []).concat(c));
|
||
});
|
||
return [...g.values()].filter((a) => a.length > 1).map((a) => a.slice().sort());
|
||
}
|
||
|
||
// ── 계산 원천(state) 코드 바꿈 — id·code·refId·machineId · 합성 자원 「코드_M/L/S」 ──
|
||
function stateRename(state, map) {
|
||
if (!state) return;
|
||
const sw = (v) => {
|
||
if (typeof v !== "string") return v;
|
||
if (map[v]) return map[v];
|
||
const m = v.match(/^([A-Z]\d{5})_([MLS])$/);
|
||
return m && map[m[1]] ? `${map[m[1]]}_${m[2]}` : v;
|
||
};
|
||
const fix = (o) =>
|
||
["id", "code", "refId", "machineId"].forEach((k) => k in o && (o[k] = sw(o[k])));
|
||
["resources", "machines", "sanItems", "ilwiItems", "boqItems"].forEach((k) =>
|
||
(state[k] || []).forEach((o) => {
|
||
fix(o);
|
||
(o.lines || []).forEach(fix);
|
||
(o.smtRefs || []).forEach(fix);
|
||
}),
|
||
);
|
||
}
|
||
|
||
// 참조만 바꿈(내역 · 구성표 · 식) — 항목교체·코드변경 공통
|
||
function remapRefs(env, map, opt) {
|
||
const d = env.d;
|
||
const o = Object.assign({ boq: true, comp: true, memo: true, items: true }, opt || {});
|
||
const n = { boq: 0, comp: 0, memo: 0 };
|
||
if (o.boq)
|
||
boqRowSets(d).forEach(([, rows]) =>
|
||
rows.forEach((r) =>
|
||
BDK.forEach((k) => {
|
||
const c = txt(r[k]);
|
||
if (!map[c]) return;
|
||
if (!r._bd0) r._bd0 = Object.fromEntries(BDK.map((x) => [x, r[x]]));
|
||
r[k] = map[c];
|
||
n.boq += 1;
|
||
}),
|
||
),
|
||
);
|
||
if (o.comp)
|
||
(d.bdqty_rows || []).forEach((r) => {
|
||
const c = txt(r.CODE);
|
||
if (map[c]) ((r.CODE = map[c]), (n.comp += 1));
|
||
});
|
||
if (o.memo) {
|
||
env.memo = env.memo || {};
|
||
(d.costn_rows || []).forEach((r) => {
|
||
const c = codeOf(r),
|
||
t = memoText(env, c);
|
||
if (!t) return;
|
||
const t2 = t.replace(REF_RE, (all, code, tail) =>
|
||
map[code] ? `&${map[code]}${tail}&` : all,
|
||
);
|
||
if (t2 !== t) ((env.memo[c] = t2), (n.memo += 1));
|
||
});
|
||
}
|
||
(env.state && env.state.boqItems ? env.state.boqItems : []).forEach((it) => {
|
||
if (o.boq && o.items && map[it.code])
|
||
Object.assign(it, { code: map[it.code], refId: map[it.code] });
|
||
});
|
||
d.ui_dirty = true;
|
||
return n;
|
||
}
|
||
|
||
/**
|
||
* 코드변경 검사 — 바뀔코드 = 같은 종류 글자 + 5자리 · 묶음 안 겹침 없음 · 남는 코드와 겹침 없음
|
||
* map = { 현재코드: 바뀔코드 } (빈 칸·같은 코드는 뺌)
|
||
*/
|
||
function checkChange(rows, map) {
|
||
const errs = [];
|
||
const all = new Set((rows || []).map(codeOf));
|
||
const pairs = Object.entries(map || {}).filter(([a, b]) => txt(b) && txt(a) !== txt(b));
|
||
const moving = new Set(pairs.map(([a]) => a));
|
||
const target = new Map();
|
||
pairs.forEach(([a, b]) => {
|
||
if (!all.has(a)) errs.push(`${a}: 없는 코드`);
|
||
if (!CODE_RE.test(b)) errs.push(`${a} → ${b}: 코드 꼴(글자+5자리) 아님`);
|
||
else if (b[0] !== a[0])
|
||
errs.push(`${a} → ${b}: 종류(${KIND_NAME[a[0]] || a[0]}) 글자가 다름`);
|
||
if (target.has(b)) errs.push(`${b}: ${target.get(b)}·${a} 둘이 같은 코드로`);
|
||
target.set(b, a);
|
||
if (all.has(b) && !moving.has(b))
|
||
errs.push(`${b}: 이미 있는 코드(항목교체·코드병합을 쓰세요)`);
|
||
});
|
||
return { pairs, errs };
|
||
}
|
||
|
||
/** 코드변경 실행(Act_Exc_Change) — COSTN 코드 · 메모 열쇠 · 모든 참조 · 계산 원천 */
|
||
function changeCodes(env, map) {
|
||
const d = env.d;
|
||
const chk = checkChange(d.costn_rows, map);
|
||
if (chk.errs.length) return { ok: false, errors: chk.errs };
|
||
const m = Object.fromEntries(chk.pairs);
|
||
if (!chk.pairs.length) return { ok: false, errors: ["바뀔코드가 없음"] };
|
||
(d.costn_rows || []).forEach((r) => {
|
||
const c = codeOf(r);
|
||
if (m[c]) r.CODE = m[c];
|
||
});
|
||
(d.bdqty_rows || []).forEach((r) => {
|
||
const c = txt(r.BDCODE);
|
||
if (m[c]) r.BDCODE = m[c];
|
||
});
|
||
const memos = d.costn_memos || {},
|
||
edits = (env.memo = env.memo || {});
|
||
const oldM = Object.assign({}, memos),
|
||
oldE = Object.assign({}, edits);
|
||
chk.pairs.forEach(([a]) => (delete memos[a], delete edits[a]));
|
||
chk.pairs.forEach(([a, b]) => {
|
||
if (oldM[a]) memos[b] = Object.assign({}, oldM[a], { code: b });
|
||
if (a in oldE) edits[b] = oldE[a];
|
||
});
|
||
const n = remapRefs(env, m, { items: false }); // 계산 원천은 아래 한 번에(연쇄 +α 겹침 방지)
|
||
stateRename(env.state, m);
|
||
return { ok: true, count: chk.pairs.length, refs: n, map: m };
|
||
}
|
||
|
||
/** 코드번호 변경(현재코드 + α) — 종류별 α 만큼 모든 코드를 밀어 냄 */
|
||
function plusMap(rows, alpha) {
|
||
const map = {};
|
||
(rows || []).forEach((r) => {
|
||
const c = codeOf(r),
|
||
a = parseInt((alpha || {})[c[0]], 10) || 0;
|
||
if (CODE_RE.test(c) && a) map[c] = mkCode(c[0], codeNum(c) + a);
|
||
});
|
||
return map;
|
||
}
|
||
|
||
/** 항목교체(Act_Exc_Replace) — 현재코드를 쓰던 자리(내역·구성·식)를 대상코드로 · 현재코드는 남음 */
|
||
function replaceCodes(env, map) {
|
||
const d = env.d;
|
||
const all = new Set((d.costn_rows || []).map(codeOf));
|
||
const errs = [];
|
||
const pairs = Object.entries(map || {}).filter(([a, b]) => txt(b) && a !== txt(b));
|
||
pairs.forEach(([a, b]) => {
|
||
if (!all.has(b)) errs.push(`${a} → ${b}: 대상코드가 단가목록에 없음`);
|
||
if (map[b] && txt(map[b]) !== b) errs.push(`${b}: 대상코드가 다시 교체됨`);
|
||
});
|
||
if (errs.length) return { ok: false, errors: errs };
|
||
if (!pairs.length) return { ok: false, errors: ["대상코드가 없음"] };
|
||
const m = Object.fromEntries(pairs);
|
||
const n = remapRefs(env, m);
|
||
// 계산 원천 — 내역 항목만 대상 단가로(단가 원천 자원은 그대로)
|
||
return { ok: true, count: pairs.length, refs: n, map: m };
|
||
}
|
||
|
||
/** 동일항목 대상코드 자동입력 — 묶음의 대표(첫 코드)로 */
|
||
function autoReplaceMap(rows, kinds) {
|
||
const map = {};
|
||
sameItems(rows, kinds).forEach((g) => g.slice(1).forEach((c) => (map[c] = g[0])));
|
||
return map;
|
||
}
|
||
|
||
// ── 지우기 ──
|
||
/** 코드일괄삭제 대상 — 사용표시 없는 코드(중기비 포함 여부) */
|
||
function unusedCodes(env, withX) {
|
||
const used = usedSet(env);
|
||
return (env.d.costn_rows || [])
|
||
.map(codeOf)
|
||
.filter((c) => CODE_RE.test(c) && !used.has(c) && (withX || c[0] !== "X"));
|
||
}
|
||
// 사용안된 "천원" "인" 일위대가
|
||
const unusedBUnit = (env) => {
|
||
const used = usedSet(env);
|
||
return (env.d.costn_rows || [])
|
||
.filter(
|
||
(r) => codeOf(r)[0] === "B" && !used.has(codeOf(r)) && /^(천원|인)$/.test(txt(r.UNIT)),
|
||
)
|
||
.map(codeOf);
|
||
};
|
||
|
||
/** 선택코드 삭제실행 — 쓰이는 코드는 지우지 않음(원본 Chk_True_Del_1 자리) */
|
||
function deleteCodes(env, codes) {
|
||
const d = env.d;
|
||
const want = new Set((codes || []).map(txt));
|
||
const used = usedSet(env);
|
||
const kept = [...want].filter((c) => used.has(c));
|
||
const kill = new Set([...want].filter((c) => !used.has(c)));
|
||
if (!kill.size)
|
||
return {
|
||
ok: false,
|
||
errors: kept.length ? [`쓰이는 코드라 못 지움: ${kept.join(", ")}`] : ["지울 코드 없음"],
|
||
};
|
||
d.costn_deleted = d.costn_deleted || [];
|
||
d.costn_rows = (d.costn_rows || []).filter((r) => {
|
||
if (!kill.has(codeOf(r))) return true;
|
||
if (!r._new && r._recno != null) d.costn_deleted.push(Number(r._recno));
|
||
return false;
|
||
});
|
||
d.bdqty_rows = (d.bdqty_rows || []).filter((r) => !kill.has(txt(r.BDCODE)));
|
||
kill.forEach((c) => {
|
||
if (d.costn_memos) delete d.costn_memos[c];
|
||
if (env.memo) delete env.memo[c];
|
||
});
|
||
const st = env.state;
|
||
if (st) {
|
||
const gone = (o) => kill.has(o.id) || kill.has(String(o.id || "").replace(/_[MLS]$/, ""));
|
||
["resources", "machines", "sanItems", "ilwiItems"].forEach(
|
||
(k) => st[k] && (st[k] = st[k].filter((o) => !gone(o))),
|
||
);
|
||
}
|
||
d.costn_count = d.costn_rows.length;
|
||
d.ui_dirty = true;
|
||
return { ok: true, count: kill.size, deleted: [...kill], kept };
|
||
}
|
||
|
||
// ── 신규등록 ──
|
||
function blankCostn(tmpl) {
|
||
const row = {};
|
||
Object.keys(tmpl || {}).forEach((k) => {
|
||
if (k.startsWith("_") || k.endsWith("_HEX") || k === "IFORM") return;
|
||
row[k] = /AMT|^PG_\d$|NAGCD|^RXX$/.test(k) ? "0" : "";
|
||
});
|
||
return row;
|
||
}
|
||
|
||
// 계산 원천 항목 하나 — 서버 convert_dbf_to_sim_state 와 같은 꼴(기초단가 자원 · 그 밖 일위대가류)
|
||
function stateEntry(state, r) {
|
||
const c = codeOf(r),
|
||
k = c[0];
|
||
const name = txt(r.NAME) || c,
|
||
spec = txt(r.SIZE),
|
||
unit = txt(r.UNIT);
|
||
const t = num(r.TAMT1);
|
||
if ("MLS".includes(k)) {
|
||
const cands = [1, 2, 3, 4, 5].map((n) => num(r[`AMT_${n}1`]));
|
||
state.resources.push({
|
||
id: c,
|
||
code: c,
|
||
name,
|
||
spec,
|
||
unit,
|
||
kind: k === "L" ? "노무비" : k === "S" ? "경비" : "재료비",
|
||
candidates: cands.some(Boolean) ? cands : [t, 0, 0, 0, 0],
|
||
adoptMode: "manual",
|
||
manualPrice: t,
|
||
convertHourly: false,
|
||
isUsd: false,
|
||
jdCode: txt(r.JDCODE),
|
||
uc: txt(r.UC) || "F",
|
||
});
|
||
return;
|
||
}
|
||
const lines = [];
|
||
[
|
||
["M", "재료비", num(r.MAMT1)],
|
||
["L", "노무비", num(r.LAMT1)],
|
||
["S", "경비", num(r.SAMT1)],
|
||
].forEach(([tag, kind, v]) => {
|
||
if (!(v > 0)) return;
|
||
const id = `${c}_${tag}`;
|
||
state.resources.push({
|
||
id,
|
||
code: id,
|
||
name: `${name} (${kind})`,
|
||
spec,
|
||
unit,
|
||
kind,
|
||
candidates: [v, 0, 0, 0, 0],
|
||
adoptMode: "manual",
|
||
manualPrice: v,
|
||
convertHourly: false,
|
||
isUsd: false,
|
||
isSynthetic: true,
|
||
});
|
||
lines.push({ refId: id, qty: 1.0, halPercent: 0, devd: false });
|
||
});
|
||
const base = { id: c, code: c, spec, unit, fixedM: num(r.MAMT1), fixedL: num(r.LAMT1) };
|
||
Object.assign(base, { fixedS: num(r.SAMT1), fixedT: t });
|
||
state.ilwiItems.push(
|
||
Object.assign({ name: `[${k}] ${name}`, rawName: name, prefix: k, lines }, base),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 신규등록(Act_CopyNew) — 선택 코드를 복사해 새 코드 · 구성표·산출근거 식도 복사
|
||
* from 없으면 빈 행(종류 kind) · fields = { NAME, SIZE, UNIT, … } 덮어씀
|
||
*/
|
||
function addCode(env, o) {
|
||
const d = env.d;
|
||
const rows = (d.costn_rows = d.costn_rows || []);
|
||
const src = o.from ? rows.find((r) => codeOf(r) === o.from) : null;
|
||
if (o.from && !src) return { ok: false, errors: [`복사할 코드 없음: ${o.from}`] };
|
||
const kind = src ? codeOf(src)[0] : txt(o.kind).toUpperCase();
|
||
if (!KIND_NAME[kind]) return { ok: false, errors: [`코드 종류 글자 아님: ${kind}`] };
|
||
const all = new Set(rows.map(codeOf));
|
||
const code = txt(o.code) || nextCode(rows, kind, all);
|
||
if (!CODE_RE.test(code) || code[0] !== kind)
|
||
return { ok: false, errors: [`코드 꼴 아님: ${code}`] };
|
||
if (all.has(code)) return { ok: false, errors: [`이미 있는 코드: ${code}`] };
|
||
const row = src
|
||
? Object.fromEntries(
|
||
Object.entries(src).filter(
|
||
([k]) => !k.startsWith("_") && !k.endsWith("_HEX") && k !== "IFORM",
|
||
),
|
||
)
|
||
: blankCostn(rows[0]);
|
||
Object.assign(row, o.fields || {}, { CODE: code, _new: true, _deleted: false });
|
||
if (src && src._recno != null) row._from = src._new ? src._from : src._recno;
|
||
const at = src ? rows.indexOf(src) + 1 : rows.length;
|
||
rows.splice(at, 0, row);
|
||
if (src) {
|
||
const comps = (d.bdqty_rows || []).filter((r) => txt(r.BDCODE) === codeOf(src));
|
||
if (comps.length) {
|
||
const all2 = d.bdqty_rows;
|
||
const last = all2.lastIndexOf(comps[comps.length - 1]);
|
||
const copies = comps.map((r) => {
|
||
return Object.assign({}, r, { BDCODE: code });
|
||
});
|
||
all2.splice(last + 1, 0, ...copies);
|
||
}
|
||
const t = memoText(env, codeOf(src));
|
||
if (t) {
|
||
env.memo = env.memo || {};
|
||
env.memo[code] = t;
|
||
d.costn_memos = d.costn_memos || {};
|
||
const m = d.costn_memos[codeOf(src)];
|
||
d.costn_memos[code] = Object.assign({}, m || { blockNo: 1 }, { code, text: t });
|
||
}
|
||
}
|
||
if (env.state) stateEntry(env.state, row);
|
||
d.costn_count = rows.length;
|
||
d.ui_dirty = true;
|
||
return { ok: true, code, row };
|
||
}
|
||
|
||
// ── 저장 묶음(api_ui_code.py /api/ui/save) ──
|
||
const EXTRA_COL = [
|
||
"BQCODE",
|
||
"EQCODE",
|
||
"JDCODE",
|
||
"WQCODE",
|
||
"EDT_DATE",
|
||
"NAME2",
|
||
"SIZE2",
|
||
"PP",
|
||
"CONTCODE",
|
||
];
|
||
/** 구조 덧붙임 — 원래 행(코드·덧붙임 칸) · 새 행 · 지운 원본 · 다른 표 코드 · 구성표 */
|
||
function structPayload(d) {
|
||
const cur = d.boq_table_name || "";
|
||
const costn_struct = (d.costn_rows || []).map((r) => {
|
||
const u = { CODE: codeOf(r) };
|
||
if (r._new)
|
||
Object.assign(u, { _new: true, _from: r._from, NAME: r.NAME, SIZE: r.SIZE, UNIT: r.UNIT });
|
||
if (r._new && r._from == null)
|
||
u._vals = Object.fromEntries(
|
||
Object.entries(r).filter(
|
||
([k, v]) =>
|
||
!k.startsWith("_") && !k.endsWith("_HEX") && k !== "IFORM" && typeof v !== "object",
|
||
),
|
||
);
|
||
else u._recno = r._recno;
|
||
EXTRA_COL.forEach((k) => k in r && (u[k] = r[k]));
|
||
return u;
|
||
});
|
||
const boq_code_rows = [];
|
||
boqRowSets(d).forEach(([t, rows]) => {
|
||
if (t === cur || rows === d.boq_rows) return;
|
||
rows.forEach(
|
||
(r) =>
|
||
r._bd0 &&
|
||
boq_code_rows.push(
|
||
Object.assign(
|
||
{ table: t, _recno: r._recno },
|
||
Object.fromEntries(BDK.map((k) => [k, r[k]])),
|
||
),
|
||
),
|
||
);
|
||
});
|
||
// 구성표(bdqty_rows)는 기본 저장 묶음이 함께 보냄(stc_save_extra.save_bdqty)
|
||
return { costn_struct, costn_deleted: d.costn_deleted || [], boq_code_rows };
|
||
}
|
||
const needsUiSave = (d) => Boolean(d && d.ui_dirty);
|
||
|
||
global.STMATE_UI_CODE = {
|
||
CODE_RE,
|
||
KIND_NAME,
|
||
EXTRA_COL,
|
||
codeOf,
|
||
boqRowSets,
|
||
memoText,
|
||
memoRefs,
|
||
usedSet,
|
||
lastCodes,
|
||
nextCode,
|
||
mkCode,
|
||
codeNum,
|
||
sameItems,
|
||
stateRename,
|
||
stateEntry,
|
||
remapRefs,
|
||
checkChange,
|
||
changeCodes,
|
||
plusMap,
|
||
replaceCodes,
|
||
autoReplaceMap,
|
||
unusedCodes,
|
||
unusedBUnit,
|
||
deleteCodes,
|
||
addCode,
|
||
structPayload,
|
||
needsUiSave,
|
||
};
|
||
if (typeof module !== "undefined" && module.exports) module.exports = global.STMATE_UI_CODE;
|
||
})(typeof window !== "undefined" ? window : globalThis);
|