/** * 기본 템플릿으로 예시 리포트 16건 생성 스크립트 * 실행: cd backend-node && node scripts/create-sample-reports.js * * - 한글 카테고리 사용 * - 작성자/작성일 다양하게 구성 * - 8가지 기본 템플릿을 활용하여 각 2건씩 총 16건 */ const http = require("http"); function apiRequest(method, path, token, body = null) { return new Promise((resolve, reject) => { const bodyStr = body ? JSON.stringify(body) : null; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }; if (bodyStr) headers["Content-Length"] = Buffer.byteLength(bodyStr); const req = http.request( { hostname: "localhost", port: 8080, path, method, headers }, (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); } }); } ); req.on("error", reject); if (bodyStr) req.write(bodyStr); req.end(); }); } async function getToken() { const body = JSON.stringify({ userId: "wace", password: "qlalfqjsgh11" }); return new Promise((resolve, reject) => { const req = http.request( { hostname: "localhost", port: 8080, path: "/api/auth/login", method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }, }, (res) => { let data = ""; res.on("data", (c) => (data += c)); res.on("end", () => { try { resolve(JSON.parse(data).data?.token); } catch (e) { reject(e); } }); } ); req.on("error", reject); req.write(body); req.end(); }); } // ─── 작성자 풀 (user_id) ─────────────────────────────────────────────────────── const AUTHORS = ["wace", "drkim", "admin"]; // ─── 작성일 풀 (다양한 날짜) ──────────────────────────────────────────────────── const DATES = [ "2025-11-15", "2025-12-03", "2025-12-22", "2026-01-08", "2026-01-19", "2026-01-28", "2026-02-05", "2026-02-14", "2026-02-21", "2026-02-28", "2026-03-01", "2026-03-03", "2026-03-05", "2026-03-07", "2026-03-08", "2026-03-10", ]; // ─── 16건 예시 리포트 정의 ────────────────────────────────────────────────────── const SAMPLE_REPORTS = [ // 견적서 x2 { reportNameKor: "표준 견적서", reportNameEng: "Standard Quotation", reportType: "견적서", description: "수신자/공급자 정보, 품목 테이블, 공급가액/세액/합계 자동 계산 포함", templateType: "QUOTATION", author: AUTHORS[0], date: DATES[15], }, { reportNameKor: "해외 견적서 (영문)", reportNameEng: "Export Quotation", reportType: "견적서", description: "해외 거래처용 영문 견적서 양식 (FOB/CIF 조건 포함)", templateType: "EXPORT_QUOTATION", author: AUTHORS[1], date: DATES[10], }, // 발주서 x2 { reportNameKor: "자재 발주서", reportNameEng: "Material Purchase Order", reportType: "발주서", description: "발주처/자사 정보, 품목 테이블, 발주조건 포함 (4단계 결재)", templateType: "PURCHASE_ORDER", author: AUTHORS[0], date: DATES[14], }, { reportNameKor: "외주 가공 발주서", reportNameEng: "Outsourcing Purchase Order", reportType: "발주서", description: "외주 협력업체 가공 의뢰용 발주서 양식", templateType: "PURCHASE_ORDER", author: AUTHORS[2], date: DATES[7], }, // 수주 확인서 x2 { reportNameKor: "수주 확인서", reportNameEng: "Sales Order Confirmation", reportType: "수주확인서", description: "발주처/자사 정보, 품목 테이블, 수주 합계금액, 납품조건 포함", templateType: "SALES_ORDER", author: AUTHORS[1], date: DATES[13], }, { reportNameKor: "긴급 수주 확인서", reportNameEng: "Urgent Sales Order Confirmation", reportType: "수주확인서", description: "긴급 납기 대응용 수주 확인서 (단납기 조건 포함)", templateType: "SALES_ORDER", author: AUTHORS[0], date: DATES[5], }, // 거래명세서 x2 { reportNameKor: "거래 명세서", reportNameEng: "Transaction Statement", reportType: "거래명세서", description: "공급자/공급받는자 정보, 품목 테이블, 합계금액, 인수 서명란 포함", templateType: "DELIVERY_NOTE", author: AUTHORS[0], date: DATES[12], }, { reportNameKor: "월별 거래 명세서", reportNameEng: "Monthly Transaction Statement", reportType: "거래명세서", description: "월간 거래 내역 합산 명세서 양식", templateType: "DELIVERY_NOTE", author: AUTHORS[2], date: DATES[3], }, // 작업지시서 x2 { reportNameKor: "생산 작업지시서", reportNameEng: "Production Work Order", reportType: "작업지시서", description: "작업지시/제품 정보, 자재 소요 내역, 작업 공정, 바코드/QR 포함", templateType: "WORK_ORDER", author: AUTHORS[1], date: DATES[11], }, { reportNameKor: "조립 작업지시서", reportNameEng: "Assembly Work Order", reportType: "작업지시서", description: "조립 라인 전용 작업지시서 (공정순서/부품목록 포함)", templateType: "WORK_ORDER", author: AUTHORS[0], date: DATES[6], }, // 검사 성적서 x2 { reportNameKor: "품질 검사 성적서", reportNameEng: "Quality Inspection Report", reportType: "검사성적서", description: "검사/제품 정보, 검사항목 테이블, 종합 판정(합격/불합격), 서명란 포함", templateType: "INSPECTION_REPORT", author: AUTHORS[0], date: DATES[9], }, { reportNameKor: "수입검사 성적서", reportNameEng: "Incoming Inspection Report", reportType: "검사성적서", description: "수입 자재/부품 품질 검사 성적서 양식", templateType: "INSPECTION_REPORT", author: AUTHORS[2], date: DATES[1], }, // 세금계산서 x2 { reportNameKor: "전자 세금계산서", reportNameEng: "Electronic Tax Invoice", reportType: "세금계산서", description: "승인번호/작성일자, 공급자/공급받는자 그리드, 품목 테이블, 결제방법 포함", templateType: "TAX_INVOICE", author: AUTHORS[1], date: DATES[8], }, { reportNameKor: "수정 세금계산서", reportNameEng: "Amended Tax Invoice", reportType: "세금계산서", description: "기존 발행 세금계산서의 수정 발행용 양식", templateType: "TAX_INVOICE", author: AUTHORS[0], date: DATES[2], }, // 생산계획 현황표 x2 { reportNameKor: "월간 생산계획 현황표", reportNameEng: "Monthly Production Plan Status", reportType: "생산현황", description: "계획/생산수량 요약 카드, 생산계획 테이블, 상태 범례, 비고 포함", templateType: "PRODUCTION_PLAN", author: AUTHORS[0], date: DATES[4], }, { reportNameKor: "주간 생산실적 현황표", reportNameEng: "Weekly Production Performance", reportType: "생산현황", description: "주간 단위 생산실적 집계 및 달성률 현황표", templateType: "PRODUCTION_PLAN", author: AUTHORS[2], date: DATES[0], }, ]; // ─── 메인 실행 ───────────────────────────────────────────────────────────────── async function main() { console.log("로그인 중..."); let token; try { token = await getToken(); console.log("로그인 성공\n"); } catch (e) { console.error("로그인 실패:", e.message); process.exit(1); } // 1. 기존 리포트 모두 삭제 console.log("기존 리포트 삭제 중..."); let allReports = []; let page = 1; while (true) { const resp = await apiRequest("GET", `/api/admin/reports?page=${page}&limit=50`, token); const items = resp.data?.items || []; if (items.length === 0) break; allReports = allReports.concat(items); if (allReports.length >= (resp.data?.total || 0)) break; page++; } console.log(` ${allReports.length}건 발견`); for (const rpt of allReports) { await apiRequest("DELETE", `/api/admin/reports/${rpt.report_id}`, token); console.log(` 삭제: ${rpt.report_name_kor}`); } // 2. 템플릿 매핑 console.log("\n템플릿 조회 중..."); const tplResp = await apiRequest("GET", "/api/admin/reports/templates", token); const allTpls = [...(tplResp.data?.system || []), ...(tplResp.data?.custom || [])]; const tplMap = {}; for (const t of allTpls) { if (!tplMap[t.template_type]) tplMap[t.template_type] = t; } console.log(` ${allTpls.length}건 발견\n`); // 3. 16건 리포트 생성 console.log("예시 리포트 16건 생성 시작..."); const createdIds = []; for (let i = 0; i < SAMPLE_REPORTS.length; i++) { const s = SAMPLE_REPORTS[i]; const tpl = tplMap[s.templateType]; const reportData = { reportNameKor: s.reportNameKor, reportNameEng: s.reportNameEng, reportType: s.reportType, description: s.description, templateId: tpl?.template_id || null, }; const result = await apiRequest("POST", "/api/admin/reports", token, reportData); if (!result.success) { console.error(` [${i + 1}] 실패: ${s.reportNameKor} - ${result.message}`); continue; } const reportId = result.data?.reportId; createdIds.push({ reportId, author: s.author, date: s.date, name: s.reportNameKor }); // 레이아웃 저장 if (tpl) { const raw = typeof tpl.layout_config === "string" ? JSON.parse(tpl.layout_config) : tpl.layout_config || {}; const components = raw.components || []; const ps = raw.pageSettings || {}; await apiRequest("PUT", `/api/admin/reports/${reportId}/layout`, token, { layoutConfig: { pages: [{ page_id: `pg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, page_name: "페이지 1", page_order: 0, width: ps.width || 210, height: ps.height || 297, orientation: ps.orientation || "portrait", margins: ps.margins || { top: 10, bottom: 10, left: 10, right: 10 }, background_color: "#ffffff", components, }], }, queries: [], menuObjids: [], }); } console.log(` [${i + 1}] ${s.reportNameKor} (${s.reportType}) - ${s.author} / ${s.date}`); } // 4. DB 직접 업데이트 (작성자 / 작성일 변경) console.log("\n작성자/작성일 DB 업데이트 중..."); for (const item of createdIds) { await apiRequest("PUT", `/api/admin/reports/${item.reportId}`, token, { // 이 API는 updateReport 이므로 직접 필드 업데이트 가능한지 확인 필요 // 그렇지 않으면 별도 SQL 필요 }); } // updateReport API로 created_by/created_at 을 변경할 수 없으므로 // 직접 DB 업데이트 스크립트를 별도 실행 console.log("\nDB 업데이트 SQL 생성..."); const sqlStatements = createdIds.map((item) => { return `UPDATE report_master SET created_by = '${item.author}', created_at = '${item.date} 09:00:00+09' WHERE report_id = '${item.reportId}';`; }); // DB 직접 접근으로 업데이트 try { const { Pool } = require("pg"); require("dotenv").config({ path: require("path").join(__dirname, "..", ".env") }); const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); for (const item of createdIds) { await pool.query( `UPDATE report_master SET created_by = $1, created_at = $2 WHERE report_id = $3`, [item.author, `${item.date} 09:00:00+09`, item.reportId] ); } await pool.end(); console.log(` ${createdIds.length}건 업데이트 완료`); } catch (e) { console.warn(" DB 직접 연결 실패, SQL을 수동으로 실행하세요:"); sqlStatements.forEach((sql) => console.log(" " + sql)); } console.log(`\n완료! ${createdIds.length}건 생성`); } main();