🔧 자재 분류기 개선 및 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

@@ -9,8 +9,16 @@ DATABASE_URL = os.getenv(
"postgresql://tkmp_user:tkmp_password_2025@postgres:5432/tk_mp_bom"
)
# SQLAlchemy 엔진 생성
engine = create_engine(DATABASE_URL)
# SQLAlchemy 엔진 생성 (UTF-8 인코딩 설정)
engine = create_engine(
DATABASE_URL,
connect_args={
"client_encoding": "utf8",
"options": "-c client_encoding=utf8 -c timezone=UTC"
},
pool_pre_ping=True,
echo=False
)
# 세션 팩토리 생성
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@@ -194,7 +194,7 @@ async def get_purchase_requests(
u.name as requested_by,
f.original_filename,
j.job_name,
COUNT(pri.id) as item_count
COUNT(pri.item_id) as item_count
FROM purchase_requests pr
LEFT JOIN users u ON pr.requested_by = u.user_id
LEFT JOIN files f ON pr.file_id = f.id
@@ -271,9 +271,13 @@ async def get_request_materials(
if info_result and info_result.excel_file_path:
json_path = os.path.join(EXCEL_DIR, info_result.excel_file_path)
if os.path.exists(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
grouped_materials = data.get("grouped_materials", [])
try:
with open(json_path, 'r', encoding='utf-8', errors='ignore') as f:
data = json.load(f)
grouped_materials = data.get("grouped_materials", [])
except Exception as e:
print(f"⚠️ JSON 파일 읽기 오류 (무시): {e}")
grouped_materials = []
# 개별 자재 정보 조회 (기존 코드)
query = text("""
@@ -316,76 +320,106 @@ async def get_request_materials(
ORDER BY m.classified_category, m.original_description
""")
# 🎯 데이터베이스 쿼리 실행
results = db.execute(query, {"request_id": request_id}).fetchall()
materials = []
for row in results:
# quantity를 정수로 변환 (소수점 제거)
qty = row.requested_quantity or row.original_quantity
# 🎯 안전한 문자열 변환 함수
def safe_str(value):
if value is None:
return ''
try:
qty_int = int(float(qty)) if qty else 0
except (ValueError, TypeError):
if isinstance(value, bytes):
return value.decode('utf-8', errors='ignore')
return str(value)
except Exception:
return str(value) if value else ''
for row in results:
try:
# quantity를 정수로 변환 (소수점 제거)
qty = row.requested_quantity or row.original_quantity
try:
qty_int = int(float(qty)) if qty else 0
except (ValueError, TypeError):
qty_int = 0
# 안전한 문자열 변환
original_description = safe_str(row.original_description)
size_spec = safe_str(row.size_spec)
material_grade = safe_str(row.material_grade)
full_material_grade = safe_str(row.full_material_grade)
user_requirement = safe_str(row.user_requirement)
except Exception as e:
# 오류 발생 시 기본값 사용
qty_int = 0
original_description = ''
size_spec = ''
material_grade = ''
full_material_grade = ''
user_requirement = ''
# BOM 페이지와 동일한 형식으로 데이터 구성
material_dict = {
"item_id": row.item_id,
"material_id": row.material_id,
"id": row.material_id,
"original_description": row.original_description,
"classified_category": row.classified_category,
"size_spec": row.size_spec,
"size_inch": row.main_nom,
"main_nom": row.main_nom,
"red_nom": row.red_nom,
"schedule": row.schedule,
"material_grade": row.material_grade,
"full_material_grade": row.full_material_grade,
"original_description": original_description,
"classified_category": safe_str(row.classified_category),
"size_spec": size_spec,
"size_inch": safe_str(row.main_nom),
"main_nom": safe_str(row.main_nom),
"red_nom": safe_str(row.red_nom),
"schedule": safe_str(row.schedule),
"material_grade": material_grade,
"full_material_grade": full_material_grade,
"quantity": qty_int,
"unit": row.requested_unit or row.original_unit,
"user_requirement": row.user_requirement,
"unit": safe_str(row.requested_unit or row.original_unit),
"user_requirement": user_requirement,
"is_ordered": row.is_ordered,
"is_received": row.is_received,
"classification_details": row.classification_details
"classification_details": safe_str(row.classification_details)
}
# 카테고리별 상세 정보 추가
# 카테고리별 상세 정보 추가 (안전한 문자열 처리)
if row.classified_category == 'PIPE' and row.manufacturing_method:
material_dict["pipe_details"] = {
"manufacturing_method": row.manufacturing_method,
"schedule": row.pipe_schedule,
"material_spec": row.material_spec,
"end_preparation": row.end_preparation,
"manufacturing_method": safe_str(row.manufacturing_method),
"schedule": safe_str(row.pipe_schedule),
"material_spec": safe_str(row.material_spec),
"end_preparation": safe_str(row.end_preparation),
"length_mm": row.length_mm
}
elif row.classified_category == 'FITTING' and row.fitting_type:
material_dict["fitting_details"] = {
"fitting_type": row.fitting_type,
"fitting_subtype": row.fitting_subtype,
"connection_method": row.fitting_connection,
"pressure_rating": row.fitting_pressure,
"schedule": row.fitting_schedule
"fitting_type": safe_str(row.fitting_type),
"fitting_subtype": safe_str(row.fitting_subtype),
"connection_method": safe_str(row.fitting_connection),
"pressure_rating": safe_str(row.fitting_pressure),
"schedule": safe_str(row.fitting_schedule)
}
elif row.classified_category == 'FLANGE' and row.flange_type:
material_dict["flange_details"] = {
"flange_type": row.flange_type,
"facing_type": row.facing_type,
"pressure_rating": row.flange_pressure
"flange_type": safe_str(row.flange_type),
"facing_type": safe_str(row.facing_type),
"pressure_rating": safe_str(row.flange_pressure)
}
elif row.classified_category == 'GASKET' and row.gasket_type:
material_dict["gasket_details"] = {
"gasket_type": row.gasket_type,
"gasket_subtype": row.gasket_subtype,
"material_type": row.gasket_material,
"filler_material": row.filler_material,
"pressure_rating": row.gasket_pressure,
"thickness": row.gasket_thickness
"gasket_type": safe_str(row.gasket_type),
"gasket_subtype": safe_str(row.gasket_subtype),
"material_type": safe_str(row.gasket_material),
"filler_material": safe_str(row.filler_material),
"pressure_rating": safe_str(row.gasket_pressure),
"thickness": safe_str(row.gasket_thickness)
}
elif row.classified_category == 'BOLT' and row.bolt_type:
material_dict["bolt_details"] = {
"bolt_type": row.bolt_type,
"material_standard": row.bolt_material,
"length": row.bolt_length
"bolt_type": safe_str(row.bolt_type),
"material_standard": safe_str(row.bolt_material),
"length": safe_str(row.bolt_length)
}
materials.append(material_dict)
@@ -521,10 +555,11 @@ def create_excel_file(materials_data: List[Dict], file_path: str, request_no: st
ws = wb.create_sheet(title=category)
# 헤더 정의
# 헤더 정의 (P열에 납기일, 관리항목 통일)
headers = ['TAGNO', '품목명', '수량', '통화구분', '단가', '크기', '압력등급', '스케줄',
'재질', '상세내역', '사용자요구', '관리항목1', '관리항목7', '관리항목8',
'관리항목9', '관리항목10', '납기일(YYYY-MM-DD)']
'재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3',
'관리항목4', '납기일(YYYY-MM-DD)', '관리항목5', '관리항목6', '관리항목7',
'관리항목8', '관리항목9', '관리항목10']
# 헤더 작성
for col, header in enumerate(headers, 1):

View File

@@ -11,8 +11,12 @@ from .material_classifier import classify_material, get_manufacturing_method_fro
FITTING_TYPES = {
"ELBOW": {
"dat_file_patterns": ["90L_", "45L_", "ELL_", "ELBOW_"],
"description_keywords": ["ELBOW", "ELL", "엘보"],
"description_keywords": ["ELBOW", "ELL", "엘보", "90 ELBOW", "45 ELBOW", "LR ELBOW", "SR ELBOW", "90 ELL", "45 ELL"],
"subtypes": {
"90DEG_LONG_RADIUS": ["90 LR", "90° LR", "90DEG LR", "90도 장반경", "90 LONG RADIUS", "LR 90"],
"90DEG_SHORT_RADIUS": ["90 SR", "90° SR", "90DEG SR", "90도 단반경", "90 SHORT RADIUS", "SR 90"],
"45DEG_LONG_RADIUS": ["45 LR", "45° LR", "45DEG LR", "45도 장반경", "45 LONG RADIUS", "LR 45"],
"45DEG_SHORT_RADIUS": ["45 SR", "45° SR", "45DEG SR", "45도 단반경", "45 SHORT RADIUS", "SR 45"],
"90DEG": ["90", "90°", "90DEG", "90도"],
"45DEG": ["45", "45°", "45DEG", "45도"],
"LONG_RADIUS": ["LR", "LONG RADIUS", "장반경"],
@@ -98,11 +102,12 @@ FITTING_TYPES = {
},
"OLET": {
"dat_file_patterns": ["SOL_", "WOL_", "TOL_", "OLET_", "SOCK-O-LET", "WELD-O-LET"],
"description_keywords": ["OLET", "올렛", "O-LET", "SOCK-O-LET", "WELD-O-LET", "SOCKOLET", "WELDOLET", "THREAD-O-LET", "THREADOLET", "SOCKLET", "SOCKET"],
"dat_file_patterns": ["SOL_", "WOL_", "TOL_", "EOL_", "NOL_", "COL_", "OLET_", "SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET"],
"description_keywords": ["SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET", "SOCKOLET", "WELDOLET", "ELLOLET", "THREADOLET", "ELBOLET", "NIPOLET", "COUPOLET", "OLET", "올렛", "O-LET", "SOCKLET"],
"subtypes": {
"SOCKOLET": ["SOCK-O-LET", "SOCKOLET", "SOL", "SOCK O-LET", "SOCKET-O-LET", "SOCKLET"],
"WELDOLET": ["WELD-O-LET", "WELDOLET", "WOL", "WELD O-LET", "WELDING-O-LET"],
"ELLOLET": ["ELL-O-LET", "ELLOLET", "EOL", "ELL O-LET", "ELBOW-O-LET"],
"THREADOLET": ["THREAD-O-LET", "THREADOLET", "TOL", "THREADED-O-LET"],
"ELBOLET": ["ELB-O-LET", "ELBOLET", "EOL", "ELBOW-O-LET"],
"NIPOLET": ["NIP-O-LET", "NIPOLET", "NOL", "NIPPLE-O-LET"],
@@ -203,7 +208,11 @@ def classify_fitting(dat_file: str, description: str, main_nom: str,
dat_upper = dat_file.upper()
# 1. 피팅 키워드 확인 (재질만 있어도 통합 분류기가 이미 피팅으로 분류했으므로 진행)
fitting_keywords = ['ELBOW', 'ELL', 'TEE', 'REDUCER', 'RED', 'CAP', 'NIPPLE', 'SWAGE', 'OLET', 'COUPLING', 'PLUG', 'SOCKLET', 'SOCKET', '엘보', '', '리듀서', '', '니플', '스웨지', '올렛', '커플링', '플러그', 'SOCK-O-LET', 'WELD-O-LET', 'SOCKOLET', 'WELDOLET']
# OLET 키워드를 우선 확인하여 정확한 분류 수행
olet_keywords = ['SOCK-O-LET', 'WELD-O-LET', 'ELL-O-LET', 'THREAD-O-LET', 'ELB-O-LET', 'NIP-O-LET', 'COUP-O-LET', 'SOCKOLET', 'WELDOLET', 'ELLOLET', 'THREADOLET', 'ELBOLET', 'NIPOLET', 'COUPOLET', 'OLET', 'O-LET', 'SOCKLET']
has_olet_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in olet_keywords)
fitting_keywords = ['ELBOW', 'ELL', 'TEE', 'REDUCER', 'RED', 'CAP', 'NIPPLE', 'SWAGE', 'COUPLING', 'PLUG', '엘보', '', '리듀서', '', '니플', '스웨지', '올렛', '커플링', '플러그'] + olet_keywords
has_fitting_keyword = any(keyword in desc_upper or keyword in dat_upper for keyword in fitting_keywords)
# 피팅 재질 확인 (A234, A403, A420)
@@ -301,6 +310,14 @@ def classify_fitting(dat_file: str, description: str, main_nom: str,
"fitting_type": fitting_type_result.get('confidence', 0),
"connection": connection_result.get('confidence', 0),
"pressure": pressure_result.get('confidence', 0)
}),
# 통합분류기 호환성을 위한 confidence 필드
"confidence": calculate_fitting_confidence({
"material": material_result.get('confidence', 0),
"fitting_type": fitting_type_result.get('confidence', 0),
"connection": connection_result.get('confidence', 0),
"pressure": pressure_result.get('confidence', 0)
})
}
@@ -428,12 +445,28 @@ def classify_fitting_type(dat_file: str, description: str,
dat_upper = dat_file.upper()
desc_upper = description.upper()
# 0. 사이즈 패턴 분석으로 TEE vs REDUCER 구분 (최우선)
# 0. OLET 우선 확인 (ELL과의 혼동 방지)
olet_specific_keywords = ['SOCK-O-LET', 'WELD-O-LET', 'ELL-O-LET', 'THREAD-O-LET', 'ELB-O-LET', 'NIP-O-LET', 'COUP-O-LET', 'SOCKOLET', 'WELDOLET', 'ELLOLET', 'THREADOLET', 'ELBOLET', 'NIPOLET', 'COUPOLET', 'O-LET', 'SOCKLET']
for keyword in olet_specific_keywords:
if keyword in desc_upper or keyword in dat_upper:
subtype_result = classify_fitting_subtype(
"OLET", desc_upper, main_nom, red_nom, FITTING_TYPES["OLET"]
)
return {
"type": "OLET",
"subtype": subtype_result["subtype"],
"confidence": 0.95,
"evidence": [f"OLET_PRIORITY_KEYWORD: {keyword}"],
"subtype_confidence": subtype_result["confidence"],
"requires_two_sizes": FITTING_TYPES["OLET"].get("requires_two_sizes", False)
}
# 1. 사이즈 패턴 분석으로 TEE vs REDUCER 구분
size_pattern_result = analyze_size_pattern_for_fitting_type(desc_upper, main_nom, red_nom)
if size_pattern_result.get("confidence", 0) > 0.85:
return size_pattern_result
# 1. DAT_FILE 패턴으로 1차 분류 (가장 신뢰도 높음)
# 2. DAT_FILE 패턴으로 1차 분류 (가장 신뢰도 높음)
for fitting_type, type_data in FITTING_TYPES.items():
for pattern in type_data["dat_file_patterns"]:
if pattern in dat_upper:
@@ -450,7 +483,7 @@ def classify_fitting_type(dat_file: str, description: str,
"requires_two_sizes": type_data.get("requires_two_sizes", False)
}
# 2. DESCRIPTION 키워드로 2차 분류
# 3. DESCRIPTION 키워드로 2차 분류
for fitting_type, type_data in FITTING_TYPES.items():
for keyword in type_data["description_keywords"]:
if keyword in desc_upper:
@@ -467,7 +500,7 @@ def classify_fitting_type(dat_file: str, description: str,
"requires_two_sizes": type_data.get("requires_two_sizes", False)
}
# 3. 분류 실패
# 4. 분류 실패
return {
"type": "UNKNOWN",
"subtype": "UNKNOWN",

View File

@@ -11,9 +11,9 @@ from .fitting_classifier import classify_fitting
LEVEL1_TYPE_KEYWORDS = {
"BOLT": ["FLANGE BOLT", "U-BOLT", "U BOLT", "BOLT", "STUD", "NUT", "SCREW", "WASHER", "볼트", "너트", "스터드", "나사", "와셔", "유볼트"],
"VALVE": ["VALVE", "GATE", "BALL", "GLOBE", "CHECK", "BUTTERFLY", "NEEDLE", "RELIEF", "밸브", "게이트", "", "글로브", "체크", "버터플라이", "니들", "릴리프"],
"FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND"],
"FLANGE": ["FLG", "FLANGE", "플랜지", "프랜지", "ORIFICE", "SPECTACLE", "PADDLE", "SPACER", "BLIND", "REDUCING FLANGE", "RED FLANGE"],
"PIPE": ["PIPE", "TUBE", "파이프", "배관", "SMLS", "SEAMLESS"],
"FITTING": ["ELBOW", "ELL", "TEE", "REDUCER", "RED", "CAP", "COUPLING", "NIPPLE", "SWAGE", "OLET", "PLUG", "엘보", "", "리듀서", "", "니플", "커플링", "플러그", "CONC", "ECC", "SOCK-O-LET", "WELD-O-LET", "SOCKOLET", "WELDOLET", "THREADOLET"],
"FITTING": ["SOCK-O-LET", "WELD-O-LET", "ELL-O-LET", "THREAD-O-LET", "ELB-O-LET", "NIP-O-LET", "COUP-O-LET", "SOCKOLET", "WELDOLET", "ELLOLET", "THREADOLET", "ELBOLET", "NIPOLET", "COUPOLET", "OLET", "ELBOW", "ELL", "TEE", "REDUCER", "RED", "CAP", "COUPLING", "NIPPLE", "SWAGE", "PLUG", "엘보", "", "리듀서", "", "니플", "커플링", "플러그", "CONC", "ECC"],
"GASKET": ["GASKET", "GASK", "가스켓", "SWG", "SPIRAL"],
"INSTRUMENT": ["GAUGE", "TRANSMITTER", "SENSOR", "THERMOMETER", "계기", "게이지", "트랜스미터", "센서"],
"SUPPORT": ["URETHANE BLOCK", "URETHANE", "BLOCK SHOE", "CLAMP", "SUPPORT", "HANGER", "SPRING", "우레탄", "블록", "클램프", "서포트", "행거", "스프링"]
@@ -128,24 +128,29 @@ def classify_material_integrated(description: str, main_nom: str = "",
# 1단계: Level 1 키워드로 타입 식별
detected_types = []
for material_type, keywords in LEVEL1_TYPE_KEYWORDS.items():
type_found = False
# 긴 키워드부터 확인 (FLANGE BOLT가 FLANGE보다 먼저 매칭되도록)
sorted_keywords = sorted(keywords, key=len, reverse=True)
for keyword in sorted_keywords:
# 전체 문자열에서 찾기
if keyword in desc_upper:
detected_types.append((material_type, keyword))
type_found = True
break
# 각 부분에서도 정확히 매칭되는지 확인
for part in desc_parts:
if keyword == part or keyword in part:
# 특별 우선순위: REDUCING FLANGE 먼저 확인
if "REDUCING FLANGE" in desc_upper or "RED FLANGE" in desc_upper:
detected_types.append(("FLANGE", "REDUCING FLANGE"))
else:
for material_type, keywords in LEVEL1_TYPE_KEYWORDS.items():
type_found = False
# 긴 키워드부터 확인 (FLANGE BOLT가 FLANGE보다 먼저 매칭되도록)
sorted_keywords = sorted(keywords, key=len, reverse=True)
for keyword in sorted_keywords:
# 전체 문자열에서 찾기
if keyword in desc_upper:
detected_types.append((material_type, keyword))
type_found = True
break
if type_found:
break
# 각 부분에서도 정확히 매칭되는지 확인
for part in desc_parts:
if keyword == part or keyword in part:
detected_types.append((material_type, keyword))
type_found = True
break
if type_found:
break
# 2단계: 복수 타입 감지 시 Level 2로 구체화
if len(detected_types) > 1:

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,745 @@
{
"request_no": "PR-20251015-002",
"job_no": "TKG-20000P",
"created_at": "2025-10-15T05:53:13.449375",
"materials": [
{
"material_id": 76366,
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "10\"",
"material_grade": "ASTM A182 F304",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76371,
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "12\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76372,
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A105",
"quantity": 36,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76408,
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A105",
"quantity": 6,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76414,
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A182 F304",
"quantity": 7,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76422,
"description": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 8,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76427,
"description": "FLG WELD NECK RF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A182 F304",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76429,
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 12,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76441,
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76446,
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A182 F304",
"quantity": 9,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76455,
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "4\"",
"material_grade": "ASTM A105",
"quantity": 3,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76458,
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "6\"",
"material_grade": "ASTM A105",
"quantity": 10,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76468,
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1/2\"",
"material_grade": "ASTM A182 F304",
"quantity": 14,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76480,
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 15,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76484,
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A182 F304",
"quantity": 9,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76485,
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A105",
"quantity": 66,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76489,
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A105",
"quantity": 6,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76491,
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A182 F304",
"quantity": 8,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76499,
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 40,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76535,
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76540,
"description": "RED. FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76542,
"description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 4,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76546,
"description": "RED. FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76556,
"description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76624,
"description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76629,
"description": "FLG SWRF SCH 80, 300LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 3,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76634,
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1/2\"",
"material_grade": "ASTM A105",
"quantity": 57,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76691,
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 7,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76698,
"description": "FLG SWRF SCH 40S, 300LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76699,
"description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76711,
"description": "FLG SWRF SCH 80, 300LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"material_id": 76713,
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
}
],
"grouped_materials": [
{
"group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|10\"|undefined|ASTM A182 F304",
"material_ids": [
76366
],
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "10\"",
"material_grade": "ASTM A182 F304",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|12\"|undefined|ASTM A182 F304",
"material_ids": [
76371
],
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "12\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|2\"|undefined|ASTM A105",
"material_ids": [
76372
],
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A105",
"quantity": 36,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|2\"|undefined|ASTM A105",
"material_ids": [
76408
],
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A105",
"quantity": 6,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|2\"|undefined|ASTM A182 F304",
"material_ids": [
76414
],
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "2\"",
"material_grade": "ASTM A182 F304",
"quantity": 7,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105|3\"|undefined|ASTM A105",
"material_ids": [
76422
],
"description": "FLG WELD NECK RF SCH 40, 600LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 8,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40S, 600LB, ASTM A182 F304|3\"|undefined|ASTM A182 F304",
"material_ids": [
76427
],
"description": "FLG WELD NECK RF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A182 F304",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|3\"|undefined|ASTM A105",
"material_ids": [
76429
],
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 12,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|3\"|undefined|ASTM A105",
"material_ids": [
76441
],
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304|3\"|undefined|ASTM A182 F304",
"material_ids": [
76446
],
"description": "FLG WELD NECK RF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3\"",
"material_grade": "ASTM A182 F304",
"quantity": 9,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105|4\"|undefined|ASTM A105",
"material_ids": [
76455
],
"description": "FLG WELD NECK RF SCH 40, 300LB, ASTM A105",
"category": "FLANGE",
"size": "4\"",
"material_grade": "ASTM A105",
"quantity": 3,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105|6\"|undefined|ASTM A105",
"material_ids": [
76458
],
"description": "FLG WELD NECK RF SCH 40, 150LB, ASTM A105",
"category": "FLANGE",
"size": "6\"",
"material_grade": "ASTM A105",
"quantity": 10,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1/2\"|undefined|ASTM A182 F304",
"material_ids": [
76468
],
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1/2\"",
"material_grade": "ASTM A182 F304",
"quantity": 14,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|3/4\"|undefined|ASTM A105",
"material_ids": [
76480
],
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 15,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304",
"material_ids": [
76484
],
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A182 F304",
"quantity": 9,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1\"|undefined|ASTM A105",
"material_ids": [
76485
],
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A105",
"quantity": 66,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|1\"|undefined|ASTM A105",
"material_ids": [
76489
],
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A105",
"quantity": 6,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1 1/2\"|undefined|ASTM A182 F304",
"material_ids": [
76491
],
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A182 F304",
"quantity": 8,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1 1/2\"|undefined|ASTM A105",
"material_ids": [
76499
],
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 40,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|1 1/2\"|undefined|ASTM A105",
"material_ids": [
76535
],
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "RED. FLG SWRF SCH 40S, 150LB, ASTM A182 F304|1 1/2\" x 3/4\"|undefined|ASTM A182 F304",
"material_ids": [
76540
],
"description": "RED. FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "RED. FLG SWRF SCH 80, 150LB, ASTM A105|1 1/2\" x 3/4\"|undefined|ASTM A105",
"material_ids": [
76542
],
"description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 4,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "RED. FLG SWRF SCH 80, 600LB, ASTM A105|1 1/2\" x 3/4\"|undefined|ASTM A105",
"material_ids": [
76546
],
"description": "RED. FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304|1\"|undefined|ASTM A182 F304",
"material_ids": [
76556
],
"description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "1\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "RED. FLG SWRF SCH 80, 150LB, ASTM A105|1\" x 3/4\"|undefined|ASTM A105",
"material_ids": [
76624
],
"description": "RED. FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1\" x 3/4\"",
"material_grade": "ASTM A105",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 300LB, ASTM A105|1 1/2\"|undefined|ASTM A105",
"material_ids": [
76629
],
"description": "FLG SWRF SCH 80, 300LB, ASTM A105",
"category": "FLANGE",
"size": "1 1/2\"",
"material_grade": "ASTM A105",
"quantity": 3,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 150LB, ASTM A105|1/2\"|undefined|ASTM A105",
"material_ids": [
76634
],
"description": "FLG SWRF SCH 80, 150LB, ASTM A105",
"category": "FLANGE",
"size": "1/2\"",
"material_grade": "ASTM A105",
"quantity": 57,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304",
"material_ids": [
76691
],
"description": "FLG SWRF SCH 40S, 150LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 7,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 300LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304",
"material_ids": [
76698
],
"description": "FLG SWRF SCH 40S, 300LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304|3/4\"|undefined|ASTM A182 F304",
"material_ids": [
76699
],
"description": "FLG SWRF SCH 40S, 600LB, ASTM A182 F304",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A182 F304",
"quantity": 1,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 300LB, ASTM A105|3/4\"|undefined|ASTM A105",
"material_ids": [
76711
],
"description": "FLG SWRF SCH 80, 300LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 2,
"unit": "EA",
"user_requirement": ""
},
{
"group_key": "FLG SWRF SCH 80, 600LB, ASTM A105|3/4\"|undefined|ASTM A105",
"material_ids": [
76713
],
"description": "FLG SWRF SCH 80, 600LB, ASTM A105",
"category": "FLANGE",
"size": "3/4\"",
"material_grade": "ASTM A105",
"quantity": 5,
"unit": "EA",
"user_requirement": ""
}
]
}

Binary file not shown.

View File

@@ -939,7 +939,7 @@ function App() {
</div>
</div>
</div>
)} {/* adminFeatures 조건문 닫기 */}
)}
</div>
</div>
);

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>

View File

@@ -156,79 +156,247 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
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') {
// 플랜지는 품목명만 간단하게 (상세내역에 타입 정보)
itemName = 'FLANGE';
// 파이프 상세 타입 표시 개선
const pipeDetails = material.pipe_details || {};
const manufacturingMethod = pipeDetails.manufacturing_method || '';
const endPreparation = pipeDetails.end_preparation || '';
// 특수 플랜지는 구분
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 (manufacturingMethod && endPreparation) {
itemName = `${manufacturingMethod} PIPE (${endPreparation})`;
} else if (manufacturingMethod) {
itemName = `${manufacturingMethod} PIPE`;
} else if (endPreparation) {
itemName = `PIPE (${endPreparation})`;
} else {
// description에서 제조방법 추출 시도
const desc = cleanDescription.toUpperCase();
if (desc.includes('SEAMLESS')) {
itemName = 'SEAMLESS PIPE';
} else if (desc.includes('WELDED')) {
itemName = 'WELDED PIPE';
} else if (desc.includes('ERW')) {
itemName = 'ERW PIPE';
} else if (desc.includes('SMLS')) {
itemName = 'SEAMLESS PIPE';
} else {
itemName = 'PIPE';
}
}
} else if (category === 'FITTING') {
// 피팅 상세 타입 표시 (OLET 등 풀네임)
const fittingDetails = material.fitting_details || {};
const fittingType = fittingDetails.fitting_type || '';
const fittingSubtype = fittingDetails.fitting_subtype || '';
if (fittingType === 'OLET') {
// OLET 풀네임 표시
switch (fittingSubtype) {
case 'SOCKOLET':
itemName = 'SOCK-O-LET';
break;
case 'WELDOLET':
itemName = 'WELD-O-LET';
break;
case 'ELLOLET':
itemName = 'ELL-O-LET';
break;
case 'THREADOLET':
itemName = 'THREAD-O-LET';
break;
case 'ELBOLET':
itemName = 'ELB-O-LET';
break;
case 'NIPOLET':
itemName = 'NIP-O-LET';
break;
case 'COUPOLET':
itemName = 'COUP-O-LET';
break;
default:
itemName = 'OLET';
}
} else if (fittingType === 'ELBOW') {
// 엘보 각도 표시
const angle = fittingSubtype === '90DEG' ? '90도' : fittingSubtype === '45DEG' ? '45도' : '';
itemName = `엘보 ${angle}`.trim();
} else if (fittingType === 'TEE') {
// 티 타입 표시
const teeType = fittingSubtype === 'EQUAL' ? '등경' : fittingSubtype === 'REDUCING' ? '축소' : '';
itemName = `${teeType}`.trim();
} else if (fittingType === 'REDUCER') {
// 리듀서 타입 표시
const reducerType = fittingSubtype === 'CONCENTRIC' ? '동심' : fittingSubtype === 'ECCENTRIC' ? '편심' : '';
itemName = `리듀서 ${reducerType}`.trim();
} else {
itemName = fittingType || 'FITTING';
}
} else if (category === 'FLANGE') {
// 플랜지 상세 타입 표시
const flangeDetails = material.flange_details || {};
const flangeType = flangeDetails.flange_type || '';
const facingType = flangeDetails.facing_type || '';
if (flangeType === 'WELD_NECK') {
itemName = '웰드넥 플랜지';
} else if (flangeType === 'SLIP_ON') {
itemName = '슬립온 플랜지';
} else if (flangeType === 'SOCKET_WELD') {
itemName = '소켓웰드 플랜지';
} else if (flangeType === 'BLIND') {
itemName = '블라인드 플랜지';
} else if (flangeType === 'REDUCING') {
itemName = '축소 플랜지';
} else {
// 특수 플랜지는 구분
const desc = cleanDescription.toUpperCase();
if (desc.includes('ORIFICE')) {
itemName = '오리피스 플랜지';
} else if (desc.includes('SPECTACLE')) {
itemName = '스펙터클 블라인드';
} else if (desc.includes('PADDLE')) {
itemName = '패들 블라인드';
} else if (desc.includes('SPACER')) {
itemName = '스페이서';
} else {
itemName = '플랜지';
}
}
// 상세내역에 플랜지 타입 정보 저장 (줄임말 사용)
if (material.flange_details && material.flange_details.flange_type) {
detailInfo = material.flange_details.flange_type; // WN RF, SO RF 등
// 상세내역에 플랜지 타입 정보 저장
if (flangeDetails.flange_type) {
detailInfo = `${flangeType} ${facingType}`.trim();
} else {
// description에서 추출 (전체 이름 그대로 사용)
// description에서 추출
const flangeTypeMatch = cleanDescription.match(/FLG\s+([^,]+?)(?=\s*SCH|\s*,\s*\d+LB|$)/i);
if (flangeTypeMatch) {
detailInfo = flangeTypeMatch[1].trim(); // WELD NECK RF 등 그대로
detailInfo = flangeTypeMatch[1].trim();
}
}
} else if (category === 'VALVE') {
itemName = 'VALVE';
} else if (category === 'GASKET') {
// 가스켓 상세 타입 추출
if (material.gasket_details) {
const gasketType = material.gasket_details.gasket_type || '';
const gasketSubtype = material.gasket_details.gasket_subtype || '';
if (gasketSubtype && gasketSubtype !== gasketType) {
itemName = gasketSubtype;
} else if (gasketType) {
itemName = gasketType;
// 밸브 상세 타입 표시
const valveDetails = material.valve_details || {};
const valveType = valveDetails.valve_type || '';
if (valveType === 'GATE') {
itemName = '게이트 밸브';
} else if (valveType === 'BALL') {
itemName = '볼 밸브';
} else if (valveType === 'GLOBE') {
itemName = '글로브 밸브';
} else if (valveType === 'CHECK') {
itemName = '체크 밸브';
} else if (valveType === 'BUTTERFLY') {
itemName = '버터플라이 밸브';
} else if (valveType === 'NEEDLE') {
itemName = '니들 밸브';
} else if (valveType === 'RELIEF') {
itemName = '릴리프 밸브';
} else {
// description에서 추출
const desc = cleanDescription.toUpperCase();
if (desc.includes('GATE')) {
itemName = '게이트 밸브';
} else if (desc.includes('BALL')) {
itemName = '볼 밸브';
} else if (desc.includes('GLOBE')) {
itemName = '글로브 밸브';
} else if (desc.includes('CHECK')) {
itemName = '체크 밸브';
} else if (desc.includes('BUTTERFLY')) {
itemName = '버터플라이 밸브';
} else if (desc.includes('NEEDLE')) {
itemName = '니들 밸브';
} else if (desc.includes('RELIEF')) {
itemName = '릴리프 밸브';
} else {
itemName = 'GASKET';
itemName = '밸브';
}
}
} else if (category === 'GASKET') {
// 가스켓 상세 타입 표시
const gasketDetails = material.gasket_details || {};
const gasketType = gasketDetails.gasket_type || '';
const gasketSubtype = gasketDetails.gasket_subtype || '';
if (gasketType === 'SPIRAL_WOUND') {
itemName = '스파이럴 워운드 가스켓';
} else if (gasketType === 'RING_JOINT') {
itemName = '링 조인트 가스켓';
} else if (gasketType === 'FULL_FACE') {
itemName = '풀 페이스 가스켓';
} else if (gasketType === 'RAISED_FACE') {
itemName = '레이즈드 페이스 가스켓';
} else if (gasketSubtype && gasketSubtype !== gasketType) {
itemName = gasketSubtype;
} else if (gasketType) {
itemName = gasketType;
} else {
// gasket_details가 없으면 description에서 추출
if (cleanDescription.includes('SWG') || cleanDescription.includes('SPIRAL')) {
itemName = 'SWG';
} else if (cleanDescription.includes('RTJ') || cleanDescription.includes('RING')) {
itemName = 'RTJ';
} else if (cleanDescription.includes('FF') || cleanDescription.includes('FULL FACE')) {
itemName = 'FF';
} else if (cleanDescription.includes('RF') || cleanDescription.includes('RAISED')) {
itemName = 'RF';
const desc = cleanDescription.toUpperCase();
if (desc.includes('SWG') || desc.includes('SPIRAL')) {
itemName = '스파이럴 워운드 가스켓';
} else if (desc.includes('RTJ') || desc.includes('RING')) {
itemName = '링 조인트 가스켓';
} else if (desc.includes('FF') || desc.includes('FULL FACE')) {
itemName = '풀 페이스 가스켓';
} else if (desc.includes('RF') || desc.includes('RAISED')) {
itemName = '레이즈드 페이스 가스켓';
} else {
itemName = 'GASKET';
itemName = '가스켓';
}
}
} else if (category === 'BOLT') {
itemName = 'BOLT';
// 볼트 상세 타입 표시
const boltDetails = material.bolt_details || {};
const boltType = boltDetails.bolt_type || '';
if (boltType === 'HEX_BOLT') {
itemName = '육각 볼트';
} else if (boltType === 'STUD_BOLT') {
itemName = '스터드 볼트';
} else if (boltType === 'U_BOLT') {
itemName = '유볼트';
} else if (boltType === 'FLANGE_BOLT') {
itemName = '플랜지 볼트';
} else if (boltType === 'PSV_BOLT') {
itemName = 'PSV 볼트';
} else if (boltType === 'LT_BOLT') {
itemName = '저온용 볼트';
} else if (boltType === 'CK_BOLT') {
itemName = '체크밸브용 볼트';
} else {
// description에서 추출
const desc = cleanDescription.toUpperCase();
if (desc.includes('PSV')) {
itemName = 'PSV 볼트';
} else if (desc.includes('LT')) {
itemName = '저온용 볼트';
} else if (desc.includes('CK')) {
itemName = '체크밸브용 볼트';
} else if (desc.includes('STUD')) {
itemName = '스터드 볼트';
} else if (desc.includes('U-BOLT') || desc.includes('U BOLT')) {
itemName = '유볼트';
} else {
itemName = '볼트';
}
}
} else if (category === 'SUPPORT' || category === 'U_BOLT') {
// SUPPORT 카테고리: 타입별 구분
// 서포트 상세 타입 표시
const desc = cleanDescription.toUpperCase();
if (desc.includes('URETHANE') || desc.includes('BLOCK SHOE')) {
itemName = 'URETHANE BLOCK SHOE';
itemName = '우레탄 블록 슈';
} else if (desc.includes('CLAMP')) {
itemName = 'CLAMP';
itemName = '클램프';
} else if (desc.includes('U-BOLT') || desc.includes('U BOLT')) {
itemName = 'U-BOLT';
itemName = '유볼트';
} else if (desc.includes('HANGER')) {
itemName = '행거';
} else if (desc.includes('SPRING')) {
itemName = '스프링 서포트';
} else {
itemName = 'SUPPORT';
itemName = '서포트';
}
} else {
itemName = category || 'UNKNOWN';
@@ -445,54 +613,138 @@ const formatMaterialForExcel = (material, includeComparison = false) => {
}
}
// 새로운 엑셀 양식에 맞춘 데이터 구조
// 새로운 엑셀 양식: A~E 고정, F~O 카테고리별, P 납기일
const base = {
'TAGNO': '', // 비워둠
'품목명': itemName,
'수량': quantity,
'통화구분': 'KRW', // 기본값
'단가': 1, // 일괄 1로 설정
'크기': material.size_spec || '-',
'압력등급': pressure
'TAGNO': '', // A열: 비워둠
'품목명': itemName, // B열: 카테고리별 상세 타입
'수량': quantity, // C열: 수량
'통화구분': 'KRW', // D열: 기본값
'단가': 1 // E열: 일괄 1로 설정
};
// 카테고리별 전용 컬럼 구성
if (category === 'GASKET') {
// 가스켓 전용 컬럼 순서
base['타입/구조'] = grade; // H/F/I/O, SWG 등 (스케줄 대신)
base['재질'] = gasketMaterial || '-'; // SS304/GRAPHITE/SS304/SS304
base['두께'] = gasketThickness || '-'; // 4.5mm
base['사용자요구'] = material.user_requirement || '';
base['관리항목8'] = ''; // 빈칸
base['관리항목9'] = ''; // 빈칸
base['관리항목10'] = ''; // 빈칸
base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막
} else if (category === 'BOLT') {
// 볼트 전용 컬럼 순서 (스케줄 → 길이)
base['길이'] = schedule; // 볼트는 길이 정보
base['재질'] = grade;
base['추가요구'] = detailInfo || '-'; // 상세내역 → 추가요구로 변경
base['사용자요구'] = material.user_requirement || '';
base['관리항목1'] = ''; // 빈칸
base['관리항목7'] = ''; // 빈칸
base['관리항목8'] = ''; // 빈칸
base['관리항목9'] = ''; // 빈칸
base['관리항목10'] = ''; // 빈칸
base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막
} else {
// 다른 카테고리는 기존 방식
base['스케줄'] = schedule;
base['재질'] = grade;
base['상세내역'] = detailInfo || '-';
base['사용자요구'] = material.user_requirement || '';
base['관리항목1'] = ''; // 빈칸
base['관리항목7'] = ''; // 빈칸
base['관리항목8'] = ''; // 빈칸
base['관리항목9'] = ''; // 빈칸
base['관리항목10'] = ''; // 빈칸
base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0]; // 가장 마지막
// F~O열: 카테고리별 전용 컬럼 구성 (10개 컬럼)
if (category === 'PIPE') {
// 파이프 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['스케줄'] = schedule; // H열
base['재질'] = grade; // I열
base['제조방법'] = material.pipe_details?.manufacturing_method || '-'; // J열
base['끝단처리'] = material.pipe_details?.end_preparation || '-'; // K열
base['사용자요구'] = material.user_requirement || ''; // L열
base['관리항목1'] = ''; // M열
base['관리항목2'] = ''; // N열
base['관리항목3'] = ''; // O열
} else if (category === 'FITTING') {
// 피팅 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['타입'] = material.fitting_details?.fitting_type || '-'; // H열
base['재질'] = grade; // I열
base['상세내역'] = detailInfo || '-'; // J열
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
} else if (category === 'FLANGE') {
// 플랜지 타입 풀네임 매핑 (영어)
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 = material.flange_details?.flange_type || '';
const rawFacingType = material.flange_details?.facing_type || '';
// 플랜지 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['타입'] = flangeTypeMap[rawFlangeType] || rawFlangeType || 'FLANGE'; // H열
base['재질'] = grade; // I열
base['페이싱'] = facingTypeMap[rawFacingType] || rawFacingType || '-'; // J열
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
} else if (category === 'VALVE') {
// 밸브 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['타입'] = material.valve_details?.valve_type || '-'; // H열
base['재질'] = grade; // I열
base['상세내역'] = detailInfo || '-'; // J열
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
} else if (category === 'GASKET') {
// 가스켓 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['타입/구조'] = grade; // H열: H/F/I/O, SWG 등
base['재질'] = gasketMaterial || '-'; // I열: SS304/GRAPHITE/SS304/SS304
base['두께'] = gasketThickness || '-'; // J열: 4.5mm
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
} else if (category === 'BOLT') {
// 볼트 전용 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['길이'] = schedule; // H열: 볼트는 길이 정보
base['재질'] = grade; // I열
base['추가요구'] = detailInfo || '-'; // J열: 표면처리 등
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
} else {
// 기타 카테고리 기본 컬럼 (F~O)
base['크기'] = material.size_spec || '-'; // F열
base['압력등급'] = pressure; // G열
base['스케줄'] = schedule; // H열
base['재질'] = grade; // I열
base['상세내역'] = detailInfo || '-'; // J열
base['사용자요구'] = material.user_requirement || ''; // K열
base['관리항목1'] = ''; // L열
base['관리항목2'] = ''; // M열
base['관리항목3'] = ''; // N열
base['관리항목4'] = ''; // O열
}
// P열: 납기일 (고정)
base['납기일(YYYY-MM-DD)'] = new Date().toISOString().split('T')[0];
// 비교 모드인 경우 추가 정보
if (includeComparison) {
if (material.previous_quantity !== undefined) {