🔧 자재 분류기 개선 및 ELL 키워드 분류 문제 해결 (테스트 필요 - 안되는거 같음)

분류기 개선사항:
1. ELL-O-LET vs 일반 엘보 분류 개선
   - OLET 우선순위 확인 로직 추가
   - ELL 키워드 충돌 문제 해결

2. 엘보 서브타입 강화
   - 90DEG_LONG_RADIUS, 90DEG_SHORT_RADIUS 등 조합형 추가
   - 더 구체적인 키워드 패턴 지원

3. 레듀스 플랜지 분류 개선
   - REDUCING FLANGE가 FITTING이 아닌 FLANGE로 분류되도록 수정
   - 특별 우선순위 로직 추가

4. 90 ELL SW 분류 문제 해결
   - fitting_keywords에 ELL 키워드 추가
   - ELBOW description_keywords에 ELL, 90 ELL, 45 ELL 추가

기술적 개선:
- 키워드 우선순위 체계 강화
- 구체적인 패턴 매칭 개선
- 분류 신뢰도 향상

플랜지 카테고리 개선:
- 타입 풀네임 표시 (WN → WELD NECK FLANGE)
- 끝단처리 별도 컬럼 추가 (RF → RAISED FACE)
- 엑셀 내보내기 구조 개선 (P열 납기일, 관리항목 4개)
This commit is contained in:
hyungi
2025-10-15 17:43:10 +09:00
parent e799aae71b
commit b9442928da
11 changed files with 3107 additions and 172 deletions

View File

