feat: BOM과 구매관리 페이지 엑셀 통합 및 완전 동일화
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

엑셀 내보내기 통합:
- BOM 페이지: 구매신청 시 백엔드에서 엑셀 파일 생성 및 저장
- 구매관리 페이지: 저장된 엑셀 파일 직접 다운로드 (재생성 안 함)
- 두 페이지에서 완전히 동일한 엑셀 파일 제공

백엔드 엑셀 생성:
- openpyxl 사용하여 서버에서 엑셀 생성
- 카테고리별 시트 구성
- 헤더 스타일링 (연파랑 배경)
- 컬럼 너비 자동 조정

FLANGE 품목명 개선:
- 품목명: FLANGE (간단)
- 상세내역: WELD NECK RF, SLIP-ON RF 등 (전체 이름)
- 특수 플랜지: ORIFICE FLANGE, SPECTACLE BLIND 등 구분

구매신청 관리 API 개선:
- 상세 정보 포함 (pipe_details, fitting_details, flange_details 등)
- BOM 형식과 동일한 데이터 구조
- 수량 정수 변환 (3.000 → 3)

에러 수정:
- fileName 중복 선언 해결
- flange_details.connection_method 컬럼 제거 (존재하지 않음)
- Python 문법 오류 수정 (new Date() → datetime.now())

DB 스키마 개선:
- revision_status 컬럼 추가 및 크기 조정 (VARCHAR(30))
- 리비전 변경사항 추적 지원
This commit is contained in:
Hyungi Ahn
2025-10-14 15:59:33 +09:00
parent 72126ef78d
commit 8f5330a008
9 changed files with 334 additions and 4535 deletions

View File

@@ -150,66 +150,43 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
// 구매 수량 계산
const purchaseInfo = calculatePurchaseQuantity(material);
// 품목명 생성 (카테고리별 상세 처리)
// 변수 선언 (먼저 선언)
let itemName = '';
let detailInfo = '';
let gasketMaterial = '';
let gasketThickness = '';
if (category === 'PIPE') {
itemName = material.pipe_details?.manufacturing_method || 'PIPE';
} else if (category === 'FITTING') {
itemName = material.fitting_details?.fitting_type || 'FITTING';
} else if (category === 'FLANGE') {
// 플랜지 타입 추출
const desc = cleanDescription.toUpperCase();
console.log('🔍 FLANGE 품목명 추출:', cleanDescription);
// 플랜지는 품목명만 간단하게 (상세내역에 타입 정보)
itemName = 'FLANGE';
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';
}
// 특수 플랜지는 구분
const desc = cleanDescription.toUpperCase();
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';
}
// 상세내역에 플랜지 타입 정보 저장 (줄임말 사용)
if (material.flange_details && material.flange_details.flange_type) {
detailInfo = material.flange_details.flange_type; // WN RF, SO RF 등
} 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';
// description에서 추출 (전체 이름 그대로 사용)
const flangeTypeMatch = cleanDescription.match(/FLG\s+([^,]+?)(?=\s*SCH|\s*,\s*\d+LB|$)/i);
if (flangeTypeMatch) {
detailInfo = flangeTypeMatch[1].trim(); // WELD NECK RF 등 그대로
}
}
console.log(' → 품목명:', itemName);
} else if (category === 'VALVE') {
itemName = 'VALVE';
} else if (category === 'GASKET') {
@@ -379,10 +356,7 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
}
}
// 카테고리별 상세 정보 추출
let detailInfo = '';
let gasketMaterial = '';
let gasketThickness = '';
// 카테고리별 상세 정보 추출 (이미 위에서 선언됨)
if (category === 'BOLT') {
// 볼트의 경우 표면처리 정보 추출