fix: 엑셀 내보내기 구조 개선 - 파이프 카테고리 우선 적용
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user