Files
wace_rps/backend-node/src/services/devBomExcelExportService.ts
T
hjjeong 7d73d2ee57 개발관리>E-BOM 조회 M4 — 정/역전개 엑셀 다운로드 신설 (wace 1:1)
운영판: structureAscendingListExcel.jsp / structureDescendingListExcel.jsp

backend (devBomService.ts):
- ascendingForExcel / descendingForExcel — 그리드용 ascending/descending 보다 풀 컬럼
  · 추가: P_QTY(bom_part_qty.item_qty), HEAT_TREATMENT_HARDNESS, HEAT_TREATMENT_METHOD,
          SURFACE_TREATMENT, MAKER, PART_TYPE_TITLE(comm_code.code_name)
- devBomExcelExportService.ts 신규 (xlsx 라이브러리)
  · 헤더: 동적 L1..LMaxLevel + 품번 / 품명 / 수량 / 항목수량 / 3D / 2D / PDF /
          재료 / 열처리경도 / 열처리방법 / 표면처리 / 메이커 / 범주 이름 / 비고 (wace 1:1)
  · LEVEL 셀: 행의 LEV 와 동일한 컬럼에 "*", 나머지는 공백
  · 3D/2D/PDF: attach_file_info count > 0 → "Y", 0 → 공백 (wace 1:1)
  · 컬럼 너비: LEVEL 4, 품번/품명/재료/MAKER 등 적절히 조정
  · 시트명: "BOM 정전개" / "BOM 역전개"
- controllers/devBomController.ts: excelAscending / excelDescending 추가
  · Content-Disposition 에 filename + filename*=UTF-8'' 둘 다 (한글 파일명 호환)
- routes/devBomRoutes.ts: /ebom-tree/ascending/excel, /ebom-tree/descending/excel

frontend:
- lib/api/devBom.ts: excelAscending/excelDescending (responseType: "blob")
  · Content-Disposition 의 filename*=UTF-8'' 우선 파싱 헬퍼 추가
- app/.../ebom-search/page.tsx: "정전개 엑셀" / "역전개 엑셀" 버튼 추가
  · 현재 검색 조건 그대로 다운로드, blob → anchor.click 으로 저장
  · 파일명: 서버 응답 헤더의 "BOM 조회(정전개)_YYYY-MM-DD_HH-mm.xlsx" 그대로 사용

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:07:27 +09:00

99 lines
4.0 KiB
TypeScript

// ============================================================
// 개발관리 M4 E-BOM 엑셀 다운로드 (정/역전개) — wace 1:1
// structureAscendingListExcel.jsp / structureDescendingListExcel.jsp
//
// 시트 구성 (wace 1:1):
// 동적 L1..LMaxLevel ("*" 표시) + 품번 / 품명 / 수량 / 항목수량(P_QTY) /
// 3D / 2D / PDF (Y/공백) / 재료 / 열처리경도 / 열처리방법 / 표면처리 / 메이커 /
// 범주 이름 / 비고
// 헤더 행: 노란색 배경 + bold (운영판 스타일)
//
// 파일명: "BOM 조회(정전개)_YYYY-MM-DD_HH-mm.xlsx" / "BOM 조회(역전개)..."
// ============================================================
import * as XLSX from "xlsx";
import { BomTreeFilter } from "./devBomService";
import { ascendingForExcel, descendingForExcel } from "./devBomService";
type ExcelDirection = "ascending" | "descending";
function pad(n: number): string { return n < 10 ? `0${n}` : String(n); }
function nowStamp(): string {
const d = new Date();
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}`;
}
function buildSheet(rows: any[], maxLevel: number): XLSX.WorkSheet {
const effectiveMax = Math.max(1, maxLevel);
// 헤더 행
const header: string[] = [];
for (let i = 1; i <= effectiveMax; i++) header.push(String(i));
header.push("품번", "품명", "수량", "항목수량", "3D", "2D", "PDF",
"재료", "열처리경도", "열처리방법", "표면처리", "메이커", "범주 이름", "비고");
const aoa: any[][] = [header];
for (const r of rows) {
const lev = Number(r.lev ?? 1);
const row: any[] = [];
for (let i = 1; i <= effectiveMax; i++) row.push(i === lev ? "*" : "");
row.push(
r.pm_part_no ?? "",
r.pm_part_name ?? "",
r.qty ?? "",
r.p_qty ?? "",
Number(r.cu01_cnt ?? 0) > 0 ? "Y" : "",
Number(r.cu02_cnt ?? 0) > 0 ? "Y" : "",
Number(r.cu03_cnt ?? 0) > 0 ? "Y" : "",
r.material ?? "",
r.heat_treatment_hardness ?? "",
r.heat_treatment_method ?? "",
r.surface_treatment ?? "",
r.maker ?? "",
r.part_type_title ?? "",
r.remark ?? "",
);
aoa.push(row);
}
const ws = XLSX.utils.aoa_to_sheet(aoa);
// 컬럼 너비 (LEVEL 컬럼은 좁게, 텍스트 컬럼은 넓게)
const cols: XLSX.ColInfo[] = [];
for (let i = 0; i < effectiveMax; i++) cols.push({ wch: 4 });
cols.push(
{ wch: 18 }, { wch: 24 }, { wch: 8 }, { wch: 10 },
{ wch: 6 }, { wch: 6 }, { wch: 6 },
{ wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 12 }, { wch: 14 }, { wch: 12 }, { wch: 16 },
);
ws["!cols"] = cols;
// 헤더 셀 노란 배경 + bold (XLSX 무료 라이브러리는 스타일 미저장. cellStyles:true 옵션과 함께 쓰면 일부만 동작)
// → 운영판 노란 배경은 시각적 효과. SheetJS Community 빌드는 스타일 무시.
// 필요 시 exceljs로 교체. 본 구현은 데이터 1:1 정확성 우선.
return ws;
}
export async function generateAscendingExcel(filter: BomTreeFilter): Promise<{ buffer: Buffer; fileName: string }> {
const { rows, max_level } = await ascendingForExcel(filter);
const ws = buildSheet(rows, max_level);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "BOM 정전개");
const buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" }) as Buffer;
return { buffer: buf, fileName: `BOM 조회(정전개)_${nowStamp()}.xlsx` };
}
export async function generateDescendingExcel(filter: BomTreeFilter): Promise<{ buffer: Buffer; fileName: string }> {
const { rows, max_level } = await descendingForExcel(filter);
const ws = buildSheet(rows, max_level);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "BOM 역전개");
const buf = XLSX.write(wb, { type: "buffer", bookType: "xlsx" }) as Buffer;
return { buffer: buf, fileName: `BOM 조회(역전개)_${nowStamp()}.xlsx` };
}
// 호환 export
export type { ExcelDirection };