- 원본처럼 공식 명칭·본문 목록 편집, 단가산출에 본문 줄 넣기 - 옛 EST Plus mach_Text.dat 구공식변환 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
165 lines
6.9 KiB
JavaScript
165 lines
6.9 KiB
JavaScript
// 원본 기계경비 공식(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(
|
|
"기계경비 공식",
|
|
`<div style="max-height:380px;overflow:auto">${list
|
|
.map(
|
|
(f, i) =>
|
|
`<div class="form-fields"><label>No ${i + 1} 명칭<input name="mf-name" data-i="${i}" value="${esc(f.name)}"></label><label>공식<textarea name="mf-body" data-i="${i}" rows="4" style="width:100%">${esc(f.body)}</textarea></label><button data-action="mach-formula-insert" data-i="${i}">이 위에 삽입</button> <button data-action="mach-formula-delete" data-i="${i}">삭제</button>${cost ? ` <button data-action="mach-formula-use" data-i="${i}">단가산출에 불러오기</button>` : ""}</div>`,
|
|
)
|
|
.join(
|
|
"<hr>",
|
|
)}</div><button data-action="mach-formula-append">추가</button> <button data-action="mach-formula-old">구공식변환(옛 mach_Text.dat)</button> <button data-action="mach-formula-save">저장</button>`,
|
|
);
|
|
}
|
|
function read(state) {
|
|
for (const node of document.querySelectorAll('#dialog-content [name="mf-name"]'))
|
|
state.machFormulas[Number(node.dataset.i)].name = node.value.trim();
|
|
for (const node of document.querySelectorAll('#dialog-content [name="mf-body"]'))
|
|
state.machFormulas[Number(node.dataset.i)].body = node.value;
|
|
}
|
|
function unique(list) {
|
|
const seen = new Set();
|
|
for (const f of list) {
|
|
if (!f.name) throw Error("공식 명칭을 입력하세요.");
|
|
if (seen.has(f.name)) throw Error(`같은 명칭이 있습니다: ${f.name}`);
|
|
seen.add(f.name);
|
|
}
|
|
}
|
|
|
|
async function run(name, state, node) {
|
|
const i = Number(node?.dataset.i);
|
|
if (name === "mach-formula") return editor(state);
|
|
read(state);
|
|
const list = state.machFormulas;
|
|
if (name === "mach-formula-append") list.push({ name: `새 공식 ${list.length + 1}`, body: "" });
|
|
else if (name === "mach-formula-insert")
|
|
list.splice(i, 0, { name: `새 공식 ${list.length + 1}`, body: "" });
|
|
else if (name === "mach-formula-delete") list.splice(i, 1);
|
|
else if (name === "mach-formula-save") {
|
|
unique(list);
|
|
await save(list);
|
|
state.notice = `기계경비 공식 ${list.length}개를 저장했습니다.`;
|
|
} else if (name === "mach-formula-use") {
|
|
const at = [...state.selection].sort((a, b) => a - b).at(-1);
|
|
let count;
|
|
EstApp.change((project) => (count = insertInto(project, at, list[i].body)));
|
|
EstApp.byId("dialog").close();
|
|
state.notice = `「${list[i].name}」 공식 ${count}줄을 넣었습니다.`;
|
|
return;
|
|
} else if (name === "mach-formula-old") {
|
|
const picker = document.createElement("input");
|
|
picker.type = "file";
|
|
picker.accept = ".dat";
|
|
picker.addEventListener("change", async () => {
|
|
try {
|
|
const file = picker.files[0];
|
|
if (!file) return;
|
|
for (const f of parseOld(new Uint8Array(await file.arrayBuffer())))
|
|
list.push({ name: f.name, body: f.body });
|
|
draw(state);
|
|
} catch (error) {
|
|
state.notice = error.message;
|
|
EstApp.render();
|
|
}
|
|
});
|
|
picker.click();
|
|
return;
|
|
}
|
|
draw(state);
|
|
}
|
|
const names = new Set([
|
|
"mach-formula",
|
|
"mach-formula-append",
|
|
"mach-formula-insert",
|
|
"mach-formula-delete",
|
|
"mach-formula-save",
|
|
"mach-formula-use",
|
|
"mach-formula-old",
|
|
]);
|
|
function handle(name, state, node) {
|
|
if (!names.has(name)) return false;
|
|
run(name, state, node).catch((error) => {
|
|
state.notice = error.message;
|
|
EstApp.render();
|
|
});
|
|
return true;
|
|
}
|
|
|
|
return { parseOld, lines, insertInto, handle };
|
|
})();
|