fix: 구매신청 엑셀 수량 표시 개선 및 FLANGE 품목명 개선

- 구매신청 관리 페이지 수량을 정수로 표시 (3.000 EA → 3 EA)
- JSON 저장 시 수량 정수 변환
- FLANGE 품목명 세분화: WN RF, SO RF, ORIFICE FLANGE, SPECTACLE BLIND 등
- 구매관리 페이지 엑셀 다운로드 데이터 구조 개선
- 디버그 로그 추가
This commit is contained in:
Hyungi Ahn
2025-10-14 15:29:01 +09:00
parent 5a21ef8f6c
commit 72126ef78d
8 changed files with 2640 additions and 3528 deletions

View File

@@ -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);
}
};

View File

@@ -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);