🔧 자재 분류기 개선 및 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:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user