diff options
| author | joonhoekim <26rote@gmail.com> | 2025-06-27 01:25:48 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-06-27 01:25:48 +0000 |
| commit | 15b2d4ff61d0339385edd8cc67bf7579fcc2af08 (patch) | |
| tree | f0c36724855abccf705a9cdcae6fa3efd54d996d /public/wsdl/_util/update_wsdl_with_csv.py | |
| parent | e9897d416b3e7327bbd4d4aef887eee37751ae82 (diff) | |
(김준회) MDG SOAP 수신 유틸리티 및 API 엔드포인트, 스키마
Diffstat (limited to 'public/wsdl/_util/update_wsdl_with_csv.py')
| -rw-r--r-- | public/wsdl/_util/update_wsdl_with_csv.py | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/public/wsdl/_util/update_wsdl_with_csv.py b/public/wsdl/_util/update_wsdl_with_csv.py new file mode 100644 index 00000000..91a9d4dc --- /dev/null +++ b/public/wsdl/_util/update_wsdl_with_csv.py @@ -0,0 +1,674 @@ +#!/usr/bin/env python3 +import csv +import re +import shutil +import os +from datetime import datetime + +# 컬러 로그를 위한 색상 코드 추가 +class Colors: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + WHITE = '\033[97m' + ENDC = '\033[0m' # End color + BOLD = '\033[1m' + +def print_color(message, color=Colors.WHITE): + """컬러 출력 함수""" + print(f"{color}{message}{Colors.ENDC}") + +def print_error(message): + """에러 메시지 출력""" + print_color(f"❌ ERROR: {message}", Colors.RED) + +def print_warning(message): + """경고 메시지 출력""" + print_color(f"⚠️ WARNING: {message}", Colors.YELLOW) + +def print_success(message): + """성공 메시지 출력""" + print_color(f"✅ SUCCESS: {message}", Colors.GREEN) + +def print_info(message): + """정보 메시지 출력""" + print_color(f"ℹ️ INFO: {message}", Colors.CYAN) + +""" +실제 CSV 파일들 +IF_MDZ_EVCP_CUSTOMER_MASTER.csv IF_MDZ_EVCP_EMPLOYEE_REFERENCE.csv IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN.csv IF_MDZ_EVCP_PROJECT_MASTER.csv +IF_MDZ_EVCP_DEPARTMENT_CODE.csv IF_MDZ_EVCP_EQUP_MASTER.csv IF_MDZ_EVCP_MODEL_MASTER.csv IF_MDZ_EVCP_VENDOR_MASTER.csv +IF_MDZ_EVCP_EMPLOYEE_MASTER.csv IF_MDZ_EVCP_MATERIAL_MASTER_PART.csv IF_MDZ_EVCP_ORGANIZATION_MASTER.csv +""" + +# ===== 설정 ===== +CSV_DIR = './public/wsdl/_csv' +WSDL_DIR = './public/wsdl' + +# 발견된 SAP 타입들을 수집하기 위한 전역 SET +discovered_sap_types = set() +type_size_combinations = set() # 타입-사이즈 조합도 수집 + +# 필드명 매핑 테이블 (CSV -> WSDL) +FIELD_MAPPING = { + # 개별 WSDL 별 테이블 만들기로 했으므로 사용하지 않고 WSDL 그대로 사용 + # 'ADR_NO': 'ADDRNO', + # 필요한 경우 더 추가 +} + +# 테이블 매핑 테이블 (complexType -> CSV Table) +TABLE_MAPPING = { + # 'MATL': 'MATL', + # 'UNIT': 'MATL/UNIT', + # 필요한 경우 더 추가 +} + +def normalize_sap_type_and_size(sap_type, size_str): + """SAP 타입과 사이즈를 정규화""" + global discovered_sap_types, type_size_combinations + + try: + # 타입을 대문자로 변환 + normalized_type = sap_type.upper().strip() if sap_type else 'CHAR' + + # 사이즈 처리 + normalized_size = size_str.strip() if size_str else '' + original_size = normalized_size # 원본 사이즈 보존 (로깅용) + + # 빈 사이즈인 경우 기본값 설정 + if not normalized_size: + normalized_size = '255' + else: + # 따옴표로 감싸진 경우 제거 (예: "1,0") + quote_removed = False + if normalized_size.startswith('"') and normalized_size.endswith('"'): + before_quote_removal = normalized_size + normalized_size = normalized_size[1:-1] + quote_removed = True + print_color(f"🔍 SIZE 파싱: 따옴표 제거 - '{before_quote_removal}' -> '{normalized_size}' (Type: {normalized_type})", Colors.YELLOW) + + # 로깅: 최종 결과 (따옴표가 없는 경우만) + if not quote_removed and original_size: + print_color(f"🔍 SIZE 파싱: 따옴표 없음 - '{original_size}' 그대로 사용 (Type: {normalized_type})", Colors.BLUE) + + # 발견된 타입들을 SET에 추가 + discovered_sap_types.add(normalized_type) + type_size_combinations.add(f"{normalized_type}({normalized_size})") + + # 컬럼 구분자나 특수문자가 있는 경우 그대로 유지 + # DEC, QUAN, NUMB 등에서 "1,0" 형태의 사이즈는 정상 + + return normalized_type, normalized_size + + except Exception as e: + print_error(f"타입/사이즈 정규화 실패 - Type: {sap_type}, Size: {size_str}, Error: {str(e)}") + return 'CHAR', '255' # 기본값 반환 + +def safe_description_escape(description): + """Description 필드의 특수문자를 안전하게 처리""" + try: + if not description: + return '' + + # HTML/XML 특수문자 이스케이프 + description = description.replace('&', '&') + description = description.replace('<', '<') + description = description.replace('>', '>') + description = description.replace('"', '"') + description = description.replace("'", ''') + + return description + + except Exception as e: + print_error(f"Description 이스케이프 실패: {description}, Error: {str(e)}") + return str(description) if description else '' + +def get_csv_files(): + """CSV 디렉토리에서 모든 CSV 파일 목록을 가져옴""" + csv_files = [] + for file in os.listdir(CSV_DIR): + if file.endswith('.csv'): + csv_files.append(file.replace('.csv', '')) + return csv_files + +def get_complex_type_info(wsdl_content): + """WSDL 파일에서 complexType 정보를 추출""" + complex_types = {} + current_type = None + current_fields = [] + type_stack = [] # 중첩된 complexType을 추적하기 위한 스택 + + for line in wsdl_content: + # complexType 시작 태그 찾기 + type_match = re.search(r'<xsd:complexType\s+name="([^"]+)"', line) + if type_match: + if current_type: + type_stack.append(current_type) + current_type = type_match.group(1) + current_fields = [] + continue + + # complexType 종료 태그 찾기 + if '</xsd:complexType>' in line: + if current_type: + complex_types[current_type] = current_fields + if type_stack: + current_type = type_stack.pop() + else: + current_type = None + continue + + # element 태그 찾기 + element_match = re.search(r'<xsd:element\s+name="([^"]+)"', line) + if element_match and current_type: + field_info = { + 'name': element_match.group(1), + 'type': re.search(r'type="([^"]+)"', line).group(1) if 'type="' in line else None, + 'is_array': 'maxOccurs="unbounded"' in line + } + current_fields.append(field_info) + + return complex_types + +def get_table_for_complex_type(table_name, complex_type): + """테이블 이름에서 complexType에 해당하는 부분 추출""" + # 테이블 이름이 '/'로 구분되어 있다면 마지막 부분을 반환 + if '/' in table_name: + return table_name.split('/')[-1].upper() + return table_name.upper() + +def load_csv_data(csv_file): + """CSV 파일에서 필드 정보를 딕셔너리로 로드""" + csv_data = {} + csv_path = os.path.join(CSV_DIR, f'{csv_file}.csv') + + try: + with open(csv_path, 'r', encoding='utf-8-sig') as f: # BOM 처리 + reader = csv.DictReader(f) + for row_num, row in enumerate(reader, start=2): # 헤더 다음부터 2행 + try: + field_name = row['Field'] + table_name = row['Table'] + + # 매핑된 필드명이 있으면 사용, 없으면 원래 필드명 사용 + wsdl_field_name = FIELD_MAPPING.get(field_name, field_name) + + # 테이블 정보를 키에 포함 (구분자를 || 로 변경) + key = f"{wsdl_field_name}||{table_name}" + + # 타입과 사이즈 정규화 + normalized_type, normalized_size = normalize_sap_type_and_size( + row.get('Type', ''), row.get('Size', '') + ) + + # Description 안전 처리 + safe_desc = safe_description_escape(row.get('Description', '')) + + csv_data[key] = { + 'seq': row.get('SEQ', ''), + 'table': table_name, + 'field': row.get('Field', ''), # 원래 CSV 필드명 저장 + 'mo': row.get('M/O', ''), + 'type': normalized_type, # 정규화된 타입 + 'size': normalized_size, # 정규화된 사이즈 + 'description': safe_desc, # 안전 처리된 Description + 'original_type': row.get('Type', ''), # 원본 타입 보존 + 'original_size': row.get('Size', '') # 원본 사이즈 보존 + } + + except Exception as e: + print_error(f"CSV 행 {row_num} 처리 실패 - {csv_file}: {str(e)}") + print_error(f"문제 행 데이터: {row}") + continue + + except Exception as e: + print_error(f"CSV 파일 로딩 실패 - {csv_path}: {str(e)}") + return {} + + return csv_data + +def extract_field_name_from_line(line): + """라인에서 name="필드명" 추출""" + match = re.search(r'name="([^"]+)"', line) + return match.group(1) if match else None + +def extract_field_from_comment(comment_line): + """주석에서 Field: 부분의 필드명 추출""" + match = re.search(r'Field:([^,]+)', comment_line) + return match.group(1).strip() if match else None + +def has_seq_in_comment(comment_line): + """주석에 SEQ가 있는지 확인""" + return 'SEQ:' in comment_line + +def get_indentation(line): + """라인의 들여쓰기 반환""" + return len(line) - len(line.lstrip()) + +def create_comment(field_name, csv_data, indentation, complex_type): + """CSV 데이터를 기반으로 주석 생성""" + try: + # 필드명으로 시작하는 키들을 찾음 (대소문자 구분 없이) + matching_keys = [key for key in csv_data.keys() if key.split('||')[0].upper() == field_name.upper()] + if not matching_keys: + indent = ' ' * indentation + print_warning(f"매칭되지 않은 필드: {field_name}") + return f"{indent}<!-- TODO: UNMATCHED FIELD OCCURS - {field_name} -->" + + # complexType과 일치하는 테이블 정보 찾기 + matching_data = None + + # 1. complexType 이름과 완전히 일치하는 테이블 찾기 + for key in matching_keys: + table_name = key.split('||', 1)[1] + if complex_type.upper() == table_name.upper(): + matching_data = csv_data[key] + break + + # 2. CSV 테이블명을 '/'로 스플릿한 마지막 부분이 complexType과 일치하는 경우 + if not matching_data: + for key in matching_keys: + table_name = key.split('||', 1)[1] + if '/' in table_name: + last_part = table_name.split('/')[-1] + if complex_type.upper() == last_part.upper(): + matching_data = csv_data[key] + break + + # 3. 필드명만 일치하는 경우 (첫 번째 매칭 데이터 사용) + if not matching_data: + matching_data = csv_data[matching_keys[0]] + + # 4. 매칭된 데이터가 있으면 주석 생성, 없으면 매칭 실패 주석 + if matching_data: + indent = ' ' * indentation + + # CSV의 실제 타입과 사이즈 사용 + comment = f"{indent}<!-- SEQ:{matching_data['seq']}, Table:{matching_data['table']}, Field:{matching_data['field']}, M/O:{matching_data['mo']}, Type:{matching_data['type']}, Size:{matching_data['size']}, Description:{matching_data['description']} -->" + + print_info(f"주석 생성 완료: {field_name} -> Type:{matching_data['type']}, Size:{matching_data['size']}") + return comment + else: + indent = ' ' * indentation + print_warning(f"매칭 데이터를 찾을 수 없음: {field_name}") + return f"{indent}<!-- TODO: NO MATCHING DATA FOUND - {field_name} -->" + + except Exception as e: + indent = ' ' * indentation + print_error(f"주석 생성 실패 - 필드: {field_name}, 에러: {str(e)}") + return f"{indent}<!-- ERROR: COMMENT GENERATION FAILED - {field_name} -->" + +def normalize_comment(comment_line): + """주석을 정규화 (공백 제거, 소문자 변환 등)""" + # <!-- 와 --> 제거하고 내용만 추출 + content = re.sub(r'^\s*<!--\s*|\s*-->\s*$', '', comment_line.strip()) + # 여러 공백을 하나로 통합 + content = re.sub(r'\s+', ' ', content) + return content.strip() + +def comments_are_equal(existing_comment, expected_comment): + """두 주석이 같은 내용인지 비교""" + existing_normalized = normalize_comment(existing_comment) + expected_normalized = normalize_comment(expected_comment) + return existing_normalized == expected_normalized + +def should_process_line(line, csv_data): + """라인이 처리 대상인지 확인""" + # 네 조건을 모두 만족해야 함: + # 1. <xsd:element 태그 + # 2. name=" 속성이 있는 태그 + # 3. maxOccurs=" 속성이 없는 태그 (배열 데이터 제외) + # 4. CSV에 해당 필드가 있는 경우 + + if not ('<xsd:element' in line and 'name="' in line): + return False + + # maxOccurs=" 가 있으면 배열 데이터이므로 제외 (모든 maxOccurs 속성) + if 'maxOccurs="' in line: + return False + + field_name = extract_field_name_from_line(line) + if not field_name: + return False + + # 필드명이 CSV 데이터의 키에 정확히 일치하는지 확인 (대소문자 구분 없이) + return any(field_name.upper() == key.split('||')[0].upper() for key in csv_data.keys()) + +def get_skip_reason(line, csv_data): + """필드를 건너뛰는 이유를 반환""" + if not ('<xsd:element' in line and 'name="' in line): + return None + + field_name = extract_field_name_from_line(line) + if not field_name: + return None + + # maxOccurs 체크 (배열 타입) + if 'maxOccurs="' in line: + return "ARRAY_TYPE" + + # 복합객체인 경우 + if 'MASTER' in field_name: + return "COMPLEX_TYPE" + + # CSV에 있는지 체크 + has_csv_data = any(field_name.upper() == key.split('||')[0].upper() for key in csv_data.keys()) + if not has_csv_data: + # Req로 끝나는 경우는 래퍼 타입이므로 정상 + if field_name.endswith('Req'): + return "REQ_WRAPPER_TYPE" + else: + return "NO_CSV_DATA" + + return None + +def get_table_prefix_from_csv_name(csv_name: str) -> str: + """CSV 파일명에서 테이블 prefix 추출""" + csv_upper = csv_name.upper() + + # CSV 파일명 패턴에서 마스터 타입 추출 + if 'CUSTOMER_MASTER' in csv_upper: + return 'CUSTOMER' + elif 'VENDOR_MASTER' in csv_upper: + return 'VENDOR' + elif 'EMPLOYEE_MASTER' in csv_upper: + return 'EMPLOYEE' + elif 'PROJECT_MASTER' in csv_upper: + return 'PROJECT' + elif 'DEPARTMENT_CODE' in csv_upper: + return 'DEPARTMENT' + elif 'ORGANIZATION_MASTER' in csv_upper: + return 'ORGANIZATION' + elif 'EQUP_MASTER' in csv_upper: + return 'EQUP' + elif 'MODEL_MASTER' in csv_upper: + return 'MODEL' + elif 'MATERIAL_MASTER' in csv_upper: + return 'MATERIAL' + elif 'EMPLOYEE_REFERENCE' in csv_upper: + return 'EMPLOYEE_REF' + else: + # 기본적으로 MDZ 부분 제거 후 첫 번째 단어 사용 + parts = csv_name.replace('IF_MDZ_EVCP_', '').split('_') + return parts[0] if parts else 'COMMON' + +def backup_file(filepath): + """파일을 백업""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = f"{filepath}.backup_{timestamp}" + shutil.copy2(filepath, backup_path) + print(f"백업 파일 생성: {backup_path}") + return backup_path + +def process_wsdl_file(target): + """WSDL 파일 처리""" + csv_file_path = os.path.join(CSV_DIR, f'{target}.csv') + wsdl_file_path = os.path.join(WSDL_DIR, f'{target}.wsdl') + + try: + # 백업 생성 + backup_path = backup_file(wsdl_file_path) + + print_color(f"\n🚀 처리 시작: {target}", Colors.BOLD) + print_info("CSV 데이터 로딩 중...") + csv_data = load_csv_data(target) + print_success(f"CSV에서 {len(csv_data)}개 필드 정보 로드됨") + + # WSDL 파일 읽기 + with open(wsdl_file_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + # complexType 정보 추출 + complex_types = get_complex_type_info(lines) + print_success(f"WSDL에서 {len(complex_types)}개 complexType 정보 추출됨") + except Exception as e: + print_error(f"파일 초기화 실패 - {target}: {str(e)}") + return + + # complexType 구조 출력 (디버깅용) + for type_name, fields in complex_types.items(): + print_color(f"\nComplexType: {type_name}", Colors.MAGENTA) + for field in fields: + print(f" - {field['name']} ({field['type']}) {'[Array]' if field['is_array'] else ''}") + + new_lines = [] + i = 0 + changes_made = 0 + processed_fields = [] + skipped_fields = [] + skipped_array_fields = [] + skipped_no_csv_fields = [] + skipped_req_wrapper_fields = [] + verified_correct = 0 + corrected_seq = 0 + error_count = 0 + + current_complex_type = None + type_stack = [] # 중첩된 complexType을 추적하기 위한 스택 + + while i < len(lines): + line = lines[i] + line_processed = False + + try: + # complexType 시작 태그 확인 + type_match = re.search(r'<xsd:complexType\s+name="([^"]+)"', line) + if type_match: + if current_complex_type: + type_stack.append(current_complex_type) + current_complex_type = type_match.group(1) + print_color(f"현재 complexType: {current_complex_type}", Colors.BLUE) + + # complexType 종료 태그 확인 + if '</xsd:complexType>' in line: + if type_stack: + current_complex_type = type_stack.pop() + print_color(f"이전 complexType으로 복귀: {current_complex_type}", Colors.BLUE) + else: + current_complex_type = None + + # CSV에 있는 xsd:element 필드인지 확인 + if should_process_line(line, csv_data): + field_name = extract_field_name_from_line(line) + + if field_name and current_complex_type: + processed_fields.append(field_name) + print_color(f"처리 중인 필드: {field_name} (complexType: {current_complex_type})", Colors.CYAN) + + # 바로 위 라인이 주석인지 확인 (공백 라인 건너뛰면서) + comment_line_index = -1 + j = len(new_lines) - 1 + + while j >= 0: + prev_line = new_lines[j].strip() + if prev_line == '': + j -= 1 + continue + elif prev_line.startswith('<!--') and prev_line.endswith('-->'): + comment_line_index = j + break + else: + break + + if comment_line_index >= 0: + existing_comment = new_lines[comment_line_index] + + if has_seq_in_comment(existing_comment): + indentation = get_indentation(line) + expected_comment = create_comment(field_name, csv_data, indentation, current_complex_type) + + if expected_comment: + if comments_are_equal(existing_comment, expected_comment): + verified_correct += 1 + print_success(f" 주석 검증 통과") + else: + new_lines[comment_line_index] = expected_comment + '\n' + changes_made += 1 + corrected_seq += 1 + print_warning(f" SEQ 주석 수정: {field_name}") + print(f" 기존: {existing_comment.strip()}") + print(f" 수정: {expected_comment}") + else: + indentation = get_indentation(line) + new_comment = create_comment(field_name, csv_data, indentation, current_complex_type) + if new_comment: + new_lines[comment_line_index] = new_comment + '\n' + changes_made += 1 + print_warning(f" 주석 교체: {field_name}") + else: + indentation = get_indentation(line) + new_comment = create_comment(field_name, csv_data, indentation, current_complex_type) + if new_comment: + new_lines.append(new_comment + '\n') + changes_made += 1 + print_info(f" 주석 추가: {field_name}") + + line_processed = True + elif '<xsd:element' in line and 'name="' in line: + field_name = extract_field_name_from_line(line) + if field_name: + skip_reason = get_skip_reason(line, csv_data) + if skip_reason == "ARRAY_TYPE": + skipped_array_fields.append(field_name) + skipped_fields.append(field_name) + print_color(f"건너뛴 필드: {field_name} (배열 타입 - maxOccurs 속성)", Colors.YELLOW) + elif skip_reason == "REQ_WRAPPER_TYPE": + skipped_req_wrapper_fields.append(field_name) + skipped_fields.append(field_name) + print_color(f"건너뛴 필드: {field_name} (요청 래퍼 타입 - 정상)", Colors.BLUE) + elif skip_reason == "NO_CSV_DATA": + skipped_no_csv_fields.append(field_name) + skipped_fields.append(field_name) + print_error(f"건너뛴 필드: {field_name} (CSV에 데이터 없음 - 확인 필요!)") + else: + # 기타 이유로 건너뛴 경우 + skipped_fields.append(field_name) + print_warning(f"건너뛴 필드: {field_name} (기타 이유)") + + except Exception as e: + print_error(f"라인 처리 중 오류 발생 (라인 {i+1}): {str(e)}") + print_error(f"문제 라인: {line.strip()}") + error_count += 1 + + new_lines.append(line) + i += 1 + + # 결과 저장 + try: + with open(wsdl_file_path, 'w', encoding='utf-8') as f: + f.writelines(new_lines) + print_success("WSDL 파일 저장 완료") + except Exception as e: + print_error(f"WSDL 파일 저장 실패: {str(e)}") + return + + # 결과 출력 + print_color(f"\n{'='*50}", Colors.BOLD) + print_color(f"처리 완료: {target}", Colors.BOLD) + print_color(f"{'='*50}", Colors.BOLD) + + print_info(f"CSV 파일: {csv_file_path}") + print_info(f"WSDL 파일: {wsdl_file_path}") + print_info(f"백업 파일: {backup_path}") + + print_color(f"\n📊 처리 통계:", Colors.MAGENTA) + print(f" 총 변경사항: {changes_made}개") + print(f" 처리된 CSV 필드 수: {len(processed_fields)}") + print(f" 건너뛴 필드 총계: {len(skipped_fields)}") + print(f" ├─ 배열 타입 (정상): {len(skipped_array_fields)}개") + print(f" ├─ 요청 래퍼 타입 (정상): {len(skipped_req_wrapper_fields)}개") + print_color(f" └─ CSV 누락 (문제): {len(skipped_no_csv_fields)}개", Colors.RED if len(skipped_no_csv_fields) > 0 else Colors.WHITE) + print(f" 검증 통과한 SEQ 주석: {verified_correct}개") + print(f" 수정된 SEQ 주석: {corrected_seq}개") + print(f" 오류 발생 횟수: {error_count}개") + + # CSV 누락 필드 상세 표시 + if len(skipped_no_csv_fields) > 0: + print_error(f"\n⚠️ CSV에 누락된 필드 목록 (확인 필요):") + for field in skipped_no_csv_fields: + print_error(f" - {field}") + + # 최종 결과 + if error_count > 0: + print_error(f"\n⚠️ {error_count}개의 오류가 발생했습니다. 로그를 확인해주세요.") + + if len(skipped_no_csv_fields) > 0: + print_error(f"\n🚨 주의: {len(skipped_no_csv_fields)}개의 필드가 CSV에 누락되어 있습니다!") + print_error("이 필드들은 WSDL에 정의되어 있지만 CSV 스펙에 없어 주석이 생성되지 않았습니다.") + + if changes_made == 0: + print_success(f"\n모든 주석이 정확합니다! (검증된 SEQ 주석: {verified_correct}개)") + else: + print_success(f"\n{changes_made}개의 주석이 수정되었습니다.") + if corrected_seq > 0: + print(f" - 기존 SEQ 주석 수정: {corrected_seq}개") + if changes_made - corrected_seq > 0: + print(f" - 새로 추가/교체된 주석: {changes_made - corrected_seq}개") + +if __name__ == "__main__": + try: + csv_files = get_csv_files() + print_color(f"\n🎯 발견된 CSV 파일: {len(csv_files)}개", Colors.BOLD) + print_info(f"처리할 파일 목록: {csv_files}") + + total_files = len(csv_files) + success_count = 0 + error_count = 0 + + for i, target in enumerate(csv_files, 1): + print_color(f"\n{'='*60}", Colors.BOLD) + print_color(f"진행률: {i}/{total_files} - {target}", Colors.BOLD) + print_color(f"{'='*60}", Colors.BOLD) + + try: + process_wsdl_file(target) + success_count += 1 + except Exception as e: + print_error(f"파일 처리 실패 - {target}: {str(e)}") + error_count += 1 + + # 최종 통계 + print_color(f"\n{'='*60}", Colors.BOLD) + print_color("🏁 전체 처리 완료", Colors.BOLD) + print_color(f"{'='*60}", Colors.BOLD) + + print_success(f"성공: {success_count}개 파일") + if error_count > 0: + print_error(f"실패: {error_count}개 파일") + else: + print_success("모든 파일이 성공적으로 처리되었습니다!") + + # 발견된 SAP 타입들 출력 (PostgreSQL 매핑용) + print_color(f"\n{'='*60}", Colors.BOLD) + print_color("📊 발견된 SAP 타입 통계 (PostgreSQL 매핑용)", Colors.MAGENTA) + print_color(f"{'='*60}", Colors.BOLD) + + print_color(f"\n🔤 고유 SAP 타입 ({len(discovered_sap_types)}개):", Colors.CYAN) + for sap_type in sorted(discovered_sap_types): + print(f" - {sap_type}") + + print_color(f"\n📏 타입-사이즈 조합 ({len(type_size_combinations)}개):", Colors.YELLOW) + for combination in sorted(type_size_combinations): + print(f" - {combination}") + + print_color(f"\n💡 PostgreSQL 타입 매핑 가이드 (XML 파싱/조회용):", Colors.GREEN) + print(" 🎯 실용적 접근법:") + print(" - 대부분 → VARCHAR(500) 또는 TEXT (XML에서 모든 데이터가 문자열로 전송)") + print(" - 숫자 검색/정렬이 필요한 경우만 → NUMERIC") + print(" - 날짜 검색/정렬이 필요한 경우만 → DATE/TIMESTAMP") + print("") + print(" 📋 SAP 타입별 상세:") + print(" - CHAR, VARC, LCHR → VARCHAR(해당사이즈) 또는 TEXT") + print(" - DATS (날짜) → VARCHAR(8) 또는 DATE (YYYYMMDD 형식)") + print(" - TIMS (시간) → VARCHAR(6) 또는 TIME (HHMMSS 형식)") + print(" - CURR, DEC, QUAN, NUMB, NUMC, FLTP → VARCHAR 또는 NUMERIC") + print(" - CUKY (통화), UNIT (단위), LANG (언어) → VARCHAR(10)") + print("") + print(" ⚡ 권장: 초기에는 모두 VARCHAR/TEXT로 시작하고 필요시 변환") + + except Exception as e: + print_error(f"스크립트 실행 중 치명적 오류 발생: {str(e)}") + exit(1)
\ No newline at end of file |
