/* -*- coding: utf-8 -*- */ /** * STMATE 원본 DBF / SMT / STC / JB_R 직접 읽기·쓰기 연동 컨트롤러 (db_bridge.js) */ const StmateDbBridge = (() => { const API_BASE = "http://127.0.0.1:8095"; const esc = (s) => String(s ?? "").replace( /[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c], ); const fmt = (n, maxDec = 2) => new Intl.NumberFormat("ko-KR", { maximumFractionDigits: maxDec }).format(Number(n) || 0); const dbCtx = { connected: false, sources: { projects: [], jbr_files: [], work_root: "" }, selectedProjectPath: "", selectedJbrPath: "", targetFolderName: "stmate_edited", loadedDbf: null, loadedJbr: null, lastSaveResult: null, statusMsg: "로컬 DB 브리지(127.0.0.1:8095) 연결 확인 중...", }; async function checkAndLoadSources(onUpdate) { try { const resp = await fetch(`${API_BASE}/api/sources`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); dbCtx.connected = true; dbCtx.sources = data; if (!dbCtx.selectedProjectPath && data.projects.length > 0) { dbCtx.selectedProjectPath = data.projects[0].path; } const onlyJbr = data.jbr_files.filter((f) => f.ext === ".JB_R"); if (!dbCtx.selectedJbrPath && onlyJbr.length > 0) { dbCtx.selectedJbrPath = onlyJbr[onlyJbr.length - 1].path; } dbCtx.statusMsg = `연결됨 — PC 프로젝트 DB ${data.projects.length}건 · ST_BSC 제비율 DB ${data.jbr_files.length}건 감지`; } catch (_) { dbCtx.connected = false; dbCtx.statusMsg = "오프라인 — 터미널에서 .\\venv\\Scripts\\python.exe tools\\stmate-test\\server.py 실행 시 DBF 직접 읽기/쓰기 활성화"; } if (onUpdate) onUpdate(); } async function loadProjectDbf(applyStateCallback, onUpdate) { if (!dbCtx.selectedProjectPath) return; try { dbCtx.statusMsg = `DBF/STC 읽는 중: ${dbCtx.selectedProjectPath} ...`; if (onUpdate) onUpdate(); const resp = await fetch(`${API_BASE}/api/load`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_path: dbCtx.selectedProjectPath }), }); if (!resp.ok) throw new Error(await resp.text()); const data = await resp.json(); dbCtx.loadedDbf = data; dbCtx.lastSaveResult = null; dbCtx.statusMsg = `로드 완료: ${data.prjlst?.PNAME || data.source_path} (COSTN ${data.costn_count}건, ${data.boq_table_name} ${data.boq_count}건)`; if (data.sim_state && applyStateCallback) { applyStateCallback(data.sim_state); } } catch (err) { dbCtx.statusMsg = `DBF 로드 실패: ${err.message || err}`; if (onUpdate) onUpdate(); } } async function loadAndApplyJbr(state, saveAndRender) { if (!dbCtx.selectedJbrPath) return; try { const resp = await fetch(`${API_BASE}/api/load-jbr`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jbr_path: dbCtx.selectedJbrPath }), }); if (!resp.ok) throw new Error(await resp.text()); const data = await resp.json(); dbCtx.loadedJbr = data; const r = data.extracted_rates; Object.assign(state.kanjubConfig.rates, { KANNO: r.KANNO || state.kanjubConfig.rates.KANNO, KITA: r.KITA || state.kanjubConfig.rates.KITA, ILBAN: r.ILBAN || state.kanjubConfig.rates.ILBAN, SANBO: r.SANBO, KOBO: r.KOBO, KUNBO: r.KUNBO, YUNBO: r.YUNBO, SLVBO: r.SLVBO, IYUN: r.IYUN, ANJUN: r.ANJUN, }); state.kanjubConfig.anjunBaseAdd = r.anjunBaseAdd; dbCtx.statusMsg = `ST_BSC 제비율 DB(${data.name}) 요율 적용 완료`; if (saveAndRender) saveAndRender(); } catch (err) { dbCtx.statusMsg = `JB_R 로드 실패: ${err.message || err}`; if (saveAndRender) saveAndRender(); } } async function saveToDbf(state, calcRes, onUpdate) { const srcPath = dbCtx.loadedDbf?.source_path || dbCtx.selectedProjectPath; if (!srcPath) return; try { const k = calcRes.kanjubOrig; const costnUpdates = (dbCtx.loadedDbf?.costn_rows || []).slice(0, 80).map((r) => ({ CODE: r.CODE, NAME: r.NAME, TAMT1: r.TAMT1, LAMT1: r.LAMT1, MAMT1: r.MAMT1, SAMT1: r.SAMT1, })); // 시뮬레이터 기초자원 단가 변경분도 COSTN 업데이트에 병합 for (const resItem of calcRes.resources.list) { costnUpdates.push({ CODE: resItem.code, NAME: resItem.name, TAMT1: resItem.adoptedPrice, LAMT1: resItem.kind === "노무비" ? resItem.adoptedPrice : 0, MAMT1: resItem.kind === "재료비" ? resItem.adoptedPrice : 0, SAMT1: resItem.kind === "경비" ? resItem.adoptedPrice : 0, }); } const boqUpdates = calcRes.boqOrig.rows .filter((r) => r._recno !== undefined) .map((r) => ({ _recno: r._recno, NAME1: r.name, QTY1: r.qty, MAMT1: r.unitInfo.M, LAMT1: r.unitInfo.L, SAMT1: r.unitInfo.S, TAMT1: r.unitInfo.T, })); const prjlstUpdates = { MCOST1: k.dM, LCOST1: k.dL, SCOST1: k.dS, TCOST1: k.dT, COSTW1: k.grandTotal, }; const rates = state.kanjubConfig.rates; const smtUpdates = { "1_R_KANNO": rates.KANNO, "1_R_SANBO": rates.SANBO, "1_R_KOBO": rates.KOBO, "1_R_KUNBO": rates.KUNBO, "1_R_SLVBO": rates.SLVBO, "1_R_YUNBO": rates.YUNBO, "1_R_ANJUN": rates.ANJUN, "1_R_T_KITA": rates.KITA, "1_R_HWAN": rates.HWAN, "1_R_EQIPSU": rates.EQIP, "1_R_ILBAN": rates.ILBAN, "1_R_IYUN": rates.IYUN, "1_C_KANNO": k.kanno, "1_C_ILBAN": k.ilbanAmt, "1_C_IYUN_M": k.pretaxProfitAdj, "1_C_KONGSA_J": k.contractBeforeAdj, "1_C_UPDNKWN": k.govFinal, }; const resp = await fetch(`${API_BASE}/api/save`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_path: srcPath, target_folder_name: dbCtx.targetFolderName || "stmate_edited", costn_updates: costnUpdates, boq_updates: boqUpdates, prjlst_updates: prjlstUpdates, smt_updates: smtUpdates, }), }); if (!resp.ok) throw new Error(await resp.text()); const saved = await resp.json(); dbCtx.lastSaveResult = saved; dbCtx.statusMsg = `DBF·STC 저장 완료: ${saved.saved_stc} (${fmt(saved.stc_size, 0)} bytes)`; await checkAndLoadSources(); } catch (err) { dbCtx.statusMsg = `DBF 저장 실패: ${err.message || err}`; } if (onUpdate) onUpdate(); } function renderDbTab(state, calcRes) { const projOpts = dbCtx.sources.projects .map( (p) => ``, ) .join(""); const jbrOpts = dbCtx.sources.jbr_files .map( (f) => ``, ) .join(""); let dbfPreviewHtml = '
.STC 파일을 선택하고 [원본 DBF/STC 불러오기]를 실행하세요.${esc(r.CODE)}| CODE | 품명(NAME) | 규격(SIZE) | 단위 | 재료단가(MAMT1) | 노무단가(LAMT1) | 경비단가(SAMT1) | 합계단가(TAMT1) | 채택6칸(AMT_61) |
|---|
C:\\경영정보(주) 설치 폴더의 프로젝트 DBF(COSTN.DBF, BOQ11.DBF, PRJLST.DBF/.SMT), System\\ST_BSC\\*.JB_R 제비율 기준 DB, 그리고 실무 .STC 패키지를 직접 읽고 CP949 고정폭 바이너리 DBF로 다시 기록합니다.
COSTN.DBF 레코드 직접 조회 및 편집 (상위 25건 미리보기)여기서 수정한 단가와 01~06 탭에서 조작한 수량·요율·원가 결과가 [DBF 및 .STC로 직접 쓰기] 실행 시 COSTN.DBF, BOQ11/12.DBF, PRJLST.DBF/.SMT에 기록됩니다.