// 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 }); }