fix: 구매신청 엑셀 수량 표시 개선 및 FLANGE 품목명 개선
- 구매신청 관리 페이지 수량을 정수로 표시 (3.000 EA → 3 EA) - JSON 저장 시 수량 정수 변환 - FLANGE 품목명 세분화: WN RF, SO RF, ORIFICE FLANGE, SPECTACLE BLIND 등 - 구매관리 페이지 엑셀 다운로드 데이터 구조 개선 - 디버그 로그 추가
This commit is contained in:
@@ -53,14 +53,19 @@ const PurchaseRequestPage = ({ onNavigate, fileId, jobNo, selectedProject }) =>
|
||||
|
||||
const handleDownloadExcel = async (requestId, requestNo) => {
|
||||
try {
|
||||
console.log('📥 엑셀 다운로드 시작:', requestId, requestNo);
|
||||
// 서버에서 자재 데이터 가져오기
|
||||
const response = await api.get(`/purchase-request/${requestId}/download-excel`);
|
||||
console.log('📦 서버 응답:', response.data);
|
||||
|
||||
if (response.data.success) {
|
||||
const materials = response.data.materials;
|
||||
const groupedMaterials = response.data.grouped_materials || [];
|
||||
const jobNo = response.data.job_no;
|
||||
|
||||
console.log('📊 materials:', materials.length, '개');
|
||||
console.log('📊 groupedMaterials:', groupedMaterials.length, '개');
|
||||
|
||||
// 사용자 요구사항 매핑
|
||||
const userRequirements = {};
|
||||
materials.forEach(material => {
|
||||
@@ -69,26 +74,64 @@ const PurchaseRequestPage = ({ onNavigate, fileId, jobNo, selectedProject }) =>
|
||||
}
|
||||
});
|
||||
|
||||
// 그룹화된 자재가 있으면 그것을 사용, 없으면 원본 자재 사용
|
||||
const dataToExport = groupedMaterials.length > 0 ? groupedMaterials : materials;
|
||||
// 개별 자재 사용 (BOM 페이지와 동일하게)
|
||||
// groupedMaterials는 일부 자재가 누락될 수 있으므로 materials 사용
|
||||
const dataToExport = materials;
|
||||
|
||||
// 파일명 생성
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
const fileName = `${jobNo}_${requestNo}_${timestamp}.xlsx`;
|
||||
|
||||
// 기존 엑셀 유틸리티 사용하여 엑셀 생성
|
||||
// 그룹화된 데이터와 사용자 요구사항 전달
|
||||
exportMaterialsToExcel(dataToExport, fileName, {
|
||||
console.log('📄 내보낼 데이터:', dataToExport.length, '개');
|
||||
console.log('📄 첫 번째 자재:', dataToExport[0]);
|
||||
|
||||
// materials 데이터를 BOM 형식으로 변환
|
||||
const materialsForExport = materials.map(m => {
|
||||
// description에서 품목명 추출 (NIPPLE, ELBOW, TEE 등)
|
||||
const desc = m.description || '';
|
||||
const category = m.category || 'UNKNOWN';
|
||||
|
||||
return {
|
||||
...m,
|
||||
classified_category: category,
|
||||
original_description: desc,
|
||||
size_spec: m.size,
|
||||
size_inch: m.size,
|
||||
material_grade: m.material_grade,
|
||||
full_material_grade: m.material_grade,
|
||||
schedule: m.schedule,
|
||||
quantity: m.quantity,
|
||||
unit: m.unit,
|
||||
// FITTING의 경우 타입 정보 추가
|
||||
fitting_details: category === 'FITTING' ? {
|
||||
fitting_type: desc.includes('NIPPLE') ? 'NIPPLE' :
|
||||
desc.includes('ELBOW') ? 'ELBOW' :
|
||||
desc.includes('TEE') ? 'TEE' :
|
||||
desc.includes('REDUCER') ? 'REDUCER' :
|
||||
desc.includes('CAP') ? 'CAP' :
|
||||
desc.includes('PLUG') ? 'PLUG' :
|
||||
desc.includes('COUPLING') ? 'COUPLING' : 'FITTING'
|
||||
} : undefined
|
||||
};
|
||||
});
|
||||
|
||||
console.log('🔄 BOM 형식으로 변환 완료:', materialsForExport.length, '개');
|
||||
|
||||
// 기존 엑셀 유틸리티 사용
|
||||
exportMaterialsToExcel(materialsForExport, fileName, {
|
||||
jobNo,
|
||||
requestNo,
|
||||
userRequirements
|
||||
});
|
||||
|
||||
console.log('✅ 엑셀 내보내기 완료');
|
||||
} else {
|
||||
console.error('❌ 서버 응답 실패:', response.data);
|
||||
alert('데이터를 가져올 수 없습니다');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to download excel:', error);
|
||||
alert('엑셀 다운로드 실패');
|
||||
console.error('❌ 엑셀 다운로드 실패:', error);
|
||||
alert('엑셀 다운로드 실패: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -157,7 +157,59 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
} else if (category === 'FITTING') {
|
||||
itemName = material.fitting_details?.fitting_type || 'FITTING';
|
||||
} else if (category === 'FLANGE') {
|
||||
itemName = 'FLANGE';
|
||||
// 플랜지 타입 추출
|
||||
const desc = cleanDescription.toUpperCase();
|
||||
console.log('🔍 FLANGE 품목명 추출:', cleanDescription);
|
||||
|
||||
if (material.flange_details) {
|
||||
console.log(' flange_details:', material.flange_details);
|
||||
const flangeType = material.flange_details.flange_type || '';
|
||||
const originalFlangeType = material.flange_details.original_flange_type || '';
|
||||
const facingType = material.flange_details.facing_type || '';
|
||||
|
||||
// 특수 플랜지 타입 우선
|
||||
if (desc.includes('ORIFICE')) {
|
||||
itemName = 'ORIFICE FLANGE';
|
||||
} else if (desc.includes('SPECTACLE')) {
|
||||
itemName = 'SPECTACLE BLIND';
|
||||
} else if (desc.includes('PADDLE')) {
|
||||
itemName = 'PADDLE BLIND';
|
||||
} else if (desc.includes('SPACER')) {
|
||||
itemName = 'SPACER';
|
||||
} else if (desc.includes('BLIND')) {
|
||||
itemName = 'BLIND FLANGE';
|
||||
} else {
|
||||
// 일반 플랜지: flange_type 사용 (WN RF, SO RF 등)
|
||||
itemName = flangeType || 'FLANGE';
|
||||
}
|
||||
} else {
|
||||
console.log(' flange_details 없음, description에서 추출');
|
||||
// flange_details가 없으면 description에서 추출
|
||||
if (desc.includes('ORIFICE')) {
|
||||
itemName = 'ORIFICE FLANGE';
|
||||
} else if (desc.includes('SPECTACLE') || desc.includes('SPEC')) {
|
||||
itemName = 'SPECTACLE BLIND';
|
||||
} else if (desc.includes('PADDLE')) {
|
||||
itemName = 'PADDLE BLIND';
|
||||
} else if (desc.includes('SPACER')) {
|
||||
itemName = 'SPACER';
|
||||
} else if (desc.includes('BLIND')) {
|
||||
itemName = 'BLIND FLANGE';
|
||||
} else if (desc.includes(' SW') || desc.includes(',SW') || desc.includes(', SW')) {
|
||||
itemName = 'FLANGE SW';
|
||||
} else if (desc.includes(' BW') || desc.includes(',BW') || desc.includes(', BW')) {
|
||||
itemName = 'FLANGE BW';
|
||||
} else if (desc.includes('RTJ')) {
|
||||
itemName = 'FLANGE RTJ';
|
||||
} else if (desc.includes(' FF') || desc.includes('FULL FACE')) {
|
||||
itemName = 'FLANGE FF';
|
||||
} else if (desc.includes(' RF') || desc.includes('RAISED')) {
|
||||
itemName = 'FLANGE RF';
|
||||
} else {
|
||||
itemName = 'FLANGE';
|
||||
}
|
||||
}
|
||||
console.log(' → 품목명:', itemName);
|
||||
} else if (category === 'VALVE') {
|
||||
itemName = 'VALVE';
|
||||
} else if (category === 'GASKET') {
|
||||
@@ -494,19 +546,27 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
|
||||
*/
|
||||
export const exportMaterialsToExcel = (materials, filename, additionalInfo = {}) => {
|
||||
try {
|
||||
console.log('🔧 exportMaterialsToExcel 시작:', materials.length, '개 자재');
|
||||
|
||||
// 카테고리별로 그룹화
|
||||
const categoryGroups = groupMaterialsByCategory(materials);
|
||||
console.log('📁 카테고리별 그룹:', Object.keys(categoryGroups).map(k => `${k}: ${categoryGroups[k].length}개`));
|
||||
|
||||
// 전체 자재 합치기 (먼저 계산)
|
||||
const consolidatedMaterials = consolidateMaterials(materials);
|
||||
console.log('📦 합쳐진 자재:', consolidatedMaterials.length, '개');
|
||||
|
||||
// 새 워크북 생성
|
||||
const workbook = XLSX.utils.book_new();
|
||||
|
||||
// 카테고리별 시트 생성 (합쳐진 자재)
|
||||
Object.entries(categoryGroups).forEach(([category, items]) => {
|
||||
console.log(`📄 ${category} 시트 생성 중... (${items.length}개 자재)`);
|
||||
const consolidatedItems = consolidateMaterials(items);
|
||||
console.log(` → 합쳐진 결과: ${consolidatedItems.length}개`);
|
||||
|
||||
const formattedItems = consolidatedItems.map(material => formatMaterialForExcel(material));
|
||||
console.log(` → 포맷 완료: ${formattedItems.length}개`);
|
||||
|
||||
if (formattedItems.length > 0) {
|
||||
const categorySheet = XLSX.utils.json_to_sheet(formattedItems);
|
||||
|
||||
Reference in New Issue
Block a user