feat(est-test): 원가계산서 사용자 양식 등록·목록·열기
- 서버 보관함 data/forms.json 에 원가계산서 양식 등록·삭제·목록 - 양식 열기·파일 내려받기/불러오기, 금액 제외하고 열 때 다시 계산 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// 원본 원가계산서 양식(TFRMWONGABASE popupSaveAs·popupFormats·popupLoadFrom)과 양식목록(TFRMFORMATFILELIST)
|
||||
// — 현재 원가 표를 이름 붙여 서버 보관함(data/forms.json)에 등록하고, 목록에서 열기·삭제, 파일로 내려받기·불러오기.
|
||||
const EstFormsLibrary = (() => {
|
||||
// 원가 표 양식으로 남길 필드 — 금액(Cost)은 계산 결과라 남기지 않는다.
|
||||
function strip(rows) {
|
||||
return rows.map((row) => {
|
||||
const { Cost, ...rest } = row;
|
||||
return rest;
|
||||
});
|
||||
}
|
||||
function validRows(rows) {
|
||||
if (!Array.isArray(rows) || !rows.length) throw Error("원가계산서 양식에 행이 없습니다.");
|
||||
if (rows.some((row) => !row || typeof row !== "object" || Array.isArray(row)))
|
||||
throw Error("원가계산서 양식 행이 올바르지 않습니다.");
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function list() {
|
||||
return (await EstApp.api("/api/forms")).filter((f) => f.kind === "wonga");
|
||||
}
|
||||
async function show(state) {
|
||||
const { esc } = EstApp,
|
||||
forms = await list();
|
||||
EstApp.showDialog(
|
||||
"원가계산서 양식 목록",
|
||||
`<p>현재 원가계산서의 항목·수식·요율을 양식으로 등록하고, 다른 공사에서 열어 씁니다. 금액은 열 때 다시 계산합니다.</p><button data-action="wonga-form-register">현재 원가계산서 등록</button> <button data-action="wonga-form-export">파일로 내려받기</button> <button data-action="wonga-form-import">파일에서 불러오기</button><table class="sheet" style="margin-top:8px"><thead><tr><th>양식 이름</th><th>행</th><th>등록</th><th>작업</th></tr></thead><tbody>${forms.map((f) => `<tr><td>${esc(f.name)}</td><td>${f.data.length}</td><td>${esc(f.saved.slice(0, 10))}</td><td><button data-action="wonga-form-open" data-name="${esc(f.name)}">열기</button><button data-action="wonga-form-delete" data-name="${esc(f.name)}">삭제</button></td></tr>`).join("") || '<tr><td colspan="4">등록된 양식이 없습니다.</td></tr>'}</tbody></table>`,
|
||||
);
|
||||
state.formsLibrary = forms;
|
||||
}
|
||||
function apply(state, rows, name) {
|
||||
validRows(rows);
|
||||
if (!confirm(`현재 원가계산서를 「${name}」 양식으로 바꾸시겠습니까?`)) return;
|
||||
EstApp.byId("dialog").close();
|
||||
// 도급액 고정은 저장된 금액을 그대로 쓰므로 금액 없는 양식에서는 풀어야 다시 계산된다(기초 뼈대와 같음).
|
||||
const fixed = state.project.options.TotalFix;
|
||||
EstApp.change((project) => {
|
||||
project.options.TotalFix = false;
|
||||
project.tables.Wonga = structuredClone(rows);
|
||||
});
|
||||
state.notice = `원가계산서를 「${name}」 양식으로 바꿨습니다.${fixed ? " 도급액 고정은 풀었으니 필요하면 다시 맞추세요." : ""}`;
|
||||
}
|
||||
|
||||
async function run(name, state, node) {
|
||||
if (name === "wonga-forms") await show(state);
|
||||
else if (name === "wonga-form-register") {
|
||||
const title = prompt("양식 이름", state.project.name || "원가계산서");
|
||||
if (!title) return;
|
||||
const data = strip(validRows(state.project.tables.Wonga));
|
||||
await EstApp.api("/api/forms", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kind: "wonga", name: title, data }),
|
||||
});
|
||||
await show(state);
|
||||
} else if (name === "wonga-form-open") {
|
||||
const form = state.formsLibrary?.find((f) => f.name === node.dataset.name);
|
||||
if (!form) throw Error("양식을 찾을 수 없습니다. 목록을 다시 여세요.");
|
||||
apply(state, form.data, form.name);
|
||||
} else if (name === "wonga-form-delete") {
|
||||
if (!confirm(`「${node.dataset.name}」 양식을 지우시겠습니까?`)) return;
|
||||
await EstApp.api("/api/forms/delete", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kind: "wonga", name: node.dataset.name }),
|
||||
});
|
||||
await show(state);
|
||||
} else if (name === "wonga-form-export") {
|
||||
const data = strip(validRows(state.project.tables.Wonga));
|
||||
EstApp.download(
|
||||
`${state.project.name || "원가계산서"}.wonga.json`,
|
||||
JSON.stringify({ kind: "est-web-wonga-1", rows: data }, null, 2),
|
||||
);
|
||||
} else if (name === "wonga-form-import") {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".json";
|
||||
input.addEventListener("change", async () => {
|
||||
try {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
const parsed = JSON.parse(await file.text());
|
||||
if (parsed?.kind !== "est-web-wonga-1") throw Error("원가계산서 양식 파일이 아닙니다.");
|
||||
apply(state, parsed.rows, file.name);
|
||||
} catch (error) {
|
||||
state.notice = error.message;
|
||||
}
|
||||
EstApp.render();
|
||||
});
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
const names = new Set([
|
||||
"wonga-forms",
|
||||
"wonga-form-register",
|
||||
"wonga-form-open",
|
||||
"wonga-form-delete",
|
||||
"wonga-form-export",
|
||||
"wonga-form-import",
|
||||
]);
|
||||
// 동작 사슬은 동기 — 이름만 먼저 가리고 서버 요청은 뒤에서 돌린다.
|
||||
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 { strip, validRows, handle };
|
||||
})();
|
||||
Reference in New Issue
Block a user