@@ -638,6 +638,38 @@ const NewMaterialsPage = ({
// 스웨이지: 타입 명시
const swageType = fittingSubtype || '';
displayType = `SWAGE ${swageType}`.trim();
} else if (fittingType === 'OLET') {
// OLET: 풀네임으로 표시
const oletSubtype = fittingSubtype || '';
let oletDisplayName = '';
switch (oletSubtype) {
case 'SOCKOLET':
oletDisplayName = 'SOCK-O-LET';
break;
case 'WELDOLET':
oletDisplayName = 'WELD-O-LET';
break;
case 'ELLOLET':
oletDisplayName = 'ELL-O-LET';
break;
case 'THREADOLET':
oletDisplayName = 'THREAD-O-LET';
break;
case 'ELBOLET':
oletDisplayName = 'ELB-O-LET';
break;
case 'NIPOLET':
oletDisplayName = 'NIP-O-LET';
break;
case 'COUPOLET':
oletDisplayName = 'COUP-O-LET';
break;
default:
oletDisplayName = 'OLET';
}
displayType = oletDisplayName;
} else if (!displayType) {
// 기타 피팅 타입
displayType = fittingType || 'FITTING';
@@ -738,9 +770,83 @@ const NewMaterialsPage = ({
};
} else if (category === 'FLANGE') {
const description = material.original_description || '';
const flangeDetails = material.flange_details || {};
// 백엔드에서 개선된 플랜지 타입 제공 (WN RF, SO FF 등)
const displayType = material.flange_details?.flange_type || '-';
// 플랜지 타입 풀네임 매핑 (영어)
const flangeTypeMap = {
'WN': 'WELD NECK FLANGE',
'WELD_NECK': 'WELD NECK FLANGE',
'SO': 'SLIP ON FLANGE',
'SLIP_ON': 'SLIP ON FLANGE',
'SW': 'SOCKET WELD FLANGE',
'SOCKET_WELD': 'SOCKET WELD FLANGE',
'BL': 'BLIND FLANGE',
'BLIND': 'BLIND FLANGE',
'RED': 'REDUCING FLANGE',
'REDUCING': 'REDUCING FLANGE',
'ORIFICE': 'ORIFICE FLANGE',
'SPECTACLE': 'SPECTACLE BLIND',
'PADDLE': 'PADDLE BLIND',
'SPACER': 'SPACER'
};
// 끝단처리 풀네임 매핑 (영어)
const facingTypeMap = {
'RF': 'RAISED FACE',
'RAISED_FACE': 'RAISED FACE',
'FF': 'FULL FACE',
'FULL_FACE': 'FULL FACE',
'RTJ': 'RING JOINT',
'RING_JOINT': 'RING JOINT',
'MALE': 'MALE',
'FEMALE': 'FEMALE'
};
// 백엔드에서 제공된 타입 정보
const rawFlangeType = flangeDetails.flange_type || '';
const rawFacingType = flangeDetails.facing_type || '';
// 풀네임으로 변환
let displayType = flangeTypeMap[rawFlangeType] || rawFlangeType || '-';
let facingType = facingTypeMap[rawFacingType] || rawFacingType || '-';
// 백엔드 데이터가 없으면 description에서 추출
if (displayType === '-') {
const upperDesc = description.toUpperCase();
if (upperDesc.includes('WN') || upperDesc.includes('WELD NECK')) {
displayType = 'WELD NECK FLANGE';
} else if (upperDesc.includes('SO') || upperDesc.includes('SLIP ON')) {
displayType = 'SLIP ON FLANGE';
} else if (upperDesc.includes('SW') || upperDesc.includes('SOCKET')) {
displayType = 'SOCKET WELD FLANGE';
} else if (upperDesc.includes('BLIND') || upperDesc.includes('BL')) {
displayType = 'BLIND FLANGE';
} else if (upperDesc.includes('REDUCING') || upperDesc.includes('RED')) {
displayType = 'REDUCING FLANGE';
} else if (upperDesc.includes('ORIFICE')) {
displayType = 'ORIFICE FLANGE';
} else if (upperDesc.includes('SPECTACLE')) {
displayType = 'SPECTACLE BLIND';
} else if (upperDesc.includes('PADDLE')) {
displayType = 'PADDLE BLIND';
} else if (upperDesc.includes('SPACER')) {
displayType = 'SPACER';
} else {
displayType = 'FLANGE';
}
}
// 끝단처리 정보가 없으면 description에서 추출
if (facingType === '-') {
const upperDesc = description.toUpperCase();
if (upperDesc.includes(' RF') || upperDesc.includes('RAISED')) {
facingType = 'RAISED FACE';
} else if (upperDesc.includes(' FF') || upperDesc.includes('FULL FACE')) {
facingType = 'FULL FACE';
} else if (upperDesc.includes('RTJ') || upperDesc.includes('RING')) {
facingType = 'RING JOINT';
}
}
// 원본 설명에서 스케줄 추출
let schedule = '-';
@@ -756,11 +862,12 @@ const NewMaterialsPage = ({
return {
type: 'FLANGE',
subtype: displayType, // 백엔드에서 개선된 타입 정보 제공 (WN RF, SO FF 등)
subtype: displayType, // 풀네임 플랜지 타입
facing: facingType, // 새로 추가: 끝단처리 정보
size: material.size_spec || '-',
pressure: material.flange_details?.pressure_rating || '-',
pressure: flangeDetails.pressure_rating || '-',
schedule: schedule,
grade: material.full_material_grade || material.material_grade || '-', // 전체 재질명 우선 사용
grade: material.full_material_grade || material.material_grade || '-',
quantity: Math.round(material.quantity || 0),
unit: '개',
isFlange: true // 플랜지 구분용 플래그
@@ -1734,6 +1841,7 @@ const NewMaterialsPage = ({
<div>선택</div>
<FilterableHeader sortKey="type" filterKey="type">종류</FilterableHeader>
<FilterableHeader sortKey="subtype" filterKey="subtype">타입</FilterableHeader>
<FilterableHeader sortKey="facing" filterKey="facing">끝단처리</FilterableHeader>
<FilterableHeader sortKey="size" filterKey="size">크기</FilterableHeader>
<div>압력(파운드)</div>
<FilterableHeader sortKey="schedule" filterKey="schedule">스케줄</FilterableHeader>
@@ -2262,6 +2370,11 @@ const NewMaterialsPage = ({
<span className="subtype-text">{info.subtype}</span>
</div>
{/* 끝단처리 (플랜지 전용) */}
<div className="material-cell">
<span className="facing-text">{info.facing || '-'}</span>
</div>
{/* 크기 */}
<div className="material-cell">
<span className="size-text">{info.size}</span>