Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
153 lines
5.3 KiB
JavaScript
153 lines
5.3 KiB
JavaScript
// 302-3 인쇄 미리보기 수량산출 — 화면 코드를 node 로 묶어 모으기 결과와 B08 화면 표 짜기를 나란히 냄.
|
||
// node helper_302_3_print_quantity.mjs <저장소 뿌리>
|
||
// 표준입력 = { project, responses: {"/quantity/…": 응답}, image_b64, drafts: [[이름, 값]], screen }
|
||
// (screen = false → 화면 표 짜기 생략 · 응답을 일부러 뺀 실패 견본)
|
||
// 표준출력 끝 줄 = { sheets: [모으기 쪽 요약], screen: {B08 화면 표 짜기 요약}, posts: [hydro/calc 본문] }
|
||
import fs from "node:fs";
|
||
import os from "node:os";
|
||
import path from "node:path";
|
||
import { pathToFileURL } from "node:url";
|
||
|
||
const root = path.resolve(process.argv[2] ?? ".");
|
||
const { build } = await import(
|
||
pathToFileURL(path.join(root, "config/node_modules/vite/dist/node/index.js")).href
|
||
);
|
||
const input = JSON.parse(fs.readFileSync(0, "utf-8"));
|
||
|
||
// 화면 · 세션만 흉내 — 이 길은 DOM 을 안 그림
|
||
class Node {
|
||
constructor() {
|
||
this.children = [];
|
||
this.classList = { toggle() {}, add() {}, remove() {}, contains: () => false };
|
||
this.style = {};
|
||
this.dataset = {};
|
||
}
|
||
append(...kids) {
|
||
this.children.push(...kids);
|
||
}
|
||
appendChild(kid) {
|
||
this.children.push(kid);
|
||
return kid;
|
||
}
|
||
addEventListener() {}
|
||
setAttribute() {}
|
||
}
|
||
const session = new Map();
|
||
const storage = {
|
||
getItem: (k) => (session.has(k) ? session.get(k) : null),
|
||
setItem: (k, v) => session.set(k, String(v)),
|
||
removeItem: (k) => session.delete(k),
|
||
};
|
||
globalThis.document = {
|
||
createElement: () => new Node(),
|
||
createElementNS: () => new Node(),
|
||
createTextNode: () => new Node(),
|
||
head: new Node(),
|
||
body: new Node(),
|
||
addEventListener() {},
|
||
documentElement: new Node(),
|
||
};
|
||
globalThis.window = {
|
||
sessionStorage: storage,
|
||
localStorage: storage,
|
||
location: { origin: "http://localhost", hash: "" },
|
||
addEventListener() {},
|
||
setTimeout: (...a) => setTimeout(...a),
|
||
clearTimeout: (id) => clearTimeout(id),
|
||
};
|
||
globalThis.sessionStorage = storage;
|
||
globalThis.localStorage = storage;
|
||
|
||
const posts = [];
|
||
const png = Buffer.from(input.image_b64, "base64");
|
||
globalThis.fetch = async (url, init = {}) => {
|
||
const at = String(url)
|
||
.replace(/^.*\/projects\/[^/]+/, "")
|
||
.split("?")[0];
|
||
if (at === "/quantity/hydro/calc") posts.push(JSON.parse(init.body));
|
||
if (at === "/quantity/milestone/image")
|
||
return new Response(png, { status: 200, headers: { "Content-Type": "image/png" } });
|
||
const body = input.responses[at];
|
||
return body
|
||
? new Response(JSON.stringify(body), {
|
||
status: 200,
|
||
headers: { "Content-Type": "application/json" },
|
||
})
|
||
: new Response(JSON.stringify({ detail: `없음 ${at}` }), { status: 404 });
|
||
};
|
||
|
||
/** 표 요약 — 열 머리(묶음 › 이름) · 줄 · 합계 */
|
||
const shape = (t) => ({
|
||
heads: t.columns.map((c) => [...(c.group ?? []), c.label].join("›")),
|
||
rows: t.rows,
|
||
total: t.total ?? null,
|
||
mergeSame: t.mergeSame ?? null,
|
||
frozen: t.frozen ?? 0,
|
||
});
|
||
|
||
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "aislo-302-3-"));
|
||
try {
|
||
await build({
|
||
configFile: false,
|
||
logLevel: "silent",
|
||
root,
|
||
resolve: {
|
||
alias: {
|
||
"@ui": path.join(root, "ui_template"),
|
||
"@config": path.join(root, "config"),
|
||
"@util": path.join(root, "common_util"),
|
||
},
|
||
},
|
||
build: {
|
||
outDir,
|
||
minify: false,
|
||
target: "es2022",
|
||
lib: {
|
||
entry: path.join(root, "resources/tester/helper_302_3_print_quantity_entry.ts"),
|
||
formats: ["es"],
|
||
fileName: "entry",
|
||
},
|
||
},
|
||
});
|
||
const mod = await import(pathToFileURL(path.join(outDir, "entry.js")).href);
|
||
const p = input.project;
|
||
for (const [name, value] of input.drafts ?? []) mod.writeState(name, value, p);
|
||
|
||
const sheets = (await mod.collectQuantitySheets(p)).map((s) => ({
|
||
id: s.id,
|
||
name: s.name,
|
||
orientation: s.orientation ?? null,
|
||
images: s.images ?? [],
|
||
...shape(s.table),
|
||
}));
|
||
|
||
if (input.screen === false) {
|
||
console.log(JSON.stringify({ sheets, screen: null, posts }));
|
||
process.exit(0);
|
||
}
|
||
// B08 화면이 부르는 같은 함수 — 같은 응답 · 같은 캐시
|
||
const screen = {};
|
||
screen.summary = shape(mod.summaryTable(await mod.loadSummary(p)).options);
|
||
const m = await mod.loadMaterials(p);
|
||
const ms = m.calc(m.payload, m.overrides);
|
||
for (const id of ["material", "concrete", "quantity"])
|
||
screen[`material:${id}`] = shape(mod.materialOptions(mod.materialTable(id, ms, m.overrides)));
|
||
const { table, interval } = await mod.loadEarthwork(p);
|
||
screen["earthwork:summary"] = shape(mod.earthworkSummaryOptions(table));
|
||
screen["earthwork:sheet"] = shape(mod.earthworkSheetOptions(table, interval));
|
||
for (const id of ["haul", "free"]) {
|
||
const { groups, caption } = mod.HAUL_SHEETS[id](table);
|
||
const { options, averages } = mod.haulOptions(groups, caption);
|
||
screen[`earthwork:${id}`] = { ...shape(options), averages };
|
||
}
|
||
screen.earthworkNames = mod.EARTHWORK_SHEETS.map((s) => s.label);
|
||
const h = await mod.loadHydro(p);
|
||
screen.hydroInputs = h.sheet.stations.map((st) => mod.hydroInputs(h.sheet, st, h.drafts));
|
||
screen.milestone = shape(
|
||
mod.milestoneTable(mod.liveMilestone(p), mod.siteInfo()?.mid_default_m ?? null).options,
|
||
);
|
||
console.log(JSON.stringify({ sheets, screen, posts }));
|
||
} finally {
|
||
fs.rmSync(outDir, { recursive: true, force: true });
|
||
}
|