// 원본 기계경비 공식(TfrmMachGongSic 0xf146f0, 전역 파일 System\MainSys\MachText.Dat: Index·Name·BigoMemo)과 // 기계경비공식불러오기(TfrmMachText 0xd3d58c: 고른 공식 본문을 줄마다 단가산출 LineData 행으로 현재 행 뒤에 넣음, 계산하지 않음). // 웹은 공식 모음을 서버 양식 보관함(종류 machformula)에 둔다. 번호는 늘 1..N. // 구공식변환(숨김 단추 0xda5c18 → 0xd90c60): 옛 EST Plus mach_Text.dat 394바이트 레코드 — +5 정수 > 0 이면 새 공식 머리 // (이름 = +0x16 짧은 문자열 + +0x49 짧은 문자열), 아니면 본문 줄(앞글+뒤글+CR). // 원본이 기계경비 연결 때 덧붙이는 요약 4줄(노무비·재료비·경비·소계)은 형식 확인 전이라 넣지 않는다. const EstMachFormula = (() => { function parseOld(bytes) { const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); if (!view.length || view.length % 394) throw Error("구공식 파일 크기가 394바이트 단위가 아닙니다."); const decoder = new TextDecoder("euc-kr"), data = new DataView(view.buffer, view.byteOffset, view.byteLength); const short = (at, max) => decoder.decode(view.subarray(at + 1, at + 1 + Math.min(view[at], max))); const out = []; let current = null; for (let at = 0; at < view.length; at += 394) { const head = data.getInt32(at + 5, true), text = short(at + 0x16, 50) + short(at + 0x49, 255); if (head > 0) { current = { name: text.slice(0, 100), body: "" }; out.push(current); } else if (current) current.body += `${text}\r`; } return out.map((f, i) => ({ ...f, index: i + 1 })); } function lines(body) { return String(body ?? "") .split(/\r\n|\r|\n/) .filter((line, i, all) => line !== "" || i < all.length - 1); } function insertInto(project, at, body) { const rows = project.tables.CostMainDat; if (!rows?.[at]) throw Error("단가산출에서 넣을 위치(행)를 고르세요."); const add = lines(body); if (!add.length) throw Error("공식 본문이 비어 있습니다."); let pk = Math.max(0, ...rows.map((r) => Number(r.PkID) || 0)); rows.splice(at + 1, 0, ...add.map((LineData) => ({ PkID: ++pk, Index: 0, LineData }))); return add.length; } // 화면 async function load() { return (await EstApp.api("/api/forms")) .filter((f) => f.kind === "machformula") .sort((a, b) => (a.data.index ?? 0) - (b.data.index ?? 0)); } async function save(list) { const old = await load(); for (const f of old) if (!list.some((n) => n.name === f.name)) await EstApp.api("/api/forms/delete", post({ kind: "machformula", name: f.name })); for (const [i, f] of list.entries()) await EstApp.api( "/api/forms", post({ kind: "machformula", name: f.name, data: { index: i + 1, body: f.body } }), ); } const post = (body) => ({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); async function editor(state) { const list = (await load()).map((f) => ({ name: f.name, body: f.data.body })); state.machFormulas = list; draw(state); } function draw(state) { const { esc } = EstApp, list = state.machFormulas, cost = state.view === "CostMainDat"; EstApp.showDialog( "기계경비 공식", `