505 lines
27 KiB
JavaScript
505 lines
27 KiB
JavaScript
/**
|
||
* STMATE w 8.0 — 초기값(wM_Set_Sys) · 단가목록표(wM_JabPum) · 대가표/산근 편집기(wM_Edit_iLWi · wM_Edit_San · COSTN.SMT)
|
||
* 근거: 22_UI_폼_분석.md, 23_COSTN_SMT_전수파싱.md, 26_편집기와_사이클식.md, 28_변환_수량산출_식언어.md, 34_화면구조_정리.md
|
||
*/
|
||
(function (global) {
|
||
"use strict";
|
||
|
||
const Calc = global.STMATE_CALC;
|
||
const fmt = Calc.fmt;
|
||
const num = Calc.num;
|
||
|
||
const KIND_LABELS = {
|
||
ALL: "모든단가",
|
||
L: "노무비(L)",
|
||
M: "재료비(M)",
|
||
S: "경비(S)",
|
||
B: "일위대가(B)",
|
||
X: "중기비(X)",
|
||
D: "단가산출(D)",
|
||
W: "견적/표준시장(W)",
|
||
};
|
||
|
||
/**
|
||
* COSTN.SMT 텍스트를 실시간 평가하여 Q식, Cm식, 참조(&X00006L/M/S&) 단가 및 비목별 합계 산출
|
||
*/
|
||
function evaluateSmtFormulaText(smtText, ctx, codeHint) {
|
||
if (global.STMATE_ENGINE && global.STMATE_ENGINE.evaluateSmtFormulaText) {
|
||
return global.STMATE_ENGINE.evaluateSmtFormulaText(smtText, ctx, codeHint);
|
||
}
|
||
return { vars: {}, linesOut: [], sumM: 0, sumL: 0, sumS: 0, sumT: 0 };
|
||
}
|
||
|
||
/**
|
||
* 1. wM_Set_Sys (초기값 입력 및 수정 화면)
|
||
*/
|
||
function renderWMSetSys(ctx) {
|
||
const sys = ctx.state.sysConfig;
|
||
const prj = (ctx.dbfData && ctx.dbfData.prjlst) || {};
|
||
const evalRx = Calc.evalLeftToRight(sys.rxFormula);
|
||
|
||
return `
|
||
<div class="win-form">
|
||
<div class="win-form-header">
|
||
<span>⚙️ 초기값 입력 및 수정 [wM_Set_Sys · PRJLST.DBF 초기값 변수 42종]</span>
|
||
<span>노임환산식 좌→우 평가: ${evalRx.toFixed(6)}</span>
|
||
</div>
|
||
<div class="win-subbar">
|
||
<button class="win-btn primary" id="sys-btn-apply">⚡ 초기값 적용 및 전 공정 재계산</button>
|
||
<span style="color:#4b5563;">변경된 초기값은 기초단가·중기사용료·일위대가·내역서에 즉시 전파됩니다.</span>
|
||
</div>
|
||
|
||
<div class="split-vert" style="overflow:auto;">
|
||
<div class="pane-box" style="padding:10px;">
|
||
<div class="pane-title"><span>1. 환율 및 소수 자리수 설정 (EXLNM1/2 · KCD · ACD · ZCD · JCD · ECD · BPN)</span></div>
|
||
<div class="kanjub-16grid" style="grid-template-columns:repeat(3,1fr);">
|
||
<div class="kanjub-field"><label>당초 달러환율(EXLNM1)</label><input type="number" id="sys-usd1" value="${sys.usdRate1}" /></div>
|
||
<div class="kanjub-field"><label>변경 달러환율(EXLNM2)</label><input type="number" id="sys-usd2" value="${sys.usdRate2}" /></div>
|
||
<div class="kanjub-field"><label>환율적용 단계</label><select id="sys-usd-mode"><option value="0" ${!sys.useChangedUsd ? "selected" : ""}>당초환율(EXLNM1)</option><option value="1" ${sys.useChangedUsd ? "selected" : ""}>변경환율(EXLNM2)</option></select></div>
|
||
<div class="kanjub-field"><label>단가 자리수(ECD)</label><select id="sys-ecd"><option value="0" ${sys.ecdDigits === 0 ? "selected" : ""}>0자리(1원 정수)</option><option value="1" ${sys.ecdDigits === 1 ? "selected" : ""}>소수 1자리</option><option value="2" ${sys.ecdDigits === 2 ? "selected" : ""}>소수 2자리</option><option value="-1" ${sys.ecdDigits === -1 ? "selected" : ""}>10원 단위 절사</option></select></div>
|
||
<div class="kanjub-field"><label>내역 차액보정(CHNNM)</label><select id="sys-chn"><option value="bimok" ${sys.chnMode === "bimok" ? "selected" : ""}>비목우선 (M+L+S=합계)</option><option value="total" ${sys.chnMode === "total" ? "selected" : ""}>총액우선 (단가×수량 차액 보정)</option></select></div>
|
||
<div class="kanjub-field"><label>차액보정 순위(OPTNM)</label><select id="sys-opt"><option value="SML" ${sys.optOrder === "SML" ? "selected" : ""}>경비→재료→노무 (SML)</option><option value="MSL" ${sys.optOrder === "MSL" ? "selected" : ""}>재료→경비→노무 (MSL)</option><option value="LSM" ${sys.optOrder === "LSM" ? "selected" : ""}>노무→경비→재료 (LSM)</option></select></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pane-box" style="padding:10px;">
|
||
<div class="pane-title"><span>2. 노임 시간당 환산식 (RXNM_ · 좌→우 순차 평가 18번 §2.1) 및 특수노임 조건코드 (RX1~5CD)</span></div>
|
||
<div class="kanjub-16grid" style="grid-template-columns:repeat(2,1fr);">
|
||
<div class="kanjub-field"><label>기본 노임환산식(RXNM_)</label><input type="text" id="sys-rx-formula" value="${sys.rxFormula}" /></div>
|
||
<div class="kanjub-field"><label>환산 계수 적용 직종코드</label><input type="text" id="sys-cond-codes" value="${sys.condCodes}" /></div>
|
||
</div>
|
||
<p style="margin:6px 0 0;font-size:11px;color:#1e3a8a;">
|
||
※ STMATE 고유 파서 검증: <code>1/8*16/12*25/20</code> → 좌→우 순차 평가 <code>0.20833333</code> (기계운전원 일급 267,360원 × 0.20833333 = <strong>55,700원/hr</strong> 정확 재현)
|
||
</p>
|
||
</div>
|
||
|
||
<div class="pane-box" style="padding:10px;">
|
||
<div class="pane-title"><span>3. 현재 프로젝트 PRJLST.DBF 원본 초기값 필드</span></div>
|
||
<div class="kanjub-16grid" style="grid-template-columns:repeat(4,1fr);">
|
||
<div class="kanjub-field"><label>공사명(PNAME)</label><input type="text" readonly value="${prj.PNAME || ""}" /></div>
|
||
<div class="kanjub-field"><label>단가기준(PNUM)</label><input type="text" readonly value="${prj.PNUM || ""}" /></div>
|
||
<div class="kanjub-field"><label>내역번호(PCODE)</label><input type="text" readonly value="BOQ${prj.PCODE || "11"}" /></div>
|
||
<div class="kanjub-field"><label>진행단계(K_ORD)</label><input type="text" readonly value="${prj.K_ORD || "0"}" /></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 2. wM_JabPum (단가목록표 화면 — COSTN.DBF 100% 전수 조회·편집)
|
||
*/
|
||
function renderWMJabPum(ctx) {
|
||
const rawCostn = (ctx.dbfData && ctx.dbfData.costn_rows) || [];
|
||
const memos = (ctx.dbfData && ctx.dbfData.costn_memos) || {};
|
||
const filterKind = ctx.uiState.danFilter || "ALL";
|
||
const kw = (ctx.uiState.danSearch || "").trim().toLowerCase();
|
||
|
||
// state.resources 수정값 동기화
|
||
const resMap = {};
|
||
(ctx.computed.resList || []).forEach((r) => {
|
||
if (!r.isSynthetic) resMap[r.code] = r;
|
||
});
|
||
|
||
const counts = { ALL: rawCostn.length, L: 0, M: 0, S: 0, B: 0, X: 0, D: 0, W: 0 };
|
||
rawCostn.forEach((r) => {
|
||
const p = String(r.CODE || " ")[0].toUpperCase();
|
||
if (counts[p] !== undefined) counts[p]++;
|
||
});
|
||
|
||
const filtered = rawCostn.filter((r) => {
|
||
const code = String(r.CODE || "").trim();
|
||
if (!code) return false;
|
||
const p = code[0].toUpperCase();
|
||
if (filterKind !== "ALL" && p !== filterKind) return false;
|
||
if (kw) {
|
||
const memoTxt = memos[code] ? memos[code].text : "";
|
||
const hay =
|
||
`${code} ${r.NAME || ""} ${r.SIZE || ""} ${r.UNIT || ""} ${memoTxt}`.toLowerCase();
|
||
if (!hay.includes(kw)) return false;
|
||
}
|
||
return true;
|
||
});
|
||
|
||
return `
|
||
<div class="win-form">
|
||
<div class="win-form-header">
|
||
<span>📋 단가목록표 [wM_JabPum · COSTN.DBF 전수 원장] — 총 ${rawCostn.length}건 중 ${filtered.length}건 표시</span>
|
||
<span>5개 가격지 후보(AMT_11~51) 및 적용단가(TAMT1 = MAMT1 + LAMT1 + SAMT1)</span>
|
||
</div>
|
||
|
||
<div class="win-subbar">
|
||
${Object.keys(KIND_LABELS)
|
||
.map(
|
||
(k) => `
|
||
<button class="win-btn dan-filter-btn ${filterKind === k ? "active" : ""}" data-filter="${k}">
|
||
${KIND_LABELS[k]} (${counts[k] || 0})
|
||
</button>
|
||
`,
|
||
)
|
||
.join("")}
|
||
<input type="text" id="dan-search-input" class="win-input" placeholder="명칭,규격 / 산근내용 검색..." value="${ctx.uiState.danSearch || ""}" style="width:185px;margin-left:auto;" />
|
||
<button class="win-btn" id="dan-btn-boxa">🔍 가격지 비교(wM_Boxa)</button>
|
||
<button class="win-btn" id="dan-btn-edthal">📐 기준단가 비율보정(wM_Edt_Hal)</button>
|
||
<button class="win-btn" id="dan-btn-catalog">📚 표준카탈로그(894종)</button>
|
||
</div>
|
||
|
||
<div class="split-vert">
|
||
<div class="pane-box" style="flex:1;">
|
||
<div class="grid-scroll">
|
||
<table class="dbgrid">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:42px;">No</th>
|
||
<th style="width:76px;">코드(CODE)</th>
|
||
<th style="width:58px;">구분</th>
|
||
<th>명칭 (NAME)</th>
|
||
<th style="width:165px;">규격 (SIZE)</th>
|
||
<th style="width:55px;">단위</th>
|
||
<th style="width:98px;">재료단가(MAMT1)</th>
|
||
<th style="width:98px;">노무단가(LAMT1)</th>
|
||
<th style="width:98px;">경비단가(SAMT1)</th>
|
||
<th style="width:105px;">합계단가(TAMT1)</th>
|
||
<th style="width:82px;">1단가(조달)</th>
|
||
<th style="width:82px;">2단가(물가)</th>
|
||
<th style="width:82px;">3단가(거래)</th>
|
||
<th style="width:82px;">산근/대가</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${filtered
|
||
.map((r, i) => {
|
||
const code = String(r.CODE || "").trim();
|
||
const p = code[0].toUpperCase();
|
||
const res = resMap[code];
|
||
const m1 = res
|
||
? res.kind === "재료비"
|
||
? res.appliedPrice
|
||
: 0
|
||
: num(r.MAMT1);
|
||
const l1 = res
|
||
? res.kind === "노무비"
|
||
? res.appliedPrice
|
||
: 0
|
||
: num(r.LAMT1);
|
||
const s1 = res ? (res.kind === "경비" ? res.appliedPrice : 0) : num(r.SAMT1);
|
||
const t1 = m1 + l1 + s1 || num(r.TAMT1);
|
||
const hasMemo = Boolean(memos[code]);
|
||
return `
|
||
<tr data-code="${code}">
|
||
<td class="center">${i + 1}</td>
|
||
<td class="center"><strong>${code}</strong></td>
|
||
<td class="center">${p}</td>
|
||
<td>${r.NAME || ""}</td>
|
||
<td>${r.SIZE || ""}</td>
|
||
<td class="center">${r.UNIT || ""}</td>
|
||
<td class="num">
|
||
<input type="number" class="cell-input num dan-edit-m" data-code="${code}" value="${m1}" />
|
||
</td>
|
||
<td class="num">
|
||
<input type="number" class="cell-input num dan-edit-l" data-code="${code}" value="${l1}" />
|
||
</td>
|
||
<td class="num">
|
||
<input type="number" class="cell-input num dan-edit-s" data-code="${code}" value="${s1}" />
|
||
</td>
|
||
<td class="num"><strong>${fmt(t1)}</strong></td>
|
||
<td class="num">${num(r.AMT_11) ? fmt(r.AMT_11) : "-"}</td>
|
||
<td class="num">${num(r.AMT_21) ? fmt(r.AMT_21) : "-"}</td>
|
||
<td class="num">${num(r.AMT_31) ? fmt(r.AMT_31) : "-"}</td>
|
||
<td class="center">
|
||
${
|
||
hasMemo || p === "B" || p === "D" || p === "X"
|
||
? `<button class="win-btn dan-jump-ilwi" data-code="${code}" style="height:19px;padding:0 5px;">산근보기</button>`
|
||
: "-"
|
||
}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
})
|
||
.join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 3. wM_Edit_iLWi / wM_Edit_San / wM_Edit_EQ (일위대가 · 단가산출근거 · 중기사용료 통합 편집 화면)
|
||
* - 좌측: COSTN.DBF 내 B(일위대가), D(단가산출), X(중기) 전체 목록 + COSTN.SMT 보유 여부
|
||
* - 우측 상단: 선택 항목의 COSTN.SMT 원문 편집기 (`&X00006L&`, `Q=3600*q*K*f*E/Cm`, `:=A01`)
|
||
* - 우측 하단: 실시간 파싱·변수 평가 결과 테이블 및 [단가에 반영(F9)]
|
||
*/
|
||
function renderWMEditIlwi(ctx) {
|
||
const rawCostn = (ctx.dbfData && ctx.dbfData.costn_rows) || [];
|
||
const memos = (ctx.dbfData && ctx.dbfData.costn_memos) || {};
|
||
const subFilter = ctx.uiState.ilwiSubFilter || "ALL_BDX";
|
||
|
||
const bdxRows = rawCostn.filter((r) => {
|
||
const c = String(r.CODE || "").trim();
|
||
if (!c) return false;
|
||
const p = c[0].toUpperCase();
|
||
if (subFilter === "ALL_BDX") return p === "B" || p === "D" || p === "X" || Boolean(memos[c]);
|
||
if (subFilter === "SMT_ONLY") return Boolean(memos[c]);
|
||
return p === subFilter;
|
||
});
|
||
|
||
const selCode =
|
||
ctx.uiState.selectedIlwiCode &&
|
||
bdxRows.some((r) => String(r.CODE).trim() === ctx.uiState.selectedIlwiCode)
|
||
? ctx.uiState.selectedIlwiCode
|
||
: bdxRows.find((r) => Boolean(memos[String(r.CODE || "").trim()])) || bdxRows[0]
|
||
? String(
|
||
(bdxRows.find((r) => Boolean(memos[String(r.CODE || "").trim()])) || bdxRows[0]).CODE,
|
||
).trim()
|
||
: "";
|
||
|
||
const selRow = bdxRows.find((r) => String(r.CODE).trim() === selCode) || {};
|
||
const memoObj = memos[selCode] || null;
|
||
const recipeComps =
|
||
global.STMATE_ENGINE && global.STMATE_ENGINE.getRecipeBreakdown
|
||
? global.STMATE_ENGINE.getRecipeBreakdown(ctx, selCode)
|
||
: [];
|
||
const defaultText = memoObj
|
||
? memoObj.text
|
||
: recipeComps.length
|
||
? `'▣ ${selRow.NAME || selCode} (${selRow.SIZE || ""}) — 표준 호표 조립 구성 (${recipeComps.length}행)'\r\n` +
|
||
recipeComps
|
||
.map(
|
||
(c, ci) =>
|
||
`'${ci + 1}. ${c["명칭"]} (${c["규격"] || "-"}) : 수량 ${c["수량"]} ${c["단위"] || ""}'`,
|
||
)
|
||
.join("\r\n") +
|
||
`\r\n'노 무 비 : ${num(selRow.LAMT1)}'\r\n'재 료 비 : ${num(selRow.MAMT1)}'\r\n'경 비 : ${num(selRow.SAMT1)}'\r\n(==)`
|
||
: `'▣ ${selRow.NAME || selCode} (${selRow.SIZE || ""})'\r\n'1. 재료비 : ${fmt(num(selRow.MAMT1))}원'\r\n'2. 노무비 : ${fmt(num(selRow.LAMT1))}원'\r\n'3. 경 비 : ${fmt(num(selRow.SAMT1))}원'\r\n(==)`;
|
||
const currentSmtText =
|
||
ctx.uiState.editedSmtMap && ctx.uiState.editedSmtMap[selCode] !== undefined
|
||
? ctx.uiState.editedSmtMap[selCode]
|
||
: defaultText;
|
||
|
||
const evalRes = evaluateSmtFormulaText(currentSmtText, ctx, selCode);
|
||
|
||
return `
|
||
<div class="win-form">
|
||
<div class="win-form-header">
|
||
<span>🧮 일위대가 · 단가산출근거 · 중기사용료 편집기 [wM_Edit_iLWi / wM_Edit_San / COSTN.SMT]</span>
|
||
<span>선택 호표: ${selCode} ${selRow.NAME || ""} (SIxMemo 블록 #${memoObj ? memoObj.blockNo : "-"}, ${memoObj ? memoObj.byteLen : 0} bytes)</span>
|
||
</div>
|
||
|
||
<div class="win-subbar">
|
||
<button class="win-btn ilwi-sub-btn ${subFilter === "ALL_BDX" ? "active" : ""}" data-sub="ALL_BDX">전체 대가·산근·중기 (${bdxRows.length})</button>
|
||
<button class="win-btn ilwi-sub-btn ${subFilter === "SMT_ONLY" ? "active" : ""}" data-sub="SMT_ONLY">COSTN.SMT 산식보유 (${Object.keys(memos).length})</button>
|
||
<button class="win-btn ilwi-sub-btn ${subFilter === "D" ? "active" : ""}" data-sub="D">단가산출 D</button>
|
||
<button class="win-btn ilwi-sub-btn ${subFilter === "B" ? "active" : ""}" data-sub="B">일위대가 B</button>
|
||
<button class="win-btn ilwi-sub-btn ${subFilter === "X" ? "active" : ""}" data-sub="X">중기사용료 X</button>
|
||
<button class="win-btn" id="ilwi-btn-load-recipe" style="margin-left:auto;">📥 표준 레시피(350종)/특수산식(21종) 불러오기</button>
|
||
<button class="win-btn primary" id="ilwi-btn-apply-f9" data-code="${selCode}">⚡ F9 산식 계산결과를 호표단가에 반영</button>
|
||
</div>
|
||
|
||
<div class="split-horiz">
|
||
<!-- 좌측: B / D / X 호표 목록 -->
|
||
<div class="pane-box" style="width:430px;">
|
||
<div class="pane-title">
|
||
<span>호표 마스터 목록 (클릭 시 우측 COSTN.SMT 전개식 로드)</span>
|
||
<span>총 ${bdxRows.length}건</span>
|
||
</div>
|
||
<div class="grid-scroll">
|
||
<table class="dbgrid">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:66px;">코드</th>
|
||
<th>품명 (NAME)</th>
|
||
<th style="width:95px;">규격</th>
|
||
<th style="width:42px;">단위</th>
|
||
<th style="width:82px;">합계단가</th>
|
||
<th style="width:45px;">SMT</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${bdxRows
|
||
.map((r) => {
|
||
const c = String(r.CODE || "").trim();
|
||
const isSel = c === selCode;
|
||
const hasM = Boolean(memos[c]);
|
||
return `
|
||
<tr class="ilwi-master-row ${isSel ? "selected" : ""}" data-code="${c}" style="cursor:pointer;">
|
||
<td class="center"><strong>${c}</strong></td>
|
||
<td>${r.NAME || ""}</td>
|
||
<td>${r.SIZE || ""}</td>
|
||
<td class="center">${r.UNIT || ""}</td>
|
||
<td class="num"><strong>${fmt(num(r.TAMT1))}</strong></td>
|
||
<td class="center">${hasM ? "📝" : "-"}</td>
|
||
</tr>
|
||
`;
|
||
})
|
||
.join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 우측: COSTN.SMT 수식 편집기 + 실시간 평가 분석기 -->
|
||
<div class="split-vert" style="flex:1;padding:0;">
|
||
<div class="pane-box" style="flex:1;">
|
||
<div class="pane-title">
|
||
<span>📝 COSTN.SMT 전개 텍스트 편집기 — <code>&X00026L&</code> 참조 · <code>Q=3600*q*K*f*E/Cm</code> · <code>:=A01</code> 문법 실시간 해석</span>
|
||
<span>DB 저장단가: 재 ${fmt(num(selRow.MAMT1))} / 노 ${fmt(num(selRow.LAMT1))} / 경 ${fmt(num(selRow.SAMT1))} / 계 ${fmt(num(selRow.TAMT1))}원</span>
|
||
</div>
|
||
<textarea id="smt-formula-textarea" class="smt-editor-area" data-code="${selCode}" spellcheck="false">${currentSmtText}</textarea>
|
||
</div>
|
||
|
||
<div class="pane-box" style="flex:1;">
|
||
<div class="pane-title">
|
||
<span>🔍 SIxMemo 수식 파서 실시간 평가 결과 (변수: ${
|
||
Object.keys(evalRes.vars)
|
||
.map((k) => `${k}=${evalRes.vars[k]}`)
|
||
.join(", ") || "없음"
|
||
})</span>
|
||
<span>평가 합계: 재 ${fmt(evalRes.sumM)} + 노 ${fmt(evalRes.sumL)} + 경 ${fmt(evalRes.sumS)} = <strong>${fmt(evalRes.sumT)}원</strong></span>
|
||
</div>
|
||
<div class="grid-scroll">
|
||
<table class="dbgrid">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px;">행</th>
|
||
<th>산식 원문 (COSTN.SMT Line)</th>
|
||
<th style="width:150px;">변수 대입/평가</th>
|
||
<th style="width:120px;">자원참조(&코드&)</th>
|
||
<th style="width:95px;">참조단가</th>
|
||
<th style="width:95px;">산출금액</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${evalRes.linesOut
|
||
.map(
|
||
(ln) => `
|
||
<tr>
|
||
<td class="center">${ln.lineNo}</td>
|
||
<td style="font-family:Consolas,monospace;">${ln.raw}</td>
|
||
<td style="color:#0050b3;font-weight:700;">${ln.assigned || ""}</td>
|
||
<td class="center">${ln.refInfo ? `${ln.refInfo.refCode}[${ln.refInfo.compTag}] (${ln.refInfo.refName})` : ""}</td>
|
||
<td class="num">${ln.refInfo ? fmt(ln.refInfo.baseUnitPrice) : ""}</td>
|
||
<td class="num"><strong>${ln.refInfo ? fmt(ln.refInfo.calcAmt, 1) : ""}</strong></td>
|
||
</tr>
|
||
`,
|
||
)
|
||
.join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
/**
|
||
* 4. 단가/카탈로그 모달 생성기 (wM_Boxa / wM_Edt_Hal / 표준카탈로그 894코드·350레시피·21특수산식)
|
||
*/
|
||
function buildDanModalHtml(modalKind, ctx) {
|
||
if (modalKind === "wM_Boxa") {
|
||
return `
|
||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||
<p style="margin:0;"><strong>[wM_Boxa] 5개 가격지 단가 비교·일괄 채택</strong> — 자재 후보단가(AMT_11~51) 중 지정 슬롯 또는 최저가를 적용단가(AMT_61/MAMT1)로 일괄 채택합니다.</p>
|
||
<div class="kanjub-16grid" style="grid-template-columns:repeat(2,1fr);">
|
||
<div class="kanjub-field"><label>채택 기준</label>
|
||
<select id="boxa-adopt-mode">
|
||
<option value="min">최소단가 (0원 제외 최저가 자동채택)</option>
|
||
<option value="0">1단가 (조달청 가격정보 AMT_11)</option>
|
||
<option value="1">2단가 (물가자료 AMT_21)</option>
|
||
<option value="2">3단가 (물가정보 AMT_31)</option>
|
||
<option value="3">4단가 (유통물가 AMT_41)</option>
|
||
<option value="4">5단가 (거래가격 AMT_51)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div style="text-align:right;"><button class="win-btn primary" id="modal-apply-boxa">자재 적용단가 일괄 변경</button></div>
|
||
</div>
|
||
`;
|
||
}
|
||
if (modalKind === "wM_Edt_Hal") {
|
||
return `
|
||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||
<p style="margin:0;"><strong>[wM_Edt_Hal] 기준단가 비율 일괄 수정</strong> — <code>적용단가 = 기준단가 × 비율(%)</code> (적용단가 0원 및 보호항목 UC=T 제외).</p>
|
||
<div class="kanjub-16grid" style="grid-template-columns:repeat(3,1fr);">
|
||
<div class="kanjub-field"><label>대상 비목</label><select id="edthal-kind"><option value="ALL">전체 (재·노·경)</option><option value="M">재료비(M)만</option><option value="L">노무비(L)만</option><option value="S">경비(S)만</option></select></div>
|
||
<div class="kanjub-field"><label>적용 비율(%)</label><input type="number" id="edthal-pct" value="98" step="0.5" /></div>
|
||
<div class="kanjub-field"><label>끝전 처리</label><select id="edthal-round"><option value="trunc">원단위 절사</option><option value="round">원단위 반올림</option></select></div>
|
||
</div>
|
||
<div style="text-align:right;"><button class="win-btn primary" id="modal-apply-edthal">기준단가 비율 일괄 수정 실행</button></div>
|
||
</div>
|
||
`;
|
||
}
|
||
// 표준 카탈로그 (894 코드 · 350 일위대가 레시피 · 21 특수 단가산출)
|
||
const cat = ctx.catalogData || { codes: [], recipes: [], special_san: [] };
|
||
return `
|
||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||
<p style="margin:0;">
|
||
<strong>[STMATE 표준 카탈로그 브라우저]</strong> — <code>30_원자료</code>에서 추출한 표준코드 <strong>${cat.codes.length}종</strong> · 일위대가 레시피 <strong>${cat.recipes.length}종</strong> · 특수 단가산출 <strong>${cat.special_san.length}종</strong>
|
||
</p>
|
||
<div class="split-horiz" style="height:400px;padding:0;">
|
||
<div class="pane-box" style="flex:1;">
|
||
<div class="pane-title"><span>특수 단가산출 21개 (Q식/사이클식 원문)</span></div>
|
||
<div class="grid-scroll">
|
||
<table class="dbgrid">
|
||
<thead><tr><th>호표</th><th>명칭</th><th>단위</th><th>Q식</th><th>편집기로 복사</th></tr></thead>
|
||
<tbody>
|
||
${(cat.special_san || [])
|
||
.map(
|
||
(s, idx) => `
|
||
<tr>
|
||
<td class="center">#${s["호표"]}</td>
|
||
<td><strong>${s["명칭"]}</strong></td>
|
||
<td class="center">${s["단위"]}</td>
|
||
<td style="font-family:Consolas,monospace;">${(s["인자"] && s["인자"].Q) || ""}</td>
|
||
<td class="center"><button class="win-btn btn-use-special-san" data-idx="${idx}">산식적용</button></td>
|
||
</tr>
|
||
`,
|
||
)
|
||
.join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="pane-box" style="flex:1;">
|
||
<div class="pane-title"><span>표준 일위대가 레시피 (${cat.recipes.length}종 중 상위 60종)</span></div>
|
||
<div class="grid-scroll">
|
||
<table class="dbgrid">
|
||
<thead><tr><th>구분</th><th>명칭</th><th>규격</th><th>단위</th><th>구성수</th></tr></thead>
|
||
<tbody>
|
||
${(cat.recipes || [])
|
||
.slice(0, 60)
|
||
.map(
|
||
(r) => `
|
||
<tr>
|
||
<td class="center">${r["종류"]}</td>
|
||
<td><strong>${r["명칭"]}</strong></td>
|
||
<td>${r["규격"] || ""}</td>
|
||
<td class="center">${r["단위"] || ""}</td>
|
||
<td class="center">${(r["구성"] || []).length}행</td>
|
||
</tr>
|
||
`,
|
||
)
|
||
.join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
global.STMATE_VIEW_DAN_ILWI = {
|
||
evaluateSmtFormulaText,
|
||
renderWMSetSys,
|
||
renderWMJabPum,
|
||
renderWMEditIlwi,
|
||
buildDanModalHtml,
|
||
};
|
||
})(window);
|