- 지식DB REA 분석 33 기성준공단계 작업자료(정적분석 보고서·근거) - 기성 공사(PCLASS 6) 저장 = COSTW2~4 · [2·3·4_KANJUB] n_C_* 기성 화면 값 그대로 - save_payload.js 로 저장 요청 묶음 분리(view_prj_boq.js 700줄 한도) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
5.9 KiB
JavaScript
143 lines
5.9 KiB
JavaScript
/**
|
|
* STMATE 저장 요청 묶음 (save_payload.js) — view_prj_boq.js 에서 떼어 냄 · /api/save 요청 본문
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
const num = (v, d = 0) => (global.STMATE_CALC || require("./calc.js")).num(v, d);
|
|
|
|
/**
|
|
* 저장 요청(/api/save) — 지금 값을 그대로 보내고 서버(stc_save.py)가 원본과 뜻으로 견줘 바뀐 칸만 씀
|
|
* 보내지 않는 것: 1차 단가로 2차 칸 덮기 · 고정 낙찰율 3차 칸 · 소계·PRJLST 직접비(서버가 다시 셈)
|
|
* 근거: STC_왕복검증.md §3(알려진 칸만 갱신) · 16번 §4(행 금액·계층 집계·TCOSTn)
|
|
*/
|
|
const COSTN_SAVE_COL =
|
|
/^(NAME|SIZE|UNIT|AMT_\d1|PG_1|UC|SBMOK|S_ADJ|ADJ_[MLS]|RXX|NAGCD|[TMLS]AMT[1-5])$/;
|
|
const isPctRow = (r) =>
|
|
String(r.UNIT1 || "")
|
|
.trim()
|
|
.endsWith("%") && num(r.T_AMT1) > 0;
|
|
const plainFields = (r) =>
|
|
Object.fromEntries(
|
|
Object.entries(r).filter(
|
|
([k, v]) =>
|
|
!k.startsWith("_") && !k.endsWith("_HEX") && k !== "IFORM" && typeof v !== "object",
|
|
),
|
|
);
|
|
|
|
function itemQtyFields(b, raw) {
|
|
const u = {};
|
|
if (!b) return u;
|
|
if (isPctRow(raw)) {
|
|
(b.pctQtys || []).forEach((q, k) => q != null && (u[`QTY${k + 1}`] = q));
|
|
return u;
|
|
}
|
|
if (b.qty !== undefined) u.QTY1 = b.qty;
|
|
const q = Array.isArray(b.qtys) ? b.qtys : null;
|
|
for (let n = 2; n <= 4; n++) if (q && q[n - 1] !== undefined) u[`QTY${n}`] = q[n - 1];
|
|
if (q && num(q[1]) > 0 && b.changedQty !== undefined && num(b.changedQty) !== num(q[1]))
|
|
u.QTY2 = b.changedQty; // 변경 수량 칸만 고친 화면(rev-edit-qty2)
|
|
Object.entries(b.units || {}).forEach(([n, mls]) => {
|
|
if (num(n) < 2 || !Array.isArray(mls)) return;
|
|
["M", "L", "S"].forEach((c, k) => (u[`${c}AMT${n}`] = mls[k]));
|
|
});
|
|
return u;
|
|
}
|
|
|
|
function newItemRow(b) {
|
|
const q = Array.isArray(b.qtys) ? b.qtys : [b.qty, b.changedQty, 0, 0];
|
|
const u = { _new: true, BDCODE1: b.code || b.refId, NAME1: b.name, SIZE1: b.spec };
|
|
Object.assign(u, { UNIT1: b.unit, ITNUM: b.itnum || "", ENDCODE: 0 });
|
|
q.forEach((v, k) => (u[`QTY${k + 1}`] = v || 0));
|
|
return Object.assign(u, itemQtyFields(b, u));
|
|
}
|
|
|
|
function buildSavePayload(ctx) {
|
|
const d = ctx.dbfData || {};
|
|
const comp = ctx.computed || {};
|
|
const items = (ctx.state.boqItems || []).filter((b) => b._recno !== undefined && !b.isNew);
|
|
const byRec = new Map(items.map((b) => [b._recno, b]));
|
|
const fresh = (ctx.state.boqItems || []).filter((b) => b.isNew && b._recno === undefined);
|
|
const boqUpdates = [];
|
|
(d.boq_rows || []).forEach((r) => {
|
|
const isNew = r._new || r.SORTCODE === undefined;
|
|
const u = Object.assign(plainFields(r), itemQtyFields(byRec.get(r._recno), r));
|
|
boqUpdates.push(
|
|
isNew ? Object.assign(u, { _new: true }) : Object.assign(u, { _recno: r._recno }),
|
|
);
|
|
fresh
|
|
.filter((b) => b.parentRecno === r._recno)
|
|
.forEach((b) => boqUpdates.push(newItemRow(b)));
|
|
});
|
|
fresh.filter((b) => b.parentRecno == null).forEach((b) => boqUpdates.push(newItemRow(b)));
|
|
|
|
const prj = d.prjlst || {};
|
|
const prjUpd = { PNAME: ctx.state.projectMeta.pname, PNUM: ctx.state.projectMeta.pnum };
|
|
Object.entries(comp.stages || {}).forEach(([n, st]) => {
|
|
const g = st && st.kanjub && st.kanjub.grandTotal;
|
|
if (
|
|
g !== undefined &&
|
|
(n === "1" || num(prj[`COSTW${n}`]) > 0 || num(prj.K_ORD) >= num(n) - 1)
|
|
)
|
|
prjUpd[`COSTW${n}`] = g;
|
|
});
|
|
if (!comp.stages && comp.kanjubComputed) prjUpd.COSTW1 = comp.kanjubComputed.GRAND_TOTAL;
|
|
// 기성 공사(PCLASS 6) — 칸 2 누계 · 3 전회 · 4 금회는 기성 화면이 정함(REA 33 · ST_KanJub 0x4ca825)
|
|
const isProgress = num(prj.PCLASS) === 6;
|
|
if (isProgress)
|
|
["COSTW2", "COSTW3", "COSTW4"].forEach((k) => {
|
|
if (prj[k] !== undefined) prjUpd[k] = prj[k];
|
|
else delete prjUpd[k];
|
|
});
|
|
|
|
const smt = d.prjlst_smt || {};
|
|
const rts = (ctx.state.kanjubConfig && ctx.state.kanjubConfig.rates) || {};
|
|
const smtCand = {
|
|
"1_R_KANNO": rts.KANNO,
|
|
"1_R_SANBO": rts.SANBO,
|
|
"1_R_KOBO": rts.KOBO,
|
|
"1_R_KUNBO": rts.KUNBO,
|
|
"1_R_SLVBO": rts.SLVBO,
|
|
"1_R_YUNBO": rts.YUNBO,
|
|
"1_R_ANJUN": rts.ANJUN,
|
|
"1_R_T_KITA": rts.KITA,
|
|
"1_R_HWAN": rts.HWAN,
|
|
"1_R_EQIPSU": rts.EQIP,
|
|
"1_R_ILBAN": rts.ILBAN,
|
|
"1_R_IYUN": rts.IYUN,
|
|
"1_C_ANJUN_G": ctx.state.kanjubConfig && ctx.state.kanjubConfig.anjunBaseAdd,
|
|
};
|
|
const smtUpd = Object.fromEntries(
|
|
Object.entries(smtCand).filter(([k, v]) => k in smt && v !== undefined && v !== null),
|
|
);
|
|
// 기성 칸 제잡비 금액 [2·3·4_KANJUB] n_C_* — 기성 화면 [기성 칸 반영]이 쓴 값 그대로(서버가 바뀐 줄만 씀)
|
|
if (isProgress)
|
|
Object.entries(smt)
|
|
.filter(([k]) => /^[234]_C_/.test(k))
|
|
.forEach(([k, v]) => (smtUpd[k] = v));
|
|
return {
|
|
source_path: ctx.currentSourcePath,
|
|
target_folder_name: (ctx.state.projectMeta.pname || "stmate_edited").slice(0, 28),
|
|
boq_table: d.boq_table_name || "",
|
|
costn_updates: (d.costn_rows || []).map((r) =>
|
|
Object.fromEntries(
|
|
Object.entries(r).filter(([k]) => k === "CODE" || COSTN_SAVE_COL.test(k)),
|
|
),
|
|
),
|
|
costn_memo_updates: ctx.uiState.editedSmtMap || {},
|
|
boq_updates: boqUpdates,
|
|
boq_deleted: d.boq_deleted || ctx.uiState.boqDeleted || [],
|
|
prjlst_updates: prjUpd,
|
|
smt_updates: smtUpd,
|
|
// 서버(stc_save_extra)가 불러온 값과 견줘 바뀐 것만 씀 — 제잡비 설정 · 구성표 · 수량코드
|
|
kanjub_stages: ctx.state.kanjubStages || {},
|
|
bdqty_rows: d.bdqty_rows || null,
|
|
qcode_rows: d.qcode_rows || null,
|
|
};
|
|
}
|
|
|
|
const api = { buildSavePayload };
|
|
global.STMATE_SAVE_PAYLOAD = api;
|
|
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
})(typeof window !== "undefined" ? window : globalThis);
|