/** * 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);