feat: 자재 관리 페이지 대규모 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 파이프 수량 계산 로직 수정 (단관 개수가 아닌 실제 길이 기반 계산)
- UI 전면 개편 (DevonThink 스타일의 간결하고 세련된 디자인)
- 자재별 그룹핑 로직 개선:
  * 플랜지: 동일 사양별 그룹핑, WN 스케줄 표시, ORIFICE 풀네임 표시
  * 피팅: 상세 타입 표시 (니플 길이, 엘보 각도/연결, 티 타입, 리듀서 타입 등)
  * 밸브: 동일 사양별 그룹핑, 타입/연결방식/압력 표시
  * 볼트: 크기/재질/길이별 그룹핑 (8SET → 개별 집계)
  * 가스켓: 동일 사양별 그룹핑, 재질/상세내역/두께 분리 표시
  * UNKNOWN: 원본 설명 전체 표시, 동일 항목 그룹핑
- 전체 카테고리 버튼 제거 (표시 복잡도 감소)
- 카테고리별 동적 컬럼 헤더 및 레이아웃 적용
This commit is contained in:
Hyungi Ahn
2025-09-09 09:24:45 +09:00
parent 4f8e395f87
commit 83b90ef05c
101 changed files with 10841 additions and 4813 deletions

View File

@@ -29,13 +29,13 @@ PIPE_MANUFACTURING = {
# ========== PIPE 끝 가공별 분류 ==========
PIPE_END_PREP = {
"BOTH_ENDS_BEVELED": {
"codes": ["BOE", "BOTH END", "BOTH BEVELED", "양쪽개선"],
"codes": ["BBE", "BOE", "BOTH END", "BOTH BEVELED", "양쪽개선"],
"cutting_note": "양쪽 개선",
"machining_required": True,
"confidence": 0.95
},
"ONE_END_BEVELED": {
"codes": ["BE", "BEV", "PBE", "PIPE BEVELED END"],
"codes": ["BE", "BEV", "PBE", "PIPE BEVELED END", "POE"],
"cutting_note": "한쪽 개선",
"machining_required": True,
"confidence": 0.95
@@ -45,9 +45,85 @@ PIPE_END_PREP = {
"cutting_note": "무 개선",
"machining_required": False,
"confidence": 0.95
},
"THREADED": {
"codes": ["TOE", "THE", "THREADED", "나사", "스레드"],
"cutting_note": "나사 가공",
"machining_required": True,
"confidence": 0.90
}
}
# ========== 구매용 파이프 분류 (끝단 가공 제외) ==========
def get_purchase_pipe_description(description: str) -> str:
"""구매용 파이프 설명 - 끝단 가공 정보 제거"""
# 모든 끝단 가공 코드들을 수집
end_prep_codes = []
for prep_data in PIPE_END_PREP.values():
end_prep_codes.extend(prep_data["codes"])
# 설명에서 끝단 가공 코드 제거
clean_description = description.upper()
# 끝단 가공 코드들을 길이 순으로 정렬 (긴 것부터 처리)
end_prep_codes.sort(key=len, reverse=True)
for code in end_prep_codes:
# 단어 경계를 고려하여 제거 (부분 매칭 방지)
pattern = r'\b' + re.escape(code) + r'\b'
clean_description = re.sub(pattern, '', clean_description, flags=re.IGNORECASE)
# 끝단 가공 관련 패턴들 추가 제거
# BOE-POE, POE-TOE 같은 조합 패턴들
end_prep_patterns = [
r'\b[A-Z]{2,3}E-[A-Z]{2,3}E\b', # BOE-POE, POE-TOE 등
r'\b[A-Z]{2,3}E-[A-Z]{2,3}\b', # BOE-TO, POE-TO 등
r'\b[A-Z]{2,3}-[A-Z]{2,3}E\b', # BO-POE, PO-TOE 등
r'\b[A-Z]{2,3}-[A-Z]{2,3}\b', # BO-PO, PO-TO 등
]
for pattern in end_prep_patterns:
clean_description = re.sub(pattern, '', clean_description, flags=re.IGNORECASE)
# 남은 하이픈과 공백 정리
clean_description = re.sub(r'\s*-\s*', ' ', clean_description) # 하이픈 제거
clean_description = re.sub(r'\s+', ' ', clean_description).strip() # 연속 공백 정리
return clean_description
def extract_end_preparation_info(description: str) -> Dict:
"""파이프 설명에서 끝단 가공 정보 추출"""
desc_upper = description.upper()
# 끝단 가공 코드 찾기
for prep_type, prep_data in PIPE_END_PREP.items():
for code in prep_data["codes"]:
if code in desc_upper:
return {
"end_preparation_type": prep_type,
"end_preparation_code": code,
"machining_required": prep_data["machining_required"],
"cutting_note": prep_data["cutting_note"],
"confidence": prep_data["confidence"],
"matched_pattern": code,
"original_description": description,
"clean_description": get_purchase_pipe_description(description)
}
# 기본값: PBE (양쪽 무개선)
return {
"end_preparation_type": "NO_BEVEL", # PBE로 매핑될 예정
"end_preparation_code": "PBE",
"machining_required": False,
"cutting_note": "양쪽 무개선 (기본값)",
"confidence": 0.5,
"matched_pattern": "DEFAULT",
"original_description": description,
"clean_description": get_purchase_pipe_description(description)
}
# ========== PIPE 스케줄별 분류 ==========
PIPE_SCHEDULE = {
"patterns": [
@@ -62,6 +138,23 @@ PIPE_SCHEDULE = {
]
}
def classify_pipe_for_purchase(dat_file: str, description: str, main_nom: str,
length: Optional[float] = None) -> Dict:
"""구매용 파이프 분류 - 끝단 가공 정보 제외"""
# 끝단 가공 정보 제거한 설명으로 분류
clean_description = get_purchase_pipe_description(description)
# 기본 파이프 분류 수행
result = classify_pipe(dat_file, clean_description, main_nom, length)
# 구매용임을 표시
result["purchase_classification"] = True
result["original_description"] = description
result["clean_description"] = clean_description
return result
def classify_pipe(dat_file: str, description: str, main_nom: str,
length: Optional[float] = None) -> Dict:
"""