feat: 자재 리비전 비교 및 구매 목록 시스템 구현

- 자재 리비전간 비교 기능 추가 (MaterialComparisonPage) - 버그 해결 필요
- 리비전간 추가 구매 필요 자재 분석 페이지 추가 (RevisionPurchasePage)
- 자재 비교 결과 컴포넌트 구현 (MaterialComparisonResult)
- 자재 비교 API 라우터 추가 (material_comparison.py) - 로직 개선 필요
- 자재 비교 시스템 데이터베이스 스키마 추가
- FileManager, FileUpload 컴포넌트 개선
- BOMManagerPage 제거 및 새로운 구조로 리팩토링
- 자재 분류기 및 스키마 개선

TODO: 자재 비교 알고리즘 정확도 향상 및 예외 처리 강화 필요
This commit is contained in:
Hyungi Ahn
2025-07-22 15:56:40 +09:00
parent 6ca1cd17e2
commit 534015cc7c
16 changed files with 2577 additions and 267 deletions

View File

@@ -169,9 +169,18 @@ def parse_file_data(file_path):
async def upload_file(
file: UploadFile = File(...),
job_no: str = Form(...),
revision: str = Form("Rev.0"),
revision: str = Form("Rev.0"), # 기본값은 Rev.0 (새 BOM)
parent_file_id: Optional[int] = Form(None), # 리비전 업로드 시 부모 파일 ID
bom_name: Optional[str] = Form(None), # BOM 이름 (사용자 입력)
db: Session = Depends(get_db)
):
print(f"📥 업로드 요청 받음:")
print(f" - 파일명: {file.filename}")
print(f" - job_no: {job_no}")
print(f" - revision: {revision}")
print(f" - parent_file_id: {parent_file_id}")
print(f" - bom_name: {bom_name}")
print(f" - parent_file_id 타입: {type(parent_file_id)}")
if not validate_file_extension(file.filename):
raise HTTPException(
status_code=400,
@@ -198,11 +207,67 @@ async def upload_file(
parsed_count = len(materials_data)
print(f"파싱 완료: {parsed_count}개 자재")
# 리비전 업로드인 경우만 자동 리비전 생성
if parent_file_id is not None:
print(f"리비전 업로드 모드: parent_file_id = {parent_file_id}")
# 부모 파일의 정보 조회
parent_query = text("""
SELECT original_filename, revision, bom_name FROM files
WHERE id = :parent_file_id AND job_no = :job_no
""")
parent_result = db.execute(parent_query, {
"parent_file_id": parent_file_id,
"job_no": job_no
})
parent_file = parent_result.fetchone()
if not parent_file:
raise HTTPException(status_code=404, detail="부모 파일을 찾을 수 없습니다.")
# 해당 BOM의 최신 리비전 확인 (bom_name 기준)
bom_name_to_use = parent_file[2] or parent_file[0] # bom_name 우선, 없으면 original_filename
latest_revision_query = text("""
SELECT revision FROM files
WHERE job_no = :job_no
AND (bom_name = :bom_name OR (bom_name IS NULL AND original_filename = :bom_name))
ORDER BY revision DESC
LIMIT 1
""")
latest_result = db.execute(latest_revision_query, {
"job_no": job_no,
"bom_name": bom_name_to_use
})
latest_revision = latest_result.fetchone()
if latest_revision:
latest_rev = latest_revision[0]
if latest_rev.startswith("Rev."):
try:
rev_num = int(latest_rev.replace("Rev.", ""))
revision = f"Rev.{rev_num + 1}"
except ValueError:
revision = "Rev.1"
else:
revision = "Rev.1"
print(f"리비전 업로드: {latest_rev}{revision}")
else:
revision = "Rev.1"
print(f"첫 번째 리비전: {revision}")
# 파일명을 부모와 동일하게 유지
file.filename = parent_file[0]
else:
# 일반 업로드 (새 BOM)
print(f"일반 업로드 모드: 새 BOM 파일 (Rev.0)")
# 파일 정보 저장
print("DB 저장 시작")
file_insert_query = text("""
INSERT INTO files (filename, original_filename, file_path, job_no, revision, description, file_size, parsed_count, is_active)
VALUES (:filename, :original_filename, :file_path, :job_no, :revision, :description, :file_size, :parsed_count, :is_active)
INSERT INTO files (filename, original_filename, file_path, job_no, revision, bom_name, description, file_size, parsed_count, is_active)
VALUES (:filename, :original_filename, :file_path, :job_no, :revision, :bom_name, :description, :file_size, :parsed_count, :is_active)
RETURNING id
""")
@@ -212,6 +277,7 @@ async def upload_file(
"file_path": str(file_path),
"job_no": job_no,
"revision": revision,
"bom_name": bom_name or file.filename, # bom_name 우선, 없으면 파일명
"description": f"BOM 파일 - {parsed_count}개 자재",
"file_size": file.size,
"parsed_count": parsed_count,
@@ -434,8 +500,28 @@ async def upload_file(
else:
end_prep = str(end_prep_info) if end_prep_info else ""
# 재질 정보 - 이미 정제된 material_grade 사용
material_spec = material_data.get("material_grade", "")
# 재질 정보 - 분류 결과에서 상세 정보 추출
material_grade_from_classifier = ""
if isinstance(material_info, dict):
material_grade_from_classifier = material_info.get("grade", "")
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
if material_grade_from_classifier and material_grade_from_classifier != "UNKNOWN":
material_spec = material_grade_from_classifier
# materials 테이블의 material_grade도 업데이트
db.execute(text("""
UPDATE materials
SET material_grade = :new_material_grade
WHERE id = :material_id
"""), {
"new_material_grade": material_grade_from_classifier,
"material_id": material_id
})
print(f"PIPE material_grade 업데이트: {material_grade_from_classifier}")
else:
# 기존 파싱 결과 사용
material_spec = material_data.get("material_grade", "")
# 제조방법 추출
manufacturing_method = ""
@@ -488,6 +574,18 @@ async def upload_file(
material_standard = material_info.get("standard", "")
material_grade = material_info.get("grade", "")
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
if material_grade and material_grade != "UNKNOWN":
db.execute(text("""
UPDATE materials
SET material_grade = :new_material_grade
WHERE id = :material_id
"""), {
"new_material_grade": material_grade,
"material_id": material_id
})
print(f"FITTING material_grade 업데이트: {material_grade}")
# main_size와 reduced_size
main_size = material_data.get("main_nom") or material_data.get("size_spec", "")
reduced_size = material_data.get("red_nom", "")
@@ -572,6 +670,18 @@ async def upload_file(
if isinstance(material_info, dict):
material_standard = material_info.get("standard", "")
material_grade = material_info.get("grade", "")
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
if material_grade and material_grade != "UNKNOWN":
db.execute(text("""
UPDATE materials
SET material_grade = :new_material_grade
WHERE id = :material_id
"""), {
"new_material_grade": material_grade,
"material_id": material_id
})
print(f"FLANGE material_grade 업데이트: {material_grade}")
# 사이즈 정보
size_inches = material_data.get("main_nom") or material_data.get("size_spec", "")
@@ -718,6 +828,18 @@ async def upload_file(
if isinstance(material_info, dict):
material_standard = material_info.get("standard", "")
material_grade = material_info.get("grade", "")
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
if material_grade and material_grade != "UNKNOWN":
db.execute(text("""
UPDATE materials
SET material_grade = :new_material_grade
WHERE id = :material_id
"""), {
"new_material_grade": material_grade,
"material_id": material_id
})
print(f"BOLT material_grade 업데이트: {material_grade}")
# 압력 등급 (150LB 등)
pressure_rating = ""
@@ -821,6 +943,18 @@ async def upload_file(
body_material = material_info.get("grade", "")
# 트림 재질은 일반적으로 바디와 동일하거나 별도 명시
trim_material = body_material
# 분류기에서 더 상세한 재질 정보가 나왔으면 업데이트
if body_material and body_material != "UNKNOWN":
db.execute(text("""
UPDATE materials
SET material_grade = :new_material_grade
WHERE id = :material_id
"""), {
"new_material_grade": body_material,
"material_id": material_id
})
print(f"VALVE material_grade 업데이트: {body_material}")
# 사이즈 정보
size_inches = material_data.get("main_nom") or material_data.get("size_spec", "")
@@ -879,6 +1013,8 @@ async def upload_file(
"original_filename": file.filename,
"file_id": file_id,
"materials_count": materials_inserted,
"saved_materials_count": materials_inserted,
"revision": revision, # 생성된 리비전 정보 추가
"parsed_count": parsed_count
}