- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면 - 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면 - 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림 - 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표 - 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0) - DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인) - 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
174 lines
9.5 KiB
JavaScript
174 lines
9.5 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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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>
|
|
</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(", ")})`;
|
|
}
|
|
|
|
function bind(ctx, api) {
|
|
if (!ctx.dbfData) return;
|
|
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);
|