/**
* STMATE w 8.0 — 단가 손질 도구 창 끼워 넣기 (scr_ui_dan2.js)
* 단가 일괄변경·물가변동(uM_Chg_Amt · Act_1st_2nd/2nd_1st) · 적용단가 일괄할증(Edt_Hal_Rate/Amt) · 프로젝트 계산 초기값(wM_Set_Init)
* · 호환규정코드 찾기/입력(wM_JDCode) · 시간당 손료계수·수량계산기(wM_Edit_XRate · F6) · 사용자문자(wM_Edit_USER)
* · 실적단가 → 일식단가(wM_Conv_A2W) · 특수문자(mMinMsg_) · 메모(wInDlgMemo) · 동일코드 추려보기(wSameCode)
* 계산 = ui_code_amt.js · calc_xrate.js(STMATE_XRATE) · calc_a2w.js(STMATE_A2W) · 코드 등록 = ui_code.js
*/
(function (global) {
"use strict";
const P = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
screens: {},
actions: {},
menus: [],
});
const UA = global.STMATE_UI_CODE_AMT;
const UC = global.STMATE_UI_CODE;
const esc = (s) =>
String(s == null ? "" : s).replace(
/[&<>"]/g,
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c],
);
const $ = (id) => document.getElementById(id);
const val = (id) => ($(id) ? $(id).value : "");
const num = (v) => Number(String(v == null ? "" : v).replace(/,/g, "")) || 0;
const opt = (list, cur) =>
list
.map(
([v, t]) =>
``,
)
.join("");
const need = (ctx) => (ctx.dbfData ? true : (alert("열린 프로젝트가 없음"), false));
const selCode = (ctx) => (ctx.uiState.dan || {}).code || "";
const KN = (k) => UC.KIND_NAME[k] || k;
const DOT = [
[-1, "10원 단위"],
[0, "1원 단위(소수 0)"],
[1, "소수 1자리"],
[2, "소수 2자리"],
[4, "소수 4자리"],
];
// 계산 원천 맞춤 — 1차 단가 바뀐 코드(기초단가 = 자원 · 그 밖 = 비목 합성 자원)
function syncState(ctx, codes) {
const rows = new Map((ctx.dbfData.costn_rows || []).map((r) => [UC.codeOf(r), r]));
codes.forEach((c) => {
const r = rows.get(c);
if (!r) return;
if (/^[MLS]/.test(c)) {
const res = (ctx.state.resources || []).find((x) => x.code === c && !x.isSynthetic);
if (res) Object.assign(res, { adoptMode: "manual", manualPrice: num(r.TAMT1) });
} else
global.STMATE_VIEW_PRJ_BOQ.syncCostnUnitPrice(
ctx,
c,
num(r.MAMT1),
num(r.LAMT1),
num(r.SAMT1),
);
});
}
const finish = (ctx, api, msg) => (
(ctx.uiState.danMsg = msg),
api.closeModal(),
api.cascadeAndRender()
);
// ── 단가 일괄변경·물가변동 ──
const SLOT = [
[1, "1 (당초·적용)"],
[2, "2 (변경)"],
[3, "3"],
[4, "4"],
];
function openChgAmt(ctx, api) {
if (!need(ctx)) return;
const c = selCode(ctx);
api.openModal(
"단가 일괄변경 · 물가변동 [uM_Chg_Amt]",
`
`,
);
$("ca-ok").onclick = () => {
const kinds = [...document.querySelectorAll(".ca-k:checked")].map((i) => i.value).join("");
const ch = UA.chgAmt(ctx.dbfData.costn_rows, {
kinds,
from: Number(val("ca-from")),
to: Number(val("ca-to")),
rate: num(val("ca-rate")),
digits: Number(val("ca-dot")),
mode: val("ca-up"),
skipZero: $("ca-z").checked,
codes: $("ca-one") && $("ca-one").checked ? [c] : null,
});
if (Number(val("ca-to")) === 1)
syncState(
ctx,
ch.map((x) => x.code),
);
finish(
ctx,
api,
`단가 일괄변경 ${ch.length}건 (${val("ca-from")}차 × ${val("ca-rate")}% → ${val("ca-to")}차)`,
);
};
}
const copySlot = (from, to) => (ctx, api) => {
if (
!need(ctx) ||
!confirm(`${from === 1 ? "당초단가 ⇒ 변경단가" : "변경단가 ⇒ 당초단가"} 로 모든 단가를 옮김`)
)
return;
const ch = UA.chgAmt(ctx.dbfData.costn_rows, {
kinds: "LMSWBDX",
from,
to,
rate: 100,
digits: 4,
skipZero: false,
force: true, // 차수 칸 옮김은 보호표시와 무관하게 전부
});
if (to === 1)
syncState(
ctx,
ch.map((x) => x.code),
);
ctx.uiState.danMsg = `${from}차 → ${to}차 단가 ${ch.length}건`;
api.cascadeAndRender();
};
// ── 적용단가 일괄할증 ──
function openHal(ctx, api) {
if (!need(ctx)) return;
const c = selCode(ctx);
api.openModal(
"적용단가 일괄할증 [Edt_Hal_Rate · Edt_Hal_Amt]",
``,
);
$("hl-ok").onclick = () => {
const k = val("hl-k");
// 마지막 할증 설정 — 보호해제시 자동할증(Chk_Auto_Hal)이 다시 씀
ctx.uiState.danHal = {
how: val("hl-how"),
value: num(val("hl-v")),
digits: Number(val("hl-dot")),
mode: val("hl-up"),
};
const ch = UA.halAll(ctx.dbfData.costn_rows, {
kinds: k === "one" ? c[0] : k,
codes: k === "one" ? [c] : null,
how: val("hl-how"),
value: num(val("hl-v")),
digits: Number(val("hl-dot")),
mode: val("hl-up"),
});
syncState(
ctx,
ch.map((x) => x.code),
);
finish(ctx, api, `일괄할증 ${ch.length}건`);
};
}
// ── 프로젝트 계산 초기값(분류번호·적용기준일·시설코드·수공분류코드·호환규정코드 초기화) ──
const INIT_G = [
["BQ", "분류번호"],
["ED", "적용기준일"],
["EQ", "시설코드"],
["WQ", "수공분류코드"],
["JD", "호환규정코드"],
];
function openSetInit(ctx, api) {
if (!need(ctx)) return;
const box = (g) =>
``;
api.openModal(
"프로젝트 분류번호 / 시설코드 / 호환규정코드 / 수공분류코드 / 적용기준일 초기화 [wM_Set_Init]",
``,
);
document.querySelectorAll(".si-all").forEach(
(b) =>
(b.onclick = () => {
const cs = [...document.querySelectorAll(`.si-c[data-g="${b.getAttribute("data-g")}"]`)];
const on = !cs.every((x) => x.checked);
cs.forEach((x) => (x.checked = on));
}),
);
$("si-ok").onclick = () => {
const pick = {};
document
.querySelectorAll(".si-c:checked")
.forEach(
(x) =>
(pick[x.getAttribute("data-g")] = (pick[x.getAttribute("data-g")] || "") + x.value),
);
if (!Object.keys(pick).length || !confirm("선택한 칸을 비움")) return;
const n = UA.initCols(ctx.dbfData.costn_rows, pick);
ctx.dbfData.ui_dirty = true;
finish(
ctx,
api,
`초기화 — ${
Object.entries(n)
.map(([k, v]) => `${k} ${v}건`)
.join(" · ") || "비울 칸 없음"
}`,
);
};
}
// ── 호환규정코드 찾기 / 입력 ──
function openJD(ctx, api) {
if (!need(ctx)) return;
const c = selCode(ctx);
const r = (ctx.dbfData.costn_rows || []).find((x) => UC.codeOf(x) === c);
const dup = UA.jdDup(ctx.dbfData.costn_rows);
api.openModal(
"공사원가 호환규정코드 찾기 / 입력 [wM_JDCode]",
``,
);
const set = (v) => {
r.JDCODE = v;
ctx.dbfData.ui_dirty = true;
finish(ctx, api, `${c} 호환규정코드 ${v || "삭제"}`);
};
if (!r) return;
$("jd-set").onclick = () => set(val("jd-in").trim());
$("jd-del").onclick = () => set("");
$("jd-go").onclick = async () => {
const kind = /^[LMS]/.test(c) ? "LMS" : c[0] === "W" ? "W" : "XBD";
const res = await api.postJson("/api/ui/search", {
kind,
kw: String(r.NAME || "").trim(),
limit: 2000,
});
const pool = res.rows.concat(
(ctx.dbfData.costn_rows || []).map((x) => Object.assign({ project: "(지금 프로젝트)" }, x)),
);
const cand = UA.jdCandidates(r, pool, val("jd-base"));
$("jd-res").innerHTML = cand.length
? `| 호환규정코드 | 건수 | 단위 | 프로젝트 | |
${cand.map((x) => `| ${esc(x.jd)} | ${x.n} | ${esc(x.unit)} | ${esc(x.src.slice(0, 3).join(" · "))} | |
`).join("")}
`
: '같은 기준의 후보 없음
';
document
.querySelectorAll(".jd-pick")
.forEach((b) => (b.onclick = () => ($("jd-in").value = b.getAttribute("data-jd"))));
};
}
// ── 시간당 손료계수 수정 · 노무/장비 수량계산기(F6) ──
function openXRate(ctx, api, code) {
if (!need(ctx)) return;
const c = code || selCode(ctx) || ctx.uiState.selectedIlwiCode || "";
const by = Object.fromEntries((ctx.dbfData.costn_rows || []).map((r) => [UC.codeOf(r), r]));
const rows = (ctx.dbfData.bdqty_rows || []).filter(
(q) =>
String(q.BDCODE || "").trim() === c &&
by[String(q.CODE || "").trim()] &&
String(by[String(q.CODE).trim()].UNIT || "").trim() === "천원",
);
const q0 = rows[0] ? num(rows[0].QTY1) : 0;
const X = global.STMATE_XRATE;
api.openModal(
"시간당 손료계수 수정 [wM_Edit_XRate]",
``,
);
const sumNow = () =>
$("xr-way").value === "1"
? num(val("xr-a")) + num(val("xr-b")) + num(val("xr-c"))
: num(val("xr-sum"));
["xr-a", "xr-b", "xr-c"].forEach(
(id) => ($(id).oninput = () => $("xr-way").value === "1" && ($("xr-sum").value = sumNow())),
);
$("xr-ok").onclick = () => {
const q = UA.lossQty(sumNow(), num(val("xr-rate")));
rows.forEach((r) => (r.QTY1 = String(q)));
finish(ctx, api, `${c} 손료계수 계 ${sumNow()} × 적용율 ${val("xr-rate")}% → 수량 ${q}`);
};
$("xq-go").onclick = () => {
if (!X) return ($("xq-out").textContent = "수량계산기(calc_xrate.js) 없음");
const o = X.compute({
qty1: val("xq-q1"),
cond: val("xq-cond"),
hrat: val("xq-h"),
lqty: val("xq-l"),
labor: val("xq-lab") === "1",
point: val("xq-p"),
auto: $("xq-auto").checked,
});
$("xq-out").textContent = o.ok
? `일당 시공량 ${o.qday} · 적용 수량 ${o.cqty} (자리 ${o.point})`
: o.error;
};
}
P.screens = P.screens || {};
Object.assign(P.actions, {
Act_1st_2nd: copySlot(1, 2),
Act_2nd_1st: copySlot(2, 1),
Act_UI_ChgAmt: (ctx, api) => openChgAmt(ctx, api),
Act_UI_SetInit: (ctx, api) => openSetInit(ctx, api),
Act_UI_XRate: (ctx, api) => openXRate(ctx, api),
});
P.menus.push(
{ menu: "m1", act: "Act_UI_SetInit", label: "프로젝트 분류번호/시설코드/호환규정코드 초기화" },
{ menu: "m4", act: "Act_UI_ChgAmt", label: "단가 일괄변경 · 물가변동" },
{ menu: "m2", act: "Act_UI_XRate", label: "시간당 손료계수 수정 · 수량계산기 (F6)" },
);
// F6 — 대가표·단가목록표 화면에서 손료계수 창
document.addEventListener("keydown", (e) => {
const A = global.STMATE_APP;
if (e.key !== "F6" || !A || !/^(wM_Edit_iLWi|wM_JabPum)$/.test(A.ctx.uiState.activeScreen))
return;
e.preventDefault();
openXRate(A.ctx, A);
});
global.STMATE_UI_DAN2 = { openChgAmt, openHal, openSetInit, openJD, openXRate, syncState };
})(window);