fix: 엑셀 내보내기 구조 개선 - 파이프 카테고리 우선 적용
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 모든 카테고리에서 '단위' 컬럼 제거 (수량만 사용)
- 파이프 카테고리: 납기일이 정확히 P열에 위치하도록 수정
- 파이프 전용 컬럼 구조: 크기, 스케줄, 재질, 제조방법, 사용자요구, 추가요청사항, 관리항목1~4
- createExcelBlob 함수에서 ExcelJS → XLSX 변경으로 오류 해결
- 백엔드 EXCEL_DIR 경로 수정 (exports → uploads/excel_exports)
- BOM에서 생성한 엑셀을 구매관리에서 동일하게 다운로드 가능

배포 버전: index-c08dc565.js
다음 단계: 피팅, 플랜지 카테고리 엑셀 구조 개선
This commit is contained in:
hyungi
2025-10-16 15:13:42 +09:00
parent a5bfeec9aa
commit 379af6b1e3

View File

@@ -730,6 +730,8 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
'단가': 1 // E열: 일괄 1로 설정
};
// 모든 카테고리에서 단위 컬럼 제거 (수량만 사용)
// F~O열: 카테고리별 전용 컬럼 구성 (10개 컬럼)
if (category === 'PIPE') {
// 파이프 전용 컬럼 (F~O) - 끝단처리, 압력등급 제거
@@ -1062,70 +1064,63 @@ export const exportComparisonToExcel = (comparisonData, filename, additionalInfo
// 엑셀 Blob 생성 함수 (서버 업로드용)
export const createExcelBlob = async (materials, filename, options = {}) => {
try {
// 기존 exportMaterialsToExcel과 동일한 로직이지만 Blob만 반환
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Materials');
// 헤더 설정
const headers = [
'TAGNO', '품목명', '수량', '통화구분', '단가'
];
// 카테고리별 추가 헤더
if (options.category === 'PIPE') {
headers.push('크기', '사용자요구', '추가요사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4', '납기일(YYYY-MM-DD)');
} else if (options.category === 'FITTING') {
headers.push('크기', '압력등급', '스케줄', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4', '납기일(YYYY-MM-DD)');
} else if (options.category === 'FLANGE') {
headers.push('크기', '페이싱', '압력등급', '스케줄', '재질', '추가요구사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4', '납기일(YYYY-MM-DD)');
} else {
// 기본 헤더
headers.push('크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4', '납기일(YYYY-MM-DD)');
}
// 헤더 추가
worksheet.addRow(headers);
// 헤더 스타일링
const headerRow = worksheet.getRow(1);
headerRow.font = { bold: true, color: { argb: 'FFFFFFFF' } };
headerRow.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF366092' }
console.log('📊 createExcelBlob 시작:', materials.length, '개 자료');
// 기존 exportMaterialsToExcel 로직을 사용하되 Blob만 반환
const formattedData = materials.map(material => formatMaterialForExcel(material, options.category));
// 헤더 추출 및 순서 정의 (모든 카테고리에서 단위 제거)
const allHeaders = Array.from(new Set(formattedData.flatMap(Object.keys)));
const fixedHeaders = ['TAGNO', '품목명', '수량', '통화구분', '단가'];
const categorySpecificHeadersOrder = {
'PIPE': ['크기', '스케줄', '재질', '제조방법', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'FITTING': ['크기', '압력등급', '스케줄', '재질', '사용자요구', '추가요청사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'FLANGE': ['크기', '페이싱', '압력등급', '스케줄', '재질', '추가요사항', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'VALVE': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'GASKET': ['크기', '압력등급', '구조', '재질', '두께', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'BOLT': ['크기', '압력등급', '길이', '재질', '추가요구', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
'SUPPORT': ['크기', '압력등급', '재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3', '관리항목4'],
};
headerRow.alignment = { horizontal: 'center', vertical: 'middle' };
// 데이터 추가
materials.forEach(material => {
const formattedData = formatMaterialForExcel(material, options.category || 'UNKNOWN');
const rowData = headers.map(header => {
const key = Object.keys(formattedData).find(k =>
formattedData[k] !== undefined &&
(header.includes(k) || k.includes(header.replace(/[()]/g, '').split(' ')[0]))
);
return key ? formattedData[key] : '';
const deliveryDateHeader = '납기일(YYYY-MM-DD)';
let orderedHeaders = [...fixedHeaders];
if (categorySpecificHeadersOrder[options.category]) {
orderedHeaders = orderedHeaders.concat(categorySpecificHeadersOrder[options.category]);
}
orderedHeaders.push(deliveryDateHeader);
// 데이터 정렬
const finalData = formattedData.map(row => {
const newRow = {};
orderedHeaders.forEach(header => {
newRow[header] = row[header] !== undefined ? row[header] : '';
});
worksheet.addRow(rowData);
return newRow;
});
// XLSX 워크북 생성
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(finalData, { header: orderedHeaders });
// 컬럼 너비 자동 조정
worksheet.columns.forEach(column => {
let maxLength = 0;
column.eachCell({ includeEmpty: true }, (cell) => {
const columnLength = cell.value ? cell.value.toString().length : 10;
if (columnLength > maxLength) {
maxLength = columnLength;
}
});
column.width = Math.min(Math.max(maxLength + 2, 10), 50);
});
const colWidths = orderedHeaders.map(header => ({
wch: Math.max(
header.toString().length,
...finalData.map(row => (row[header] ? row[header].toString().length : 0))
) + 2
}));
worksheet['!cols'] = colWidths;
XLSX.utils.book_append_sheet(workbook, worksheet, options.category || 'Materials');
// Blob 생성
const excelBuffer = await workbook.xlsx.writeBuffer();
return new Blob([excelBuffer], {
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
console.log('✅ createExcelBlob 완료:', blob.size, 'bytes');
return blob;
} catch (error) {
console.error('엑셀 Blob 생성 실패:', error);