Files
Aislo/tools/stmate-test/scr_file.js
T
eomsangdonandClaude Opus 5.5 e987602f24 feat(stmate-test): 무손실 저장 — 손대지 않은 저장 = 원본 STC 바이트 동일 · 고친 칸만 다시 씀
- DBF 고친 칸만 다시 씀(한글 깨짐 · 2칸을 1칸으로 · 3칸 87.745% 덮어쓰기 없앰) · 단가 → 내역 행 → 소계 → 직공비 다시 셈 · 행 끼우기·지우기
- SIxMemo(COSTN.SMT · PRJLST.SMT) 블록 구조 유지 · 넘치면 뒤에 붙이고 가리키는 값 갱신
- STC 다시 묶기(같은 파일은 원본 바이트) · 검사 · 자동 백업·복원 · 새 공사 · 복사등록(차수 인계) · 해당분 압축
- BDQTY 역변환(쓰기 준비) · 모듈 dbf_rw · dbf_memo · dbf_crypt · stc_archive · stc_save · stc_read · stc_project · api_file · scr_file
- 지킴 시험 test_stmate_file_roundtrip · file_project · file_payload — 실무 STC 7건 왕복

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:42:04 +09:00

147 lines
5.5 KiB
JavaScript

/**
* STMATE w 8.0 — 파일 작업 끼워 넣기 (scr_file.js) · 공사목록표 단추와 1번 메뉴
* 복사등록(차수/단계 변환) · 해당분만 압축(.STC) · 새 공사 · STC 구조·버전 검사(Chk_STC_Ver) · 백업·복원(wM_D_Restore)
* 서버: api_file.py — 쓰기는 work_dbf/ 아래만 · 단추는 data-file-act 로 문서 전체에서 받음
*/
(function (global) {
"use strict";
const PLUG = (global.STMATE_PLUGINS = global.STMATE_PLUGINS || {
screens: {},
actions: {},
menus: [],
});
const app = () => global.STMATE_APP;
async function post(url, body) {
const res = await fetch(url, {
method: body === undefined ? "GET" : "POST",
headers: { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
const curPath = (ctx) => {
if (!ctx.currentSourcePath) throw new Error("먼저 프로젝트를 불러오세요.");
return ctx.currentSourcePath;
};
const pickedPcode = (ctx) => {
const el = document.querySelector('input[name="prj-pcode"]:checked');
const pc = el ? el.value : ctx.uiState.filePcode || "11";
ctx.uiState.filePcode = pc;
return pc;
};
async function openSaved(path) {
const a = app();
const src = await post("/api/sources");
if (src && src.projects) a.ctx.sourcesData = src;
const sel = document.getElementById("top-project-select");
if (sel && src && src.projects)
sel.innerHTML = src.projects
.map((p) => `<option value="${p.path}">${p.name}</option>`)
.join("");
await a.loadProjectFromServer(path, "wPrjList");
}
const ACTS = {
async check(ctx) {
const r = await post("/api/file/check", { source_path: curPath(ctx) });
const t = Object.entries(r.tables || {})
.map(([n, h]) => `${n} v${h.ver.toString(16)} ${h.nfield}칸 ${h.nrec}행`)
.join("\n");
alert(
`[STC 검사] ${r.ok ? "이상 없음" : "문제 있음"} · ${r.edition}\n${(r.problems || []).join("\n")}\n\n${t}`,
);
},
async pack(ctx) {
const pc = pickedPcode(ctx);
const name = prompt(
`BOQ${pc} 공사만 담은 STC 이름:`,
`${ctx.state.projectMeta.pname || "공사"}_${pc}`,
);
if (!name) return;
const r = await post("/api/file/pack", {
source_path: curPath(ctx),
pcode: pc,
target_name: name,
});
alert(`[해당분만 압축] ${r.saved_stc} (${r.stc_size}B)`);
},
async copy(ctx) {
const pc = pickedPcode(ctx);
const mode = prompt(
`BOQ${pc} 복사등록 방식 — copy(그대로) · change(변경 차수 변환) · contract(계약 변환):`,
"change",
);
if (!mode) return;
const body = { source_path: curPath(ctx), src_pcode: pc, mode: mode.trim() };
if (body.mode === "contract") {
const r = prompt("단가 적용율 % — 재료,노무,경비:", "87.745,87.745,87.745");
if (!r) return;
const [M, L, S] = r.split(",").map((x) => parseFloat(x) || 0);
body.rates = { M, L, S };
}
const res = await post("/api/file/copy-register", body);
alert(`[복사등록] 새 공사 BOQ${res.new_pcode} · ${res.saved_dir}`);
await openSaved(res.saved_dir);
},
async new(ctx) {
const pname = prompt("새 공사명:", "");
if (!pname) return;
const res = await post("/api/file/new", { template_path: curPath(ctx), pname });
alert(`[새 공사] 틀(요율·서식·설정)은 지금 공사에서, 내역·단가는 빈 채로 · ${res.saved_dir}`);
await openSaved(res.saved_dir);
},
async backups(ctx) {
const nm = (ctx.state.projectMeta.pname || "").slice(0, 28);
const r = await post(`/api/file/backups?name=${encodeURIComponent(nm)}`);
const list = r.backups || [];
if (!list.length) return alert("[백업] 이 공사 이름의 작업본 백업이 없습니다.");
const pick = prompt(
`[백업 ${list.length}벌] 되돌릴 번호(취소 = 닫기):\n` +
list.map((b, i) => `${i + 1}. ${b.stamp} (${b.size}B)`).join("\n"),
"1",
);
const b = list[parseInt(pick, 10) - 1];
if (!b) return;
const res = await post("/api/file/restore", {
backup_path: b.path,
target_folder_name: b.name,
});
await openSaved(res.saved_dir);
},
};
async function run(act) {
try {
await ACTS[act](app().ctx);
} catch (err) {
alert("파일 작업 실패: " + err.message);
}
}
document.addEventListener("click", (e) => {
const el = e.target && e.target.closest && e.target.closest("[data-file-act]");
if (!el || !ACTS[el.getAttribute("data-file-act")]) return;
e.preventDefault();
run(el.getAttribute("data-file-act"));
});
document.addEventListener("change", (e) => {
if (e.target && e.target.name === "prj-pcode") app().ctx.uiState.filePcode = e.target.value;
});
[
["Act_FileCopyReg", "복사등록 — 차수/단계 변환", "copy"],
["Act_FilePack", "해당분만 압축하기 (.STC)", "pack"],
["Act_FileNew", "새 공사 만들기", "new"],
["Act_FileCheck", "STC 구조·버전 검사 (Chk_STC_Ver)", "check"],
["Act_FileRestore", "작업본 백업·복원 (wM_D_Restore)", "backups"],
].forEach(([act, label, key]) => {
PLUG.actions[act] = () => run(key);
PLUG.menus.push({ menu: "m1", label, act });
});
})(window);