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

212 lines
13 KiB
JavaScript

/**
* STMATE w 8.0 — 초기값 입력및 수정 화면(wM_Set_Sys) 끼워 넣기 (scr_ui_sys.js)
* 배치 근거: resource-TWM_SET_SYS.dfm.json — 환율/노임계수 · 소수점 적용기준 · 계산방식 · 금액 보정순서
* · 단가의 조사시점 · 재료비 적용단가 일괄적용 · 공사원가호환규정(XML) 계산방식 일괄적용
* 편집 = ctx.uiState.sysDraft(초안) → [전체 재계산후 종료]·[작업 종료] 때 dbfData.start_ini 와 state.sysConfig 에 반영
*/
(function (global) {
"use strict";
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
screens: {},
actions: {},
menus: [],
});
const S = global.STMATE_UI_SYS;
const U = global.STMATE_UI_DAN;
const esc = (s) =>
String(s == null ? "" : s).replace(
/[&<>"]/g,
(c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c],
);
function draft(ctx) {
const u = ctx.uiState;
const ini = ctx.dbfData.start_ini || (ctx.dbfData.start_ini = {});
if (!u.sysDraft || u.sysDraftSrc !== ctx.currentSourcePath) {
u.sysDraft = S.draftFromIni(ini);
u.sysDraftSrc = ctx.currentSourcePath;
u.sysRx = u.sysRx || "T";
}
return u.sysDraft;
}
function field(f, d, n) {
const k = S.keyOf(f, n || d.ARENM);
const v = d[k] == null ? "" : d[k];
const title = f.hint ? ` title="${esc(f.hint)}"` : "";
if (f.type === "sel")
return `<select class="win-select sys-f" data-k="${k}"${title}>${f.opts
.map(
([o, t]) =>
`<option value="${o}" ${String(v) === o ? "selected" : ""}>${esc(t)}</option>`,
)
.join(
"",
)}${f.opts.some(([o]) => o === String(v)) ? "" : `<option value="${esc(v)}" selected>(원본 값 ${esc(v) || "없음"})</option>`}</select>`;
if (f.type === "codes")
return `<input class="win-input sys-f" data-k="${k}" value="${esc(S.splitCodes(v).join(", "))}" style="width:240px;"${title} placeholder="L00008, L00009" />`;
return `<input class="win-input sys-f" data-k="${k}" value="${esc(v)}" style="width:${f.type === "num" ? 110 : 240}px;"${title} />`;
}
const row = (f, d, n) =>
`<div class="stm-row"><label${f.hint ? ` title="${esc(f.hint)}"` : ""}>${esc(f.cap)}</label>${field(f, d, n)}</div>`;
function render(ctx) {
if (!ctx.dbfData)
return `<div class="win-form"><div class="win-form-header"><span>초기값 입력및 수정 [wM_Set_Sys]</span></div><p class="stm-note" style="padding:12px;">열린 프로젝트 없음 — 초기값(Start8x.Ini)은 프로젝트를 연 뒤 고칩니다.</p></div>`;
const d = draft(ctx),
u = ctx.uiState,
ini = ctx.dbfData.start_ini;
const changed = S.applyDraft(Object.assign({}, ini), d);
const rx = S.RX_SETS.find((s) => s.id === u.sysRx) || S.RX_SETS[0];
const rxv = S.checkFormula(d.RXXNM);
const slots = U.slotNames(ini);
const errs = S.validate(d);
return `<div class="win-form" id="sys-form">
<div class="win-form-header"><span>초기값 입력및 수정 [wM_Set_Sys · Start8x.Ini]</span><span>바뀐 칸 ${changed.length}개${changed.length ? ` (${esc(changed.join(", "))})` : ""}</span></div>
<div class="win-subbar"><strong>※ 수정하기 원하는 항목을 고치세요 ※</strong>
<button class="win-btn primary" id="sys-recalc">전체 재계산후 종료(X)</button><button class="win-btn" id="sys-close">작 업 종 료 (Q)</button>
<button class="win-btn" id="sys-cancel">수정내용 모두 취소</button>
<button class="win-btn" id="sys-xml" title="${esc("공사원가 호환규정(XML) 계산방식 기준 — 환율 10만$ 미만/이상 동일 · 범위 전체 · D 1·1·0 · E 1·1 · F 2 · 3원미만 버림 · G·H 비목별 먼저")}">공사원가호환규정(XML,조달청) 계산방식 일괄적용</button>
${errs.length ? `<span class="stm-warn">${esc(errs.join(" · "))}</span>` : ""}</div>
<div class="stm-sys-grid">
<fieldset class="stm-group"><legend>환 율 / 노 임 계 수</legend>
<div class="stm-row"><label>K. 단가의 조사시점</label><select class="win-select sys-f" data-k="ARENM"><option value="1" ${d.ARENM === "1" ? "selected" : ""}>당초시점</option><option value="2" ${d.ARENM === "2" ? "selected" : ""}>변경시점</option></select><span class="stm-note">환율 칸 ${d.ARENM === "2" ? "2(변경)" : "1(당초)"} 편집</span></div>
${S.FIELDS.rate.map((f) => row(f, d)).join("")}
<div class="stm-note">노임환산식 값: ${rxv.ok && rxv.value != null ? rxv.value.toFixed(8) : esc(rxv.error || "-")}</div>
<div class="stm-row"><label>기타 노임산출계수</label><select class="win-select" id="sys-rx-sel">${S.RX_SETS.map((s) => `<option value="${s.id}" ${s.id === rx.id ? "selected" : ""}>${esc(s.cap)}</option>`).join("")}</select></div>
<div class="stm-row"><label>${esc(rx.btn)}</label>${field({ key: rx.rx, type: "text" }, d)}</div>
<div class="stm-row"><label> └ 적용코드</label>${field({ key: rx.con, type: "codes" }, d)}</div>
<div class="stm-row"><label>시간당 노임산출계수 적용코드</label><button class="win-btn sys-jlab" data-k="CONLD">C 기본(일반구간)</button><button class="win-btn sys-jlab" data-k="${rx.con}">${esc(rx.cap.split(";")[1] || rx.con)}</button></div>
</fieldset>
<fieldset class="stm-group"><legend>소 수 점 적 용 기 준</legend>${S.FIELDS.digits.map((f) => row(f, d)).join("")}</fieldset>
<fieldset class="stm-group"><legend>계 산 방 식</legend>${S.FIELDS.calc.map((f) => row(f, d)).join("")}
<div class="stm-row"><label title="계산방식이 【총액 먼저 계산】일 때 총액과 비목별 합계의 차액을 보정순서대로 보정">L 금액 보정순서</label><span class="stm-order">${d.OPTNM.split(
"",
)
.map(
(c, i) =>
`<span>${i + 1}. ${S.ORDER_NAMES[c]} <button class="win-btn sys-opt" data-i="${i}" data-dir="-1">◀</button><button class="win-btn sys-opt" data-i="${i}" data-dir="1">▶</button></span>`,
)
.join(
"",
)}</span><button class="win-btn" id="sys-opt-df" title="경비, 재료비, 노무비 순">기 본</button></div>
</fieldset>
<fieldset class="stm-group"><legend>재료비 적용단가 일괄적용</legend>
<div class="stm-row"><label>M. 적용할 물가지</label><select class="win-select" id="sys-selm"><option value="none">적용 안함</option>${slots.list.map((s) => `<option value="${s.slot}">${esc(s.name)}</option>`).join("")}<option value="min">최소 단가</option></select></div>
<div class="stm-row"><label><input type="checkbox" id="sys-skip0" checked /> 비교표의 단가가 0원인 것은 적용안함</label></div>
<div class="stm-row"><label><input type="checkbox" id="sys-keep" checked /> 사용자가 직접수정한 적용페이지 유지</label></div>
<div class="stm-row"><button class="win-btn primary" id="sys-selm-ok">확인 — 재료비 적용단가 일괄적용</button><span class="stm-note" id="sys-selm-msg">${esc(u.sysMsg || "")}</span></div>
<p class="stm-note">직접수정 판정 = 적용단가가 후보 5칸 어디에도 없음</p>
</fieldset>
</div>
</div>`;
}
function commit(ctx, api, recalc) {
const d = draft(ctx);
const errs = S.validate(d);
if (errs.length) return alert("고칠 칸: " + errs.join(" · "));
const changed = S.applyDraft(ctx.dbfData.start_ini, d);
ctx.state.sysConfig = S.sysConfigFromIni(ctx.dbfData.start_ini, ctx.state.sysConfig);
ctx.uiState.sysDraft = null;
ctx.uiState.sysMsg = "";
ctx.uiState.activeScreen = "wPrjList";
if (recalc) api.cascadeAndRender();
else api.renderWorkspace();
if (changed.length)
ctx.uiState.boqMsg = `초기값 ${changed.length}칸 반영(${changed.join(", ")})`;
}
// 직종·노임 설정(wM_Set_JLab) — 노무비 코드 목록에서 고른 코드를 적용코드 목록으로(추가·삭제) · 적용단가 미리보기
function openJLab(ctx, api, conKey, orig) {
const d = draft(ctx),
$ = (id) => document.getElementById(id);
const ev = global.STMATE_SMT_EVAL;
const hourly = ev && ev.hourlyWage;
const ini = Object.assign({}, ctx.dbfData.start_ini, d);
const j = S.jlab(ctx.dbfData.costn_rows, ini, conKey, hourly);
const fmt = api.fmt;
const tr = (r, cls) =>
`<tr><td class="center"><input type="checkbox" class="${cls}" value="${esc(r.code)}" /></td><td>${esc(r.code)}</td><td>${esc(r.name)}</td><td>${esc(r.size)}</td><td class="center">${esc(r.unit)}</td>${cls === "jl-del" ? `<td class="num">${r.hourly == null ? "" : fmt(r.hourly)}</td>` : ""}<td class="num">${fmt(r.price)}</td></tr>`;
api.openModal(
`시간당 노임산출계수 적용코드 [wM_Set_JLab] — ${conKey} · 환산식 ${j.formula || "(없음)"}`,
`<div class="stm-form"><p class="stm-note">노무비 코드를 목록에서 선택하십시요 — 선택코드 추가 · 선택코드삭제 · 적용 / 취소</p>
<div style="display:flex;gap:8px;">
<div style="flex:1;"><strong>노무비 목록(Grd_BDLst)</strong><div class="grid-scroll" style="max-height:300px;"><table class="dbgrid"><thead><tr><th></th><th>코드번호</th><th>명칭</th><th>규격</th><th>단위</th><th>단가</th></tr></thead><tbody>${j.source.map((r) => tr(r, "jl-add")).join("")}</tbody></table></div></div>
<div style="flex:1;"><strong>적용될 코드목록(Grd_CONLTD)</strong><div class="grid-scroll" style="max-height:300px;"><table class="dbgrid"><thead><tr><th></th><th>코드번호</th><th>명칭</th><th>규격</th><th>단위</th><th>적용단가</th><th>단가</th></tr></thead><tbody>${j.target.map((r) => tr(r, "jl-del")).join("")}</tbody></table></div></div>
</div>
<div class="stm-row"><button class="win-btn" id="jl-add">선택코드 추가 ▶</button><button class="win-btn" id="jl-del">◀ 선택코드삭제</button><button class="win-btn primary" id="jl-ok">적 용</button><button class="win-btn" id="jl-cancel">취 소</button></div></div>`,
);
let cur = String(d[conKey] != null ? d[conKey] : ini[conKey] || "");
const back = orig == null ? cur : orig;
const picked = (cls) => [...document.querySelectorAll(`.${cls}:checked`)].map((x) => x.value);
const apply = (next) => (
(d[conKey] = next),
api.closeModal(),
openJLab(ctx, api, conKey, back)
);
$("jl-add").onclick = () => apply((cur = S.jlabEdit(cur, picked("jl-add"), [])));
$("jl-del").onclick = () => apply((cur = S.jlabEdit(cur, [], picked("jl-del"))));
$("jl-ok").onclick = () => (api.closeModal(), api.renderWorkspace());
$("jl-cancel").onclick = () => ((d[conKey] = back), api.closeModal(), api.renderWorkspace());
}
function bind(ctx, api) {
if (!ctx.dbfData) return;
document
.querySelectorAll("#sys-form .sys-jlab")
.forEach((b) => (b.onclick = () => openJLab(ctx, api, b.getAttribute("data-k"))));
const d = draft(ctx),
u = ctx.uiState,
$ = (id) => document.getElementById(id);
document.querySelectorAll("#sys-form .sys-f").forEach(
(el) =>
(el.onchange = () => {
const k = el.getAttribute("data-k");
d[k] = /^CON/.test(k)
? S.joinCodes(String(el.value).split(/[,\s]+/))
: String(el.value).trim();
api.renderWorkspace();
}),
);
$("sys-rx-sel").onchange = (e) => ((u.sysRx = e.target.value), api.renderWorkspace());
document
.querySelectorAll("#sys-form .sys-opt")
.forEach(
(b) =>
(b.onclick = () => (
(d.OPTNM = S.moveOrder(
d.OPTNM,
Number(b.getAttribute("data-i")),
Number(b.getAttribute("data-dir")),
)),
api.renderWorkspace()
)),
);
$("sys-opt-df").onclick = () => ((d.OPTNM = "SML"), api.renderWorkspace());
$("sys-xml").onclick = () => (S.xmlPreset(d), api.renderWorkspace());
$("sys-cancel").onclick = () => ((u.sysDraft = null), api.renderWorkspace());
$("sys-recalc").onclick = () => commit(ctx, api, true);
$("sys-close").onclick = () => commit(ctx, api, false);
$("sys-selm-ok").onclick = () => {
const how = $("sys-selm").value;
if (how === "none") return;
const ch = U.adoptAll(ctx.dbfData.costn_rows, how, {
kinds: "M",
skipZero: $("sys-skip0").checked,
keepUser: $("sys-keep").checked,
});
ch.forEach((c) => {
const res = (ctx.state.resources || []).find((r) => r.code === c.code);
if (res) Object.assign(res, { adoptMode: `slot${c.slot}`, manualPrice: c.to });
});
u.sysMsg = `재료비 ${ch.length}건 적용단가 바꿈`;
api.cascadeAndRender();
};
}
P.screens.wM_Set_Sys = { render, bind };
})(window);