@@ -55,11 +55,11 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
pipe_patterns = [
( r ' A312 \ s*(TP \ d+[A-Z]*) ' , lambda m : f ' A312 { m . group ( 1 ) } ' ) ,
( r ' A106 \ s*(GR \ .? \ s*[A-Z]+) ' , lambda m : f ' A106 { m . group ( 1 ) } ' ) , # A106 GR.B, A106 GR B 등 전체 보존
( r ' A106 \ s*([A-Z]+) ' , lambda m : f ' A106 GR. { m . group ( 1 ) } ' ) , # A106 B → A106 GR.B
( r ' A106 \ s*([A-Z]+) ' , lambda m : f ' A106 GR. { m . group ( 1 ) } ' ) , # A106 B -> A106 GR.B
( r ' A333 \ s*(GR \ .? \ s*[A-Z0-9]+) ' , lambda m : f ' A333 { m . group ( 1 ) } ' ) , # A333 GR.6 등 전체 보존
( r ' A333 \ s*([A-Z0-9]+) ' , lambda m : f ' A333 GR. { m . group ( 1 ) } ' ) , # A333 6 → A333 GR.6
( r ' A333 \ s*([A-Z0-9]+) ' , lambda m : f ' A333 GR. { m . group ( 1 ) } ' ) , # A333 6 -> A333 GR.6
( r ' A53 \ s*(GR \ .? \ s*[A-Z]+) ' , lambda m : f ' A53 { m . group ( 1 ) } ' ) , # A53 GR.B 등 전체 보존
( r ' A53 \ s*([A-Z]+) ' , lambda m : f ' A53 GR. { m . group ( 1 ) } ' ) , # A53 B → A53 GR.B
( r ' A53 \ s*([A-Z]+) ' , lambda m : f ' A53 GR. { m . group ( 1 ) } ' ) , # A53 B -> A53 GR.B
( r ' A335 \ s*(P \ d+[A-Z]*) ' , lambda m : f ' A335 { m . group ( 1 ) } ' ) ,
( r ' STPG \ s*( \ d+) ' , lambda m : f ' STPG { m . group ( 1 ) } ' ) ,
( r ' STS \ s*( \ d+[A-Z]*) ' , lambda m : f ' STS { m . group ( 1 ) } ' )
@@ -77,10 +77,10 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
( r ' A234 \ s*(WP[A-Z]+) ' , lambda m : f ' A234 { m . group ( 1 ) } ' ) ,
( r ' A420 \ s*(WPL \ d+) ' , lambda m : f ' A420 { m . group ( 1 ) } ' ) ,
( r ' A105 \ s*(GR \ .? \ s*[N]) ' , lambda m : f ' A105 { m . group ( 1 ) } ' ) , # A105 GR.N 전체 보존
( r ' A105 \ s*([N]) ' , lambda m : f ' A105 GR. { m . group ( 1 ) } ' ) , # A105 N → A105 GR.N
( r ' A105 \ s*([N]) ' , lambda m : f ' A105 GR. { m . group ( 1 ) } ' ) , # A105 N -> A105 GR.N
( r ' A105(?! \ s*[A-Z]) ' , lambda m : f ' A105 ' ) , # A105만 있는 경우
( r ' A106 \ s*(GR \ .? \ s*[A-Z]+) ' , lambda m : f ' A106 { m . group ( 1 ) } ' ) , # A106 GR.B 전체 보존
( r ' A106 \ s*([A-Z]+) ' , lambda m : f ' A106 GR. { m . group ( 1 ) } ' ) , # A106 B → A106 GR.B
( r ' A106 \ s*([A-Z]+) ' , lambda m : f ' A106 GR. { m . group ( 1 ) } ' ) , # A106 B -> A106 GR.B
( r ' A182 \ s*(F \ d+[A-Z]*) ' , lambda m : f ' A182 { m . group ( 1 ) } ' ) ,
( r ' A350 \ s*(LF \ d+) ' , lambda m : f ' A350 { m . group ( 1 ) } ' )
]
@@ -95,7 +95,7 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
flange_patterns = [
( r ' A182 \ s*(F \ d+[A-Z]*) ' , lambda m : f ' A182 { m . group ( 1 ) } ' ) ,
( r ' A105 \ s*(GR \ .? \ s*[N]) ' , lambda m : f ' A105 { m . group ( 1 ) } ' ) , # A105 GR.N 전체 보존
( r ' A105 \ s*([N]) ' , lambda m : f ' A105 GR. { m . group ( 1 ) } ' ) , # A105 N → A105 GR.N
( r ' A105 \ s*([N]) ' , lambda m : f ' A105 GR. { m . group ( 1 ) } ' ) , # A105 N -> A105 GR.N
( r ' A105(?! \ s*[A-Z]) ' , lambda m : f ' A105 ' ) , # A105만 있는 경우
( r ' A350 \ s*(LF \ d+) ' , lambda m : f ' A350 { m . group ( 1 ) } ' ) ,
( r ' A694 \ s*(F \ d+) ' , lambda m : f ' A694 { m . group ( 1 ) } ' )
@@ -398,16 +398,16 @@ async def upload_file(
# 🔥 구매확정된 자재 매핑 정보 저장
purchased_materials_map = revision_comparison . get ( " purchased_materials_map " , { } )
print ( f " - 구매확정 자재 매핑: { len ( purchased_materials_map ) } 개 " )
logger . info ( f " - 구매확정 자재 매핑: { len ( purchased_materials_map ) } 개 " )
else :
print ( " 📝 이전 구매확정 자료 없음 - 전체 자재 분류" )
logger . info ( " 이전 구매확정 자료 없음 - 전체 자재 분류 " )
except Exception as e :
pr int ( f " ⚠️ 리비전 비교 실패, 전체 자재 분류로 진행: { str ( e ) } " )
logger . warn ing ( f " 리비전 비교 실패, 전체 자재 분류로 진행: { str ( e ) } " )
import traceback
traceback . print_exc ( )
print ( f " 🔧 자재 분류 및 저장 시작: { len ( materials_to_classify ) } 개 자재 " )
logger . info ( f " 자재 분류 및 저장 시작: { len ( materials_to_classify ) } 개 자재 " )
# [변경] MaterialService를 사용하여 자재 처리 및 저장
materials_inserted = MaterialService . process_and_save_materials (
@@ -418,7 +418,7 @@ async def upload_file(
)
db . commit ( )
print ( f " 자재 저장 완료: { materials_inserted } 개 " )
logger . info ( f " 자재 저장 완료: { materials_inserted } 개 " )
# [변경] MaterialService를 사용하여 구매신청 정보 상속
if parent_file_id is not None :
@@ -439,9 +439,9 @@ async def upload_file(
ip_address = request . client . host if request . client else None ,
user_agent = request . headers . get ( ' user-agent ' )
)
print ( f " 활동 로그 기록 완료: { username } - 파일 업로드 " )
logger . info ( f " 활동 로그 기록 완료: { username } - 파일 업로드 " )
except Exception as e :
print ( f " 활동 로그 기록 실패: { str ( e ) } " )
logger . error ( f " 활동 로그 기록 실패: { str ( e ) } " )
# 로그 실패는 업로드 성공에 영향을 주지 않음
# 리비전 업로드인 경우 누락된 도면 감지 및 구매신청 여부 확인
@@ -460,8 +460,8 @@ async def upload_file(
purchase_result = db . execute ( purchase_check_query , { " parent_file_id " : parent_file_id } ) . fetchone ( )
has_previous_purchase = purchase_result . purchase_count > 0
print ( f " 📦 이전 리비전 구매신청 여부: { has_previous_purchase } ( { purchase_result . purchase_count } 개 자재) " )
print ( f " 📂 parent_file_id: { parent_file_id } , new file_id: { file_id } " )
logger . info ( f " 이전 리비전 구매신청 여부: { has_previous_purchase } ( { purchase_result . purchase_count } 개 자재) " )
logger . info ( f " parent_file_id: { parent_file_id } , new file_id: { file_id } " )
# 이전 리비전의 도면 목록 조회
prev_drawings_query = text ( """
@@ -472,7 +472,7 @@ async def upload_file(
GROUP BY drawing_name, line_no
""" )
prev_drawings_result = db . execute ( prev_drawings_query , { " parent_file_id " : parent_file_id } ) . fetchall ( )
print ( f " 📋 이전 리비전 도면 수: { len ( prev_drawings_result ) } " )
logger . info ( f " 이전 리비전 도면 수: { len ( prev_drawings_result ) } " )
# 새 리비전의 도면 목록 조회
new_drawings_query = text ( """
@@ -482,7 +482,7 @@ async def upload_file(
AND (drawing_name IS NOT NULL OR line_no IS NOT NULL)
""" )
new_drawings_result = db . execute ( new_drawings_query , { " file_id " : file_id } ) . fetchall ( )
print ( f " 📋 새 리비전 도면 수: { len ( new_drawings_result ) } " )
logger . info ( f " 새 리비전 도면 수: { len ( new_drawings_result ) } " )
prev_drawings = set ( )
for row in prev_drawings_result :
@@ -500,9 +500,9 @@ async def upload_file(
missing_drawings = prev_drawings - new_drawings
print ( f " 📊 이전 도면: { list ( prev_drawings ) [ : 5 ] } " )
print ( f " 📊 새 도면: { list ( new_drawings ) [ : 5 ] } " )
print ( f " ❌ 누락 도면: { len ( missing_drawings ) } 개 " )
logger . info ( f " 이전 도면: { list ( prev_drawings ) [ : 5 ] } " )
logger . info ( f " 새 도면: { list ( new_drawings ) [ : 5 ] } " )
logger . error ( f " 누락 도면: { len ( missing_drawings ) } 개 " )
if missing_drawings :
# 누락된 도면의 자재 상세 정보
@@ -543,9 +543,9 @@ async def upload_file(
} )
db . commit ( )
print ( f " ✅ 누락 도면 자재 상태 업데이트: { len ( missing_drawings ) } 개 도면 → { status_to_set } " )
logger . info ( f " 누락 도면 자재 상태 업데이트: { len ( missing_drawings ) } 개 도면 -> { status_to_set } " )
except Exception as e :
print ( f " 누락 도면 감지 실패: { str ( e ) } " )
logger . error ( f " 누락 도면 감지 실패: { str ( e ) } " )
db . rollback ( )
# 감지 실패는 업로드 성공에 영향 없음
@@ -591,8 +591,8 @@ async def upload_file(
except Exception as e :
import traceback
error_details = traceback . format_exc ( )
print ( f " ❌ 파일 업로드 실패 - 상세 에러:" )
print ( error_details )
logger . error ( f " 파일 업로드 실패 - 상세 에러: " )
logger . error ( error_details )
db . rollback ( )
if os . path . exists ( file_path ) :
@@ -1431,7 +1431,7 @@ async def get_materials(
pass
# 밸브 그룹별로 대표 밸브 하나만 추가 (그룹핑된 정보로)
print ( f " DEBUG: 전체 밸브 수: { valve_count } , valve_groups 수: { len ( valve_groups ) } " )
logger . info ( f " 전체 밸브 수: { valve_count } , valve_groups 수: { len ( valve_groups ) } " )
try :
for valve_key , group_info in valve_groups . items ( ) :
if group_info [ " materials " ] :
@@ -1441,14 +1441,14 @@ async def get_materials(
if ' valve_details ' in representative_valve :
representative_valve [ ' valve_details ' ] [ ' group_total_quantity ' ] = group_info [ " total_quantity " ]
material_list . append ( representative_valve )
print ( f " DEBUG: 밸브 추가됨 - { valve_key } , 수량: { group_info [ ' total_quantity ' ] } " )
logger . info ( f " 밸브 추가됨 - { valve_key } , 수량: { group_info [ ' total_quantity ' ] } " )
except Exception as valve_error :
print ( f " ERROR: 밸브 그룹핑 실패 - { valve_error } " )
logger . error ( f " 밸브 그룹핑 실패 - { valve_error } " )
# 밸브 그룹핑 실패시에도 계속 진행
pass
# 볼트 그룹별로 대표 볼트 하나만 추가 (그룹핑된 정보로)
print ( f " DEBUG: bolt_groups 수: { len ( bolt_groups ) } " )
logger . info ( f " bolt_groups 수: { len ( bolt_groups ) } " )
try :
for bolt_key , group_info in bolt_groups . items ( ) :
if group_info [ " materials " ] :
@@ -1458,14 +1458,14 @@ async def get_materials(
if ' bolt_details ' in representative_bolt :
representative_bolt [ ' bolt_details ' ] [ ' group_total_quantity ' ] = group_info [ " total_quantity " ]
material_list . append ( representative_bolt )
print ( f " DEBUG: 볼트 추가됨 - { bolt_key } , 수량: { group_info [ ' total_quantity ' ] } " )
logger . info ( f " 볼트 추가됨 - { bolt_key } , 수량: { group_info [ ' total_quantity ' ] } " )
except Exception as bolt_error :
print ( f " ERROR: 볼트 그룹핑 실패 - { bolt_error } " )
logger . error ( f " 볼트 그룹핑 실패 - { bolt_error } " )
# 볼트 그룹핑 실패시에도 계속 진행
pass
# 가스켓 그룹별로 대표 가스켓 하나만 추가 (그룹핑된 정보로)
print ( f " DEBUG: gasket_groups 수: { len ( gasket_groups ) } " )
logger . info ( f " gasket_groups 수: { len ( gasket_groups ) } " )
try :
for gasket_key , group_info in gasket_groups . items ( ) :
if group_info [ " materials " ] :
@@ -1475,23 +1475,23 @@ async def get_materials(
if ' gasket_details ' in representative_gasket :
representative_gasket [ ' gasket_details ' ] [ ' group_total_quantity ' ] = group_info [ " total_quantity " ]
material_list . append ( representative_gasket )
print ( f " DEBUG: 가스켓 추가됨 - { gasket_key } , 수량: { group_info [ ' total_quantity ' ] } " )
logger . info ( f " 가스켓 추가됨 - { gasket_key } , 수량: { group_info [ ' total_quantity ' ] } " )
except Exception as gasket_error :
print ( f " ERROR: 가스켓 그룹핑 실패 - { gasket_error } " )
logger . error ( f " 가스켓 그룹핑 실패 - { gasket_error } " )
# 가스켓 그룹핑 실패시에도 계속 진행
pass
# UNKNOWN 그룹별로 대표 항목 하나만 추가 (그룹핑된 정보로)
print ( f " DEBUG: unknown_groups 수: { len ( unknown_groups ) } " )
logger . info ( f " unknown_groups 수: { len ( unknown_groups ) } " )
try :
for unknown_key , group_info in unknown_groups . items ( ) :
if group_info [ " materials " ] :
representative_unknown = group_info [ " materials " ] [ 0 ] . copy ( )
representative_unknown [ ' quantity ' ] = group_info [ " total_quantity " ]
material_list . append ( representative_unknown )
print ( f " DEBUG: UNKNOWN 추가됨 - { unknown_key [ : 50 ] } , 수량: { group_info [ ' total_quantity ' ] } " )
logger . info ( f " UNKNOWN 추가됨 - { unknown_key [ : 50 ] } , 수량: { group_info [ ' total_quantity ' ] } " )
except Exception as unknown_error :
print ( f " ERROR: UNKNOWN 그룹핑 실패 - { unknown_error } " )
logger . error ( f " UNKNOWN 그룹핑 실패 - { unknown_error } " )
# UNKNOWN 그룹핑 실패시에도 계속 진행
pass
@@ -2215,7 +2215,7 @@ async def verify_material_classification(
f " 자재 분류 검증: { material . original_description } "
)
except Exception as e :
print ( f " 활동 로그 기록 실패: { str ( e ) } " )
logger . error ( f " 활동 로그 기록 실패: { str ( e ) } " )
db . commit ( )
@@ -2299,7 +2299,7 @@ async def update_material_classification(
except Exception as e :
db . rollback ( )
print ( f " 자재 분류 업데이트 실패: { str ( e ) } " )
logger . error ( f " 자재 분류 업데이트 실패: { str ( e ) } " )
raise HTTPException ( status_code = 500 , detail = f " 자재 분류 업데이트 실패: { str ( e ) } " )
@router.post ( " /materials/confirm-purchase " )
@@ -2412,7 +2412,7 @@ async def confirm_material_purchase_api(
except Exception as e :
db . rollback ( )
print ( f " 구매수량 확정 실패: { str ( e ) } " )
logger . error ( f " 구매수량 확정 실패: { str ( e ) } " )
raise HTTPException ( status_code = 500 , detail = f " 구매수량 확정 실패: { str ( e ) } " )
@@ -2918,7 +2918,7 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
previous_materials = previous_result . fetchall ( )
if not previous_materials :
print ( " 📝 이전 자료가 없음 - 전체 자재 분류" )
logger . info ( " 이전 자료가 없음 - 전체 자재 분류 " )
return {
" has_purchased_materials " : False ,
" message " : " 이전 자료가 없습니다. " ,
@@ -2985,18 +2985,18 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
new_count = 0
excluded_purchased_count = 0
print ( f " \n { ' = ' * 60 } " )
print ( f " 🔍 리비전 비교 시작" )
print ( f " { ' = ' * 60 } " )
print ( f " 📊 이전 자재: { len ( previous_dict ) } 개 (구매확정: { len ( purchased_dict ) } 개, 미구매: { len ( unpurchased_dict ) } 개) " )
print ( f " 📊 신규 자재: { len ( new_dict ) } 개 " )
logger . info ( f " \n { ' = ' * 60 } " )
logger . info ( f " 리비전 비교 시작 " )
logger . info ( f " { ' = ' * 60 } " )
logger . info ( f " 이전 자재: { len ( previous_dict ) } 개 (구매확정: { len ( purchased_dict ) } 개, 미구매: { len ( unpurchased_dict ) } 개) " )
logger . info ( f " 신규 자재: { len ( new_dict ) } 개 " )
# 4-1. 신규 자재 확인 (구매확정된 자재는 제외)
for key , new_material in new_dict . items ( ) :
if key in purchased_dict :
# ✅ 구매확정된 자재는 완전히 제외
excluded_purchased_count + = 1
print ( f " 🚫 구매확정 자재 제외: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
logger . info ( f " 구매확정 자재 제외: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
elif key in unpurchased_dict :
# 미구매 자재인데 새 리비전에도 있음
previous_material = unpurchased_dict [ key ]
@@ -3014,20 +3014,20 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
if prev_pipes_needed != new_pipes_needed :
# 필요한 본수가 변경됨 - 분류 필요
print ( f " 🔧 PIPE 본수 변경: { new_material . get ( ' original_description ' , ' ' ) [ : 40 ] } ... "
f " { prev_qty } mm( { prev_pipes_needed } 본) → { new_qty } mm( { new_pipes_needed } 본) " )
logger . info ( f " PIPE 본수 변경: { new_material . get ( ' original_description ' , ' ' ) [ : 40 ] } ... "
f " { prev_qty } mm( { prev_pipes_needed } 본) -> { new_qty } mm( { new_pipes_needed } 본) " )
materials_to_classify . append ( new_material )
new_count + = 1
else :
# 필요한 본수 동일 - 기존 분류 유지
print ( f " ♻️ PIPE 본수 유지: { new_material . get ( ' original_description ' , ' ' ) [ : 40 ] } ... "
f " { prev_qty } mm → { new_qty } mm ( { new_pipes_needed } 본) " )
logger . info ( f " PIPE 본수 유지: { new_material . get ( ' original_description ' , ' ' ) [ : 40 ] } ... "
f " { prev_qty } mm -> { new_qty } mm ( { new_pipes_needed } 본) " )
else :
# 기타 자재: 기존 분류 유지
print ( f " ♻️ 기존 미구매 자재 유지: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
logger . info ( f " 기존 미구매 자재 유지: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
else :
# ✅ 완전히 새로운 자재 - 분류 필요
print ( f " ➕ 신규 자재: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
logger . info ( f " 신규 자재: { new_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
materials_to_classify . append ( new_material )
new_count + = 1
@@ -3036,15 +3036,15 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
if key not in new_dict :
# ✅ 이전에는 있었지만 새 리비전에는 없음
removed_materials . append ( previous_material )
print ( f " ➖ 삭제된 자재: { previous_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
logger . info ( f " 삭제된 자재: { previous_material . get ( ' original_description ' , ' ' ) [ : 50 ] } ... " )
print ( f " \n { ' = ' * 60 } " )
print ( f " 📊 비교 결과" )
print ( f " { ' = ' * 60 } " )
print ( f " ✅ 신규 자재: { new_count } 개 (분류 필요) " )
print ( f " ➖ 삭제 자재: { len ( removed_materials ) } 개 " )
print ( f " 🚫 구매확정 제외: { excluded_purchased_count } 개 " )
print ( f " { ' = ' * 60 } \n " )
logger . info ( f " \n { ' = ' * 60 } " )
logger . info ( f " 비교 결과 " )
logger . info ( f " { ' = ' * 60 } " )
logger . info ( f " 신규 자재: { new_count } 개 (분류 필요) " )
logger . info ( f " 삭제 자재: { len ( removed_materials ) } 개 " )
logger . info ( f " 구매확정 제외: { excluded_purchased_count } 개 " )
logger . info ( f " { ' = ' * 60 } \n " )
return {
" has_purchased_materials " : len ( purchased_dict ) > 0 ,
@@ -3061,7 +3061,7 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
}
except Exception as e :
print ( f " ❌ 리비전 비교 실패: { str ( e ) } " )
logger . error ( f " 리비전 비교 실패: { str ( e ) } " )
import traceback
traceback . print_exc ( )
return {