- 원본 대조 = 설치본 실행파일 52개 화면 이름·버튼 글·한글 문자열 - 지움: conv_jb.py · excel_builder.py · view_dan_ilwi.js · view_kanjub_prt.js · /api/conv/jb-* · /api/report/csv · /api/stage/projects · /api/catalog · /api/export-xlsx - 낡은 시험 삭제(지운 기능 전용): conv_change 제비율표 3 · stage_api 공사별 설정 2 · report_sheets CSV 2줄 - 장부 F4-15 · F1-33 · F6-24 설명 고침 · stmate 시험 786 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
190 lines
6.1 KiB
JavaScript
190 lines
6.1 KiB
JavaScript
/**
|
|
* STMATE w 8.0 — 계층 내역 재집계(evaluateFullBoqRows) · 단가 반영(syncCostnUnitPrice) · 저장 요청 묶음
|
|
* 근거: 16번 §4.2, 22번 §4, 28번 §2.2, 34번 §2.1
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
const Calc = global.STMATE_CALC;
|
|
const num = Calc.num;
|
|
|
|
function isStructCode(bd) {
|
|
return /^(T\d|E\d|C\d|:)$/.test(String(bd || "").trim());
|
|
}
|
|
|
|
/**
|
|
* BOQ11~14.DBF 전수 행(T1~T6 제목, E1~E6 소계/계/총계/총합계, C* 제외금액, 말단 공종행)을
|
|
* 실시간 단가·수량과 동기화하여 원단위까지 재집계
|
|
*/
|
|
function evaluateFullBoqRows(ctx) {
|
|
const rawRows = (ctx.dbfData && ctx.dbfData.boq_rows) || [];
|
|
const compRes = ctx.computed;
|
|
const leafByRecno = {};
|
|
const leafByCode = {};
|
|
(compRes.boqComputed.rows || []).forEach((r) => {
|
|
if (r._recno !== undefined) leafByRecno[r._recno] = r;
|
|
if (r.code) leafByCode[r.code] = r;
|
|
});
|
|
|
|
// C* 제외구역 판별 (ENDCODE == SORTCODE)
|
|
const exclSet = new Set();
|
|
rawRows.forEach((r, idx) => {
|
|
const bd = String(r.BDCODE1 || "").trim();
|
|
if (bd.startsWith("C")) {
|
|
const endCode = r.ENDCODE;
|
|
const startIdx = rawRows.findIndex((q) => q.SORTCODE === endCode);
|
|
if (startIdx >= 0) {
|
|
for (let k = Math.min(startIdx, idx); k <= Math.max(startIdx, idx); k++) {
|
|
exclSet.add(k);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 1차: 말단 행 단가·금액 동기화
|
|
const rows = rawRows.map((r, idx) => {
|
|
const bd = String(r.BDCODE1 || "").trim();
|
|
const recno = r._recno !== undefined ? r._recno : idx;
|
|
const leaf = leafByRecno[recno] || leafByCode[bd];
|
|
const isStruct = isStructCode(bd) || !bd;
|
|
|
|
if (!isStruct && leaf) {
|
|
return {
|
|
...r,
|
|
_idx: idx,
|
|
_recno: recno,
|
|
_rowClass: exclSet.has(idx) ? "boq-row-excl" : "boq-row-leaf",
|
|
_isExcl: exclSet.has(idx),
|
|
QTY1: leaf.qty,
|
|
MAMT1: leaf.mUnit,
|
|
LAMT1: leaf.lUnit,
|
|
SAMT1: leaf.sUnit,
|
|
TAMT1: leaf.tUnit,
|
|
M_AMT1: leaf.mAmt,
|
|
L_AMT1: leaf.lAmt,
|
|
S_AMT1: leaf.sAmt,
|
|
T_AMT1: leaf.tAmt,
|
|
};
|
|
}
|
|
let rowClass = "";
|
|
if (bd.startsWith("T")) rowClass = "boq-row-T";
|
|
else if (bd.startsWith("E")) rowClass = "boq-row-E";
|
|
else if (bd.startsWith("C")) rowClass = "boq-row-C";
|
|
|
|
return {
|
|
...r,
|
|
_idx: idx,
|
|
_recno: recno,
|
|
_rowClass: rowClass,
|
|
_isExcl: exclSet.has(idx),
|
|
QTY1: num(r.QTY1),
|
|
MAMT1: num(r.MAMT1),
|
|
LAMT1: num(r.LAMT1),
|
|
SAMT1: num(r.SAMT1),
|
|
TAMT1: num(r.TAMT1),
|
|
M_AMT1: num(r.M_AMT1),
|
|
L_AMT1: num(r.L_AMT1),
|
|
S_AMT1: num(r.S_AMT1),
|
|
T_AMT1: num(r.T_AMT1),
|
|
};
|
|
});
|
|
|
|
// 2차: T*(공종제목) 및 E*(소계/계/총계/총합계), C*(제외금액 총계) 구간 합산 동기화
|
|
rows.forEach((r, idx) => {
|
|
const bd = String(r.BDCODE1 || "").trim();
|
|
if (!bd.startsWith("T") && !bd.startsWith("E") && !bd.startsWith("C")) return;
|
|
const endCode = r.ENDCODE;
|
|
const partnerIdx = rows.findIndex((q, j) => j !== idx && q.SORTCODE === endCode);
|
|
if (partnerIdx < 0 && bd !== "E5" && bd !== "T5") return;
|
|
|
|
const sIdx = partnerIdx >= 0 ? Math.min(idx, partnerIdx) : 0;
|
|
const eIdx = partnerIdx >= 0 ? Math.max(idx, partnerIdx) : rows.length - 1;
|
|
const isExclSummary = bd.startsWith("C") || exclSet.has(idx);
|
|
|
|
let sumM = 0,
|
|
sumL = 0,
|
|
sumS = 0,
|
|
sumT = 0;
|
|
for (let k = sIdx; k <= eIdx; k++) {
|
|
const sub = rows[k];
|
|
const sbd = String(sub.BDCODE1 || "").trim();
|
|
if (!sbd || isStructCode(sbd)) continue;
|
|
if (!isExclSummary && sub._isExcl) continue;
|
|
sumM += num(sub.M_AMT1);
|
|
sumL += num(sub.L_AMT1);
|
|
sumS += num(sub.S_AMT1);
|
|
sumT += num(sub.T_AMT1);
|
|
}
|
|
if (sumT > 0 || partnerIdx >= 0) {
|
|
r.M_AMT1 = sumM;
|
|
r.L_AMT1 = sumL;
|
|
r.S_AMT1 = sumS;
|
|
r.T_AMT1 = sumT;
|
|
}
|
|
});
|
|
|
|
return rows;
|
|
}
|
|
|
|
function syncCostnUnitPrice(ctx, code, mVal, lVal, sVal) {
|
|
const tVal = mVal + lVal + sVal;
|
|
if (ctx.dbfData && ctx.dbfData.costn_rows) {
|
|
const row = ctx.dbfData.costn_rows.find((r) => String(r.CODE || "").trim() === code);
|
|
if (row) {
|
|
Object.assign(row, {
|
|
MAMT1: String(mVal),
|
|
LAMT1: String(lVal),
|
|
SAMT1: String(sVal),
|
|
TAMT1: String(tVal),
|
|
});
|
|
// 적용단가 칸(AMT_61)은 기초단가(M·L·S)만 — 일위대가·산근·중기는 원본도 0
|
|
if (/^[MLS]/.test(code) || num(row.AMT_61)) row.AMT_61 = String(tVal);
|
|
}
|
|
}
|
|
const directRes = ctx.state.resources.find((r) => r.code === code);
|
|
if (directRes) Object.assign(directRes, { adoptMode: "manual", manualPrice: tVal });
|
|
[
|
|
["M", "재료비", mVal],
|
|
["L", "노무비", lVal],
|
|
["S", "경비", sVal],
|
|
].forEach(([tag, kind, v]) => {
|
|
const synId = `${code}_${tag}`;
|
|
const syn = ctx.state.resources.find((r) => r.id === synId);
|
|
if (syn) syn.manualPrice = v;
|
|
else if (v > 0) {
|
|
ctx.state.resources.push({
|
|
id: synId,
|
|
code: synId,
|
|
name: `${code} (${kind})`,
|
|
spec: "",
|
|
unit: "",
|
|
kind,
|
|
candidates: [v, 0, 0, 0, 0],
|
|
adoptMode: "manual",
|
|
manualPrice: v,
|
|
convertHourly: false,
|
|
isUsd: false,
|
|
isSynthetic: true,
|
|
});
|
|
const ilwi = ctx.state.ilwiItems.find((x) => x.code === code);
|
|
if (ilwi && !ilwi.lines.some((ln) => ln.refId === synId)) {
|
|
ilwi.lines.push({ refId: synId, qty: 1.0, halPercent: 0, devd: false });
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 저장 요청 묶음 — save_payload.js
|
|
const buildSavePayload = (ctx) =>
|
|
(
|
|
global.STMATE_SAVE_PAYLOAD ||
|
|
(typeof require !== "undefined" ? require("./save_payload.js") : null)
|
|
).buildSavePayload(ctx);
|
|
|
|
global.STMATE_VIEW_PRJ_BOQ = {
|
|
evaluateFullBoqRows,
|
|
syncCostnUnitPrice,
|
|
buildSavePayload,
|
|
};
|
|
})(window);
